public static Builder nameBuilder(String name, StackFunction function) {
	return new Builder(name, function);
}
 public static Builder terminalNameBuilder(String name, TerminalFunction function) {
	return new Builder(name, function);
}
 public interface Window {
	
	public int viewCount();
	public View getView(int i);
	public void apply(DoubleUnaryOperator duo);
	public void apply(DoubleBinaryOperator duo, Window other);


	public static interface View extends DMatrix {
		
		public double get(int i);
		public int size();
		public boolean reset();
		public boolean returnNa();
		public Array additions();
		public Array subtractions();
		
		default public DMatrixRMaj toMatrix() {
			return new DMatrixRMaj(this);
		}
		
		@Override
		default int getNumRows() {
			return size();
		}
		@Override
		default int getNumCols() {
			return 1;
		}
		@Override
		default MatrixType getType() {
			return MatrixType.DDRM;
		}
		@Override
		default double get(int row, int col) {
			return get(row);
		}
		@Override
		default double unsafe_get(int row, int col) {
			return get(row);
		}
		@Override
		default int getNumElements() {
			return size();
		}

		@Override default <T extends Matrix> T copy() { throw new UnsupportedOperationException(); }
		@Override default <T extends Matrix> T createLike() { throw new UnsupportedOperationException(); }
		@Override default void set(Matrix original) { throw new UnsupportedOperationException(); }
		@Override default void print() { throw new UnsupportedOperationException(); }
		@Override default void print(String format) { throw new UnsupportedOperationException(); }
		@Override default void set(int row, int col, double val) { throw new UnsupportedOperationException(); }
		@Override default void unsafe_set(int row, int col, double val) { throw new UnsupportedOperationException(); }

	}

	public static interface Array {
		public int size();
		public double get(int i);
		default public Array subArray(int start, int end) {
			return null;
		}
	}
	
	public static class Rolling implements Window {
		
		private final double[] d;
		private final int size;
		private final boolean returnNa;
		
		public Rolling(double[] d, int size, boolean returnNa) {
			this.d = d;
			this.size = size;
			this.returnNa = returnNa;
		}

		@Override
		public int viewCount() {
			return d.length - size + 1;
		}

		@Override
		public View getView(int v) {
			return new View() {
				private static final long serialVersionUID = 1L;

				@Override
				public double get(int i) {
					return d[i + v];
				}

				@Override
				public Array additions() {
					return v == 0 ? new BaseArray(d, 0, size) : new BaseArray(d, size + v - 1, size + v);
				}

				@Override
				public Array subtractions() {
					return v == 0 ? new EmptyArray() : new BaseArray(d, v - 1, v);
				}

				@Override
				public int size() {
					return size;
				}

				@Override
				public boolean reset() {
					return v == 0;
				}

				@Override
				public boolean returnNa() {
					if(!returnNa) {
						return false;
					}
					double last = d[size + v - 1];
					return last != last;
				}};
		}

		@Override
		public void apply(DoubleUnaryOperator duo) {
			for(int i = 0; i < d.length; i++) {
				d[i] = duo.applyAsDouble(d[i]);
			}
		}

		@Override
		public void apply(DoubleBinaryOperator dbo, Window other) {
			if(!other.getClass().equals(Rolling.class)) {
				throw new IllegalArgumentException("Incompatible window types.");
			} else if (((Rolling) other).d.length != d.length || ((Rolling) other).size != size) {
				throw new IllegalArgumentException("Incompatible window dimensions.");
			}
			for(int i = 0; i < d.length; i++) {
				d[i] = dbo.applyAsDouble(d[i], ((Rolling)other).d[i]);
			}
		}
		
	}
	

	public static class Expanding implements Window {
		
		private final double[] d;
		private final int offset;
		
		public Expanding(double[] d, int offset) {
			this.d = d;
			this.offset = offset;
		}

		@Override
		public int viewCount() {
			return d.length - offset;
		}

		@Override
		public View getView(int v) {
			return new View() {
				private static final long serialVersionUID = 1L;

				@Override
				public double get(int i) {
					return d[i];
				}

				@Override
				public Array additions() {
					return v + offset < 0 ? new EmptyArray() :
							v == 0 ? new BaseArray(d, 0, offset + 1) :
								new BaseArray(d, v + offset, v + offset + 1);
				}

				@Override
				public Array subtractions() {
					return new EmptyArray();
				}

				@Override
				public int size() {
					return Math.max(0, v + 1 + offset);
				}

				@Override
				public boolean reset() {
					return v == 0;
				}

				@Override
				public boolean returnNa() {
					if(v + offset < 0) {
						return true;
					}
					double last = d[v + offset];
					return last != last;
				}};
		}

		@Override
		public void apply(DoubleUnaryOperator duo) {
			for(int i = 0; i < d.length; i++) {
				d[i] = duo.applyAsDouble(d[i]);
			}
		}

		@Override
		public void apply(DoubleBinaryOperator dbo, Window other) {
			if(!other.getClass().equals(Expanding.class)) {
				throw new IllegalArgumentException("Incompatible window types.");
			} else if (((Expanding) other).d.length != d.length || ((Expanding) other).offset != offset) {
				throw new IllegalArgumentException("Incompatible window dimensions.");
			}
			for(int i = 0; i < d.length; i++) {
				d[i] = dbo.applyAsDouble(d[i], ((Expanding)other).d[i]);
			}
		}
		
	}

	public static class ReverseExpanding implements Window {
		
		private final double[] d;
		
		public ReverseExpanding(double[] d) {
			this.d = d;
		}

		@Override
		public int viewCount() {
			return d.length;
		}

		@Override
		public View getView(int v) {
			return new View() {
				private static final long serialVersionUID = 1L;

				@Override
				public double get(int i) {
					return d[v + i];
				}

				@Override
				public Array additions() {
					return v == 0 ? new BaseArray(d, 0, d.length) : new EmptyArray();
				}

				@Override
				public Array subtractions() {
					return v == 0 ? new EmptyArray() : new BaseArray(d, v - 1, v);
				}

				@Override
				public int size() {
					return d.length - v;
				}

				@Override
				public boolean reset() {
					return v == 0;
				}

				@Override
				public boolean returnNa() {
					return false;
				}};
		}

		@Override
		public void apply(DoubleUnaryOperator duo) {
			for(int i = 0; i < d.length; i++) {
				d[i] = duo.applyAsDouble(d[i]);
			}
		}

		@Override
		public void apply(DoubleBinaryOperator dbo, Window other) {
			if(!other.getClass().equals(ReverseExpanding.class)) {
				throw new IllegalArgumentException("Incompatible window types.");
			} else if (((ReverseExpanding) other).d.length != d.length) {
				throw new IllegalArgumentException("Incompatible window dimensions.");
			}
			for(int i = 0; i < d.length; i++) {
				d[i] = dbo.applyAsDouble(d[i], ((ReverseExpanding)other).d[i]);
			}
		}
		
	}
	
	public static class Cross implements Window {
		
		final double[][] d;
		
		public Cross(double[][] d) {
			this.d = d;
		}

		@Override
		public int viewCount() {
			return d.length < 1 ? 0 : d[0].length;
		}

		@Override
		public View getView(int v) {
			return new View() {
				private static final long serialVersionUID = 1L;

				@Override
				public double get(int i) {
					return d[i][v];
				}

				@Override
				public int size() {
					return d.length;
				}

				@Override
				public boolean reset() {
					return true;
				}

				@Override
				public boolean returnNa() {
					return false;
				}

				@Override
				public Array additions() {
					return new CrossArray(d, v);
				}

				@Override
				public Array subtractions() {
					return new EmptyArray();
				}};
		}

		@Override
		public void apply(DoubleUnaryOperator duo) {
			for(int i = 0; i < d.length; i++) {
				for(int j = 0; i < d.length; i++) {
					d[i][j] = duo.applyAsDouble(d[i][j]);
				}
			}
		}

		@Override
		public void apply(DoubleBinaryOperator duo, Window other) {
			if(!other.getClass().equals(Cross.class)) {
				throw new IllegalArgumentException("Incompatible window types.");
			} else if (((Cross) other).d.length != d.length || (d.length > 0 && ((Cross) other).d[0].length != d[0].length)) {
				throw new IllegalArgumentException("Incompatible window dimensions.");
			}
			for(int i = 0; i < d.length; i++) {
				for(int j = 0; i < d.length; i++) {
					d[i][j] = duo.applyAsDouble(d[i][j], ((Cross)other).d[i][j]);
				}
			}
		}
		
	}

	public static class Downsample implements Window {
		
		final double[] data;
		final List<LocalDate> dates;
		final PeriodicRange<?> dataRange;	

		public Downsample(double[] data, List<LocalDate> dates, PeriodicRange<?> dataRange) {
			this.data = data;
			this.dates = dates;
			this.dataRange = dataRange;
		}

		@Override
		public int viewCount() {
			return dates.size();
		}

		@Override
		public View getView(int v) {
			return new View() {
				private static final long serialVersionUID = 1L;

				int start = v == 0 ? 0 : (int) dataRange.indexOf(dates.get(v-1)) + 1; // inclusive
				int end = (int) dataRange.indexOf(dates.get(v)) + 1; // exclusive

				@Override
				public double get(int i) {
					return data[start + i];
				}

				@Override
				public int size() {
					return end - start + 1;
				}

				@Override
				public boolean reset() {
					return true;
				}

				@Override
				public boolean returnNa() {
					return false;
				}

				@Override
				public Array additions() {
					return new BaseArray(data, start, end);
				}

				@Override
				public Array subtractions() {
					return new EmptyArray();
				}};
		}

		@Override
		public void apply(DoubleUnaryOperator duo) {
			for(int i = 0; i < data.length; i++) {
				data[i] = duo.applyAsDouble(data[i]);
			}
		}

		@Override
		public void apply(DoubleBinaryOperator dbo, Window other) {
			if(!other.getClass().equals(Downsample.class)) {
				throw new IllegalArgumentException("Incompatible window types.");
			} else if (((Downsample) other).data.length != data.length || ((Downsample) other).dataRange != dataRange) {
				throw new IllegalArgumentException("Incompatible window dimensions.");
			}
			for(int i = 0; i < data.length; i++) {
				data[i] = dbo.applyAsDouble(data[i], ((Downsample)other).data[i]);
			}
		}
	}
	
	public static class ResetOnNa implements Window {
		
		private final Window wrapped;
		

		public ResetOnNa(Window wrapped) {
			this.wrapped = wrapped;
		}

		@Override
		public int viewCount() {
			return wrapped.viewCount();
		}

		@Override
		public View getView(int i) {
			final var v = wrapped.getView(i);
			return new View() {
				private static final long serialVersionUID = 1L;

				@Override
				public double get(int i) {
					return v.get(i);
				}

				@Override
				public int size() {
					return v.size();
				}

				@Override
				public boolean reset() {
					return v.reset() || v.returnNa();
				}

				@Override
				public boolean returnNa() {
					return v.returnNa();
				}

				@Override
				public Array additions() {
					return v.additions();
				}

				@Override
				public Array subtractions() {
					return v.subtractions();
				}};
		}

		@Override
		public void apply(DoubleUnaryOperator duo) {
			wrapped.apply(duo);
		}

		@Override
		public void apply(DoubleBinaryOperator duo, Window other) {
			wrapped.apply(duo,other);
		}
		
	}

	public static class EmptyArray implements Array {

		@Override
		public int size() {
			return 0;
		}

		@Override
		public double get(int i) {
			return Double.NaN;
		}
		
	}
		
	public static class BaseArray implements Array {
		
		double[] d;
		final int start;
		final int end;

		public BaseArray(double[] d, int start, int end) {
			if(start > end || end > d.length) {
				throw new ArrayIndexOutOfBoundsException();
			}
			this.d = d;
			this.start = start;
			this.end = end;
		}

		@Override
		public int size() {
			return end - start;
		}

		@Override
		public double get(int i) {
			return d[i + start];
		}
		
	}

	public static class CrossArray implements Array {
		
		final double[][] d;
		final int row;
		

		private CrossArray(double[][] d, int row) {
			if(row >= d[0].length) {
				throw new ArrayIndexOutOfBoundsException("Cross array row " + row + " >= length " + d.length);
			}
			this.d = d;
			this.row = row;
		}

		public int size() {
			return d.length;
		}

		public double get(int i) {
			return d[i][row];
		}
	}
	
}
 @FunctionalInterface
public interface RowsFunction<T> {
	public T getRows(PeriodicRange<?> range, Column<?>[] columns);
}
 @FunctionalInterface
public interface RowsFunctionGeneric<T> extends RowsFunction<T> {
	public <P extends Period<P>> T getRows(PeriodicRange<P> range, Column<?>[] columns, Class<?> clazz);

	default public T getRows(PeriodicRange<?> range, Column<?>[] columns) {
		return getRows(range, columns, null);
	}
}
 public static class Expression implements TableFunction {
		
	private final boolean isSubExpression;
	private ArrayList<TableFunction>  functions = new ArrayList<>();
	private	Optional<Indexer> indexer = null;
	private	Optional<TerminalFunction> terminalFunction = Optional.empty();
	private Set<String> dependencies = new HashSet<>();
	private StringBuilder text = new StringBuilder();
	private	Optional<String> nameLiteral = Optional.empty();
		
	public Expression(boolean isSubExpression) {
		this.isSubExpression = isSubExpression;
	}

	public Table evaluate(Pinto pinto) {
		if(!terminalFunction.isPresent()) {
			throw new PintoSyntaxException("Need terminal to evaluate expression.");
		}
		return terminalFunction.get().apply(pinto, this);
	}

	@Override
	public void accept(Pinto pinto, Table table) {
		try {
			this.indexer.orElse(new Indexer(true)).accept(pinto, table);
			for(int i = 0; i < functions.size(); i++) {
				functions.get(i).accept(pinto, table);
			}
			table.collapseFunction();
		} catch(RuntimeException e) {
			throw new PintoSyntaxException("Error in expression: \"" + getText().trim() + "\": " + e.getLocalizedMessage() + "\n",e);
		}
	}
		
	public void addIndexer(Indexer indexer) {
		if(this.indexer == null) {
			indexer.setIndexForExpression();
			this.indexer = Optional.of(indexer);
		} else {
			functions.add(indexer);
		}
	}

	public void addFunction(Column<?> col) {
		addFunction((StackFunction) (p, s) -> s.addFirst(col));
	}

	public void addFunction(TableFunction function) {
		if(this.indexer == null) {
			this.indexer = Optional.empty();
		}
		functions.add(function);
	}

	public void setTerminal(TerminalFunction function) {
		if(isSubExpression) {
			throw new PintoSyntaxException("Sub-expressions cannot include terminal functions");
		}
		this.terminalFunction = Optional.of(function);
	}

	public ArrayList<TableFunction> getFunctions() {
		return functions;
	}
		
	public boolean isExpressionStart() {
		return functions.size() == 0;
	}

	public boolean isNullary() {
		return (!indexer.isPresent()) || indexer.get().isNone();
	}

	public Set<String> getDependencies() {
		return dependencies;
	}

	public String getText() {
		return text.toString();
	}

	public void addText(String expression) {
		this.text.append(" ").append(expression);
	}

	public Optional<String> getNameLiteral() {
		return nameLiteral;
	}

	public void setNameLiteral(String nameLiteral) {
		this.nameLiteral = Optional.of(nameLiteral);
	}
		
	public boolean hasTerminal() {
		return terminalFunction.isPresent();
	}

}
 @FunctionalInterface
public static interface StackFunction extends TableFunction {
	void accept(Pinto p, Stack s);

	default void accept(Pinto p, Table t) {
			List<Stack> inputs = t.takeTop();
			for(int i = 0; i < inputs.size(); i++) {
				accept(p, inputs.get(i));
			}
			t.insertAtTop(inputs);
	}
}
 public static class Expanding implements Window {
		
	private final double[] d;
	private final int offset;
		
	public Expanding(double[] d, int offset) {
		this.d = d;
		this.offset = offset;
	}

	@Override
	public int viewCount() {
		return d.length - offset;
	}

	@Override
	public View getView(int v) {
		return new View() {
			private static final long serialVersionUID = 1L;

			@Override
			public double get(int i) {
				return d[i];
			}

			@Override
			public Array additions() {
				return v + offset < 0 ? new EmptyArray() :
						v == 0 ? new BaseArray(d, 0, offset + 1) :
							new BaseArray(d, v + offset, v + offset + 1);
			}

			@Override
			public Array subtractions() {
				return new EmptyArray();
			}

			@Override
			public int size() {
				return Math.max(0, v + 1 + offset);
			}

			@Override
			public boolean reset() {
				return v == 0;
			}

			@Override
			public boolean returnNa() {
				if(v + offset < 0) {
					return true;
				}
				double last = d[v + offset];
				return last != last;
			}};
	}

	@Override
	public void apply(DoubleUnaryOperator duo) {
		for(int i = 0; i < d.length; i++) {
			d[i] = duo.applyAsDouble(d[i]);
		}
	}

	@Override
	public void apply(DoubleBinaryOperator dbo, Window other) {
		if(!other.getClass().equals(Expanding.class)) {
			throw new IllegalArgumentException("Incompatible window types.");
		} else if (((Expanding) other).d.length != d.length || ((Expanding) other).offset != offset) {
			throw new IllegalArgumentException("Incompatible window dimensions.");
		}
		for(int i = 0; i < d.length; i++) {
			d[i] = dbo.applyAsDouble(d[i], ((Expanding)other).d[i]);
		}
	}
		
}
 public static class Rolling implements Window {
		
	private final double[] d;
	private final int size;
	private final boolean returnNa;
		
	public Rolling(double[] d, int size, boolean returnNa) {
		this.d = d;
		this.size = size;
		this.returnNa = returnNa;
	}

	@Override
	public int viewCount() {
		return d.length - size + 1;
	}

	@Override
	public View getView(int v) {
		return new View() {
			private static final long serialVersionUID = 1L;

			@Override
			public double get(int i) {
				return d[i + v];
			}

			@Override
			public Array additions() {
				return v == 0 ? new BaseArray(d, 0, size) : new BaseArray(d, size + v - 1, size + v);
			}

			@Override
			public Array subtractions() {
				return v == 0 ? new EmptyArray() : new BaseArray(d, v - 1, v);
			}

			@Override
			public int size() {
				return size;
			}

			@Override
			public boolean reset() {
				return v == 0;
			}

			@Override
			public boolean returnNa() {
				if(!returnNa) {
					return false;
				}
				double last = d[size + v - 1];
				return last != last;
			}};
	}

	@Override
	public void apply(DoubleUnaryOperator duo) {
		for(int i = 0; i < d.length; i++) {
			d[i] = duo.applyAsDouble(d[i]);
		}
	}

	@Override
	public void apply(DoubleBinaryOperator dbo, Window other) {
		if(!other.getClass().equals(Rolling.class)) {
			throw new IllegalArgumentException("Incompatible window types.");
		} else if (((Rolling) other).d.length != d.length || ((Rolling) other).size != size) {
			throw new IllegalArgumentException("Incompatible window dimensions.");
		}
		for(int i = 0; i < d.length; i++) {
			d[i] = dbo.applyAsDouble(d[i], ((Rolling)other).d[i]);
		}
	}
		
}
 public abstract class Period<T extends Period<T>> implements Comparable<Period<T>> {
	
		final protected long value;

		public abstract LocalDate endDate();

		protected abstract T makeSame(long value);
		
		protected Period(long value) {
			this.value = value;
		}
		
		public LocalDate startDate() {
			return previous().endDate().plusDays(1);
		}

		public long longValue() {
			return value;
		}
		
		public boolean isBefore(T other) {
			return compareTo(other) == -1;
		}

		public boolean isAfter(T other) {
			return compareTo(other) == 1;
		}
		
		public T next() {
			return offset(1);
		}

		public T previous() {
			return offset(-1);
		}
		
		public T offset(long value) {
			return makeSame(this.value + value);
		}
		
		public long dayCount() {
			return previous().endDate().until(endDate(),ChronoUnit.DAYS);
		}
		
		public double yearFrac() {
			return dayCount() / (double) Year.from(endDate()).length();
		}
		
		@Override
		public int compareTo(Period<T> other) {
			if(! getClass().equals(other.getClass())) {
				throw new IllegalArgumentException("Periods can only be compared to other "
						+ "Periods of the same type.");
			}
			return value == other.value ? 0 : value > other.value ? 1 : -1;
		}
		
		@Override
		public boolean equals(Object obj) {
			if (obj == null) return false;
	        if (getClass() != obj.getClass()) return false;
	        @SuppressWarnings("unchecked")
	        final T other = (T) obj;
	        return other.value == value;
		}

		@Override
		public int hashCode() {
			return Objects.hashCode(getClass(), value);
		}

		@Override
		public String toString() {
			return MoreObjects.toStringHelper(this).add("end", endDate()).toString();
		}
		
}
 public final class PeriodicRange<P extends Period<P>> {
	
	private final Periodicity<P> periodicity;
	private final P startInclusive;
	private final P endInclusive;
	private final String id = ID.getId();

	PeriodicRange(Periodicity<P> periodcity, P startInclusive, P endInclusive) {
		if(endInclusive.isBefore(startInclusive)) {
			throw new IllegalArgumentException("Range end must be >= range start");
		}
		this.periodicity = periodcity;
		this.startInclusive = startInclusive;
		this.endInclusive = endInclusive;
	}
	
	public String getId() {
		return id;
	}
	
	public List<P> values() {
		List<P> l = new ArrayList<>();
		P p = start();
		do {
			l.add(p);
			p = (P) p.next();
		} while(!p.isAfter(end()));
		return l;
	}
	
	public List<LocalDate> dates() {
		List<LocalDate> l = new ArrayList<>();
		P p = start();
		do {
			l.add(p.endDate());
			p = p.next();
		} while(!p.isAfter(end()));
		return l;
	}

	public long[] datesInMillis() {
		var zone = ZoneId.of("GMT");
		long[] d = new long[(int)size()];
		int i = 0;
		P p = start();
		do {
			d[i++] = p.endDate().atStartOfDay(zone).toInstant().toEpochMilli();
			p = p.next();
		} while(!p.isAfter(end()));
		return d;
	}
	
	public long size() {
		return periodicity.distance(startInclusive, endInclusive) + 1;
	}
	
	public long indexOf(P period) {
		return periodicity.distance(startInclusive, (P) period);
	}

	public long indexOf(LocalDate d) {
		return periodicity.distance(startInclusive, periodicity.from(d));
	}

	public P start() {
		return startInclusive;
	}

	public P end() {
		return endInclusive;
	}
	
	public PeriodicRange<P> expand(long offset) {
		P start = periodicity.offset(Math.min(offset, 0), start());
		P end = periodicity.offset(Math.max(offset, 0), end());
		return periodicity.range(start, end);
	}

	public Periodicity<P> periodicity() {
		return periodicity;
	}
	
	public Map<String,String> asStringMap() {
		return new ImmutableMap.Builder<String, String>()
				.put("start", start().endDate().toString())
				.put("end", end().endDate().toString())
				.put("freq", periodicity().code()).build();	
	}
	
	@Override
	public boolean equals(Object obj) {
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final PeriodicRange<?> other = (PeriodicRange<?>) obj;
        return Objects.equals(this.periodicity, other.periodicity)
            && Objects.equals(this.startInclusive, other.startInclusive)
            && Objects.equals(this.endInclusive, other.endInclusive);
	}
	
	@Override
	public String toString() {
		return Joiner.on(":").join(periodicity.code(),start().toString(),end().toString());
	}
	
	@Override
	public int hashCode() {
		return Objects.hash(periodicity.code(),startInclusive.hashCode(),endInclusive.hashCode());
	}
}
 public class Periodicities {
	
	public static final Map<String,Supplier<Periodicity<?>>> map = 
			new ImmutableMap.Builder<String,Supplier<Periodicity<?>>>()
			.put("B",() -> new BusinessDaily())
			.put("W-MON",() -> new WeeklyEndingMondays())
			.put("W-TUE",() -> new WeeklyEndingTuesdays())
			.put("W-WED",() -> new WeeklyEndingWednesdays())
			.put("W-THU",() -> new WeeklyEndingThursdays())
			.put("W-FRI",() -> new WeeklyEndingFridays())
			.put("BM",() -> new BusinessMonthly())
			.put("BQ-DEC",() -> new BusinessQuarterly())
			.put("BA-DEC",() -> new BusinessYearly())
			.build();
	
	@SuppressWarnings("unchecked")
	public static <P extends Period<P>> Periodicity<P> get(String code) {
		if(!map.containsKey(code)) {
			throw new IllegalArgumentException("Periodicity code \"" + code + "\" not found.");
		}
		return (Periodicity<P>) map.get(code).get();
	}
	
	public static Set<String> allCodes() {
		return map.keySet();
	}

}
 public abstract class Periodicity<P extends Period<P>> {
	
	public abstract P get(long index);
	
	public abstract P from(LocalDate date);
	
	public abstract String code();

	public abstract String bloombergCode();
	
	public abstract double annualizationFactor();
	

	public P next(P arg0) {
		return offset(1,arg0);
	}

	public P previous(P arg0) {
		return offset(-1,arg0);
	}

	public long distance(P arg0, P period) {
		return period.longValue() - arg0.longValue();
	}

	public long distance(LocalDate arg0, LocalDate arg1) {
		return from(arg1).longValue() - from(arg0).longValue();
	}
	
	public P roundDown(LocalDate date) {
		P p = from(date);
		return p.endDate().isAfter(date) ? previous(p) : p;
	}

	public P roundUp(LocalDate date) {
		P p = from(date);
		return p.endDate().isBefore(date) ? next(p) : p;
	}
	
	public P offset(long count, P start) {
		return get(start.longValue() + count);
	}

	public LocalDate offset(long count, LocalDate start) {
		return get(from(start).longValue() + count).endDate();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public PeriodicRange<P> range(Period start, Period end) {
		return new PeriodicRange(this, start, end);
	}

	public PeriodicRange<P> range(LocalDate start, LocalDate end) {
		return range(from(start),from(end));
	}
	
	public PeriodicRange<P> range(long start, long end) {
		return range(get(start),get(end));
	}

	@Override
	public String toString() {
		return code();
	}

	@Override
	public boolean equals(Object obj) {
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final Periodicity<?> other = (Periodicity<?>) obj;
        return Objects.equals(this.code(), other.code());
	}

	@Override
	public int hashCode() {
		return code().hashCode();
	}

	public P previous(LocalDate endDate) {
		return from(endDate).previous();
	}

	public P next(LocalDate endDate) {
		return from(endDate).next();
	}
	

}
 public class Chart {
	
	public static String lineChart(Table table, String id, String title, String dateFormat,
			String numberFormat, int width, int height, List<Color> colors, Color backgroundColor,
			boolean dataLabel) {
		TableDataSet data = new TableDataSet(table);
		JFreeChart chart = ChartFactory.createTimeSeriesChart(
				title.equals("") ? null : "           " + title, // title
				null, // x-axis label
				null, // y-axis label
				data, // data
				true,
				true, // generate tooltips
				false // generate URLs
		);
		if(!title.equals("")) {
			chart.getTitle().setFont(new Font("SansSerif", Font.ITALIC + Font.BOLD, 14));
			chart.getTitle().setVerticalAlignment(VerticalAlignment.BOTTOM);
			chart.getTitle().setHorizontalAlignment(HorizontalAlignment.LEFT);
			chart.getTitle().setPaint(new Color(216-50, 221-50, 225-50));
			chart.getTitle().setMargin(0, 65, 0, 0);
		}


		XYPlot plot = (XYPlot) chart.getPlot();
		plot.setBackgroundPaint(backgroundColor);
		plot.setDomainGridlinePaint(new Color(216-25, 221-25, 225-25));
		plot.setRangeGridlinePaint(new Color(216-25, 221-25, 225-25));
		plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
		plot.setDomainCrosshairVisible(true);
		plot.setRangeCrosshairVisible(true);
		
		if(dataLabel) {
			plot.getDomainAxis().setUpperMargin(plot.getDomainAxis().getUpperMargin() + 0.20);
		}

		XYLineAndShapeRenderer r = (XYLineAndShapeRenderer) plot.getRenderer();
		for (int i = 0; i < table.getColumnCount() && i < colors.size(); i++) {
			r.setSeriesPaint(i, colors.get(i));
			r.setSeriesItemLabelPaint(i, colors.get(i));
			r.setSeriesStroke(i, new BasicStroke(1.5f));
		}
		DecimalFormat format = new DecimalFormat(numberFormat);
		NumberAxis na = (NumberAxis) plot.getRangeAxis();
		na.setNumberFormatOverride(format);

		DateAxis axis = (DateAxis) plot.getDomainAxis();
		axis.setDateFormatOverride(new SimpleDateFormat(dateFormat.equals("") ? "yyyy-MM-dd" : dateFormat));

        XYItemLabelGenerator generator = new StandardXYItemLabelGenerator() {
			private static final long serialVersionUID = 1L;

			@Override
			public String generateLabel(XYDataset dataset, int series, int item) {
				return item != dataset.getItemCount(0) - 1 ? "" : data.headers.get(dataset.getSeriesCount() - series - 1) +
						" " + format.format(dataset.getYValue(series, item));
			}
        	
        };
		if(dataLabel) {
			r.setDefaultItemLabelGenerator(generator);
			r.setDefaultItemLabelsVisible(true);
			r.setDefaultNegativeItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.CENTER_LEFT));
			r.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.CENTER_LEFT));
			r.setDefaultItemLabelFont(new Font("SansSerif", Font.PLAIN, 11));
		}
        return toSVG(chart,width,height,id);
	}

	public static String histogramChart(Table table, String id, String title, String dateFormat,
			String numberFormat, int width, int height, List<Color> colors, Color backgroundColor) {
		var set = new HistogramDataset();
		set.setType(HistogramType.SCALE_AREA_TO_1);
		var data = table.toColumnMajorArray();
		var headers = table.getHeaders(false, false);
		for(int i = 0; i < table.getColumnCount(); i++) {
			set.addSeries(headers.get(i), data[i], 25);
		}
		JFreeChart chart = ChartFactory.createHistogram(title == "" ? null : title, null, null, set,
				PlotOrientation.VERTICAL, true, false, false);
        XYPlot plot = (XYPlot)chart.getPlot();   
        plot.setForegroundAlpha(0.65F);   
		plot.setBackgroundPaint(backgroundColor);
		//var gridlineDarkness = 25;
		//var gridlineColor = new Color(backgroundColor.getRed(),backgroundColor.getBlue(),backgroundColor.getGreen());
		plot.setDomainGridlinePaint(backgroundColor.darker());
		plot.setRangeGridlinePaint(backgroundColor.darker());
		plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
		
        XYBarRenderer xybarrenderer = (XYBarRenderer)plot.getRenderer();   
        xybarrenderer.setDrawBarOutline(false);  
		xybarrenderer.setBarPainter(new StandardXYBarPainter());
		xybarrenderer.setShadowVisible(false);
		NumberAxis na = (NumberAxis) plot.getDomainAxis();
		na.setNumberFormatOverride(new DecimalFormat(numberFormat));
		na.setAxisLineVisible(false);
        return toSVG(chart,width,height,id);
	}

	public static String barChart(Table table, String id, String title, String dateFormat,
			String numberFormat, int width, int height, List<Color> colors, Color backgroundColor) {

		JFreeChart chart = ChartFactory.createXYBarChart(
				title.equals("") ? null : "           " + title, // title
				null, // x-axis label
				true,
				null, // y-axis label
				new TableDataSet(table), // data
				PlotOrientation.VERTICAL,
				true,
				true, // generate tooltips?
				false // generate URLs?
		);
		if(!title.equals("")) {
			chart.getTitle().setFont(new Font("SansSerif", Font.BOLD, 12));
			chart.getTitle().setHorizontalAlignment(HorizontalAlignment.LEFT);
		}
		XYPlot plot = (XYPlot) chart.getPlot();
		plot.setBackgroundPaint(backgroundColor);
		plot.setDomainGridlinePaint(new Color(216, 221, 225));
		plot.setRangeGridlinePaint(new Color(216, 221, 225));
		plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
		plot.setDomainCrosshairVisible(true);
		plot.setRangeCrosshairVisible(true);
		
		
		//chart.getLegend().setBackgroundPaint(new Color(216, 221, 225));
		//chart.getLegend().setBorder(new BlockBorder(new Color(174, 180, 187)));

		XYItemRenderer r = plot.getRenderer();
		for (int i = 0; i < table.getColumnCount() && i < colors.size(); i++)
			r.setSeriesPaint(i, colors.get(i));
		((XYBarRenderer) r).setBarPainter(new StandardXYBarPainter());
		((XYBarRenderer) r).setMargin(0.05);
		NumberAxis na = (NumberAxis) plot.getRangeAxis();
		na.setNumberFormatOverride(new DecimalFormat(numberFormat));
		//plot.setRangeAxis(0,na);
	

		DateAxis axis = (DateAxis) plot.getDomainAxis();
		axis.setDateFormatOverride(new SimpleDateFormat(dateFormat.equals("") ? "yyyy-MM-dd" : dateFormat));

        return toSVG(chart,width,height,id);
	}
	
	private static String toSVG(JFreeChart chart, int width, int height, String id) {
        SVGGraphics2D g2 = new SVGGraphics2D(width, height);
        g2.setFontMapper(new FontMapper() {

			@Override
			public String mapFont(String family) {
				return "'Myriad Pro', 'Roboto', sans-serif";
			}});
        g2.setDefsKeyPrefix(id);
        chart.setElementHinting(true);
        chart.draw(g2, new Rectangle(width, height));
        return g2.getSVGElement(chart.getID());
	}
	
	
	public static class TableDataSet implements XYDataset, IntervalXYDataset {
		
		private final List<LocalDate> dates;
		private final List<LocalDate> startDates;
		private final List<String> headers;
		private final double[][] columns;
		
		public TableDataSet(Table table) {
			this.dates = table.getRange().dates();
			this.startDates = table.getRange().expand(-1).dates();
			this.headers = table.getHeaders(false, false);
			this.columns = table.toColumnMajorArray();
		}

		@Override
		public DomainOrder getDomainOrder() {
			return DomainOrder.ASCENDING;
		}

		@Override
		public int getItemCount(int arg0) {
			return dates.size();
		}

		@Override
		public double getYValue(int arg0, int arg1) {
			double d = columns[columns.length - arg0 - 1][arg1];
			return Double.isInfinite(d) ? Double.NaN : d;
		}

		@Override
		public double getXValue(int arg0, int arg1) {
			return dates.get(arg1).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
		}

		@Override
		public int getSeriesCount() {
			return columns.length;
		}

		@Override
		public Comparable<String> getSeriesKey(int arg0) {
			return headers.get(columns.length - arg0 - 1);
		}

		@Override
		public int indexOf(@SuppressWarnings("rawtypes") Comparable arg0) {
			return columns.length - headers.indexOf(arg0) - 1;
		}

		@Override
		public Number getX(int arg0, int arg1) {
			return getXValue(arg0,arg1);
		}

		@Override
		public Number getY(int arg0, int arg1) {
			return getYValue(arg0,arg1);
		}

		@Override public void addChangeListener(DatasetChangeListener arg0) { }
		@Override public DatasetGroup getGroup() { return null; }
		@Override public void removeChangeListener(DatasetChangeListener arg0) { } 
		@Override public void setGroup(DatasetGroup arg0) { }


		@Override
		public double getStartXValue(int series, int item) {
			return startDates.get(item).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli() + 1;
		}


		@Override
		public double getEndXValue(int series, int item) {
			return getXValue(series, item);
		}

		@Override
		public double getStartYValue(int series, int item) {
			return getYValue(series, item);
		}


		@Override
		public double getEndYValue(int series, int item) {
			return getYValue(series, item);
		} 

		@Override public Number getStartX(int series, int item) { return getStartXValue(series,item); }
		@Override public Number getStartY(int series, int item) { return getStartYValue(series,item); }
		@Override public Number getEndX(int series, int item) { return getEndXValue(series,item); }
		@Override public Number getEndY(int series, int item) { return getEndYValue(series, item); }

		
	}

}
 public class ID {
	
	private static final AtomicInteger current = new AtomicInteger();

	public static String getId() {
		return toBase26(current.getAndIncrement());
	}

    private static String toBase26(int i){
        String s = "";
        while(i > 25) {
            int r = i % 26;
            i = i / 26;
            s = (char)(r + 65) + s;//26 + 64 = 100
        }
        s = (char)(i + 65) + s;
        return s.toLowerCase();
    }

}
 public static class Stack extends LinkedList<Column<?>> {
	private static final long serialVersionUID = 1L;

	public Stack() {
		super();
	}

	public Stack(Collection<? extends Column<?>> c) {
		super(c);
	}
		
}

import static tech.pinto.Name.nameBuilder;
import static tech.pinto.Name.terminalNameBuilder;
import static tech.pinto.Window.*;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.DoubleBinaryOperator;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import tech.pinto.Column.RowsFunction;
import tech.pinto.Column.RowsFunctionGeneric;
import tech.pinto.Pinto.Expression;
import tech.pinto.Pinto.StackFunction;
import tech.pinto.Window.Expanding;
import tech.pinto.Window.Rolling;
import tech.pinto.time.Period;
import tech.pinto.time.PeriodicRange;
import tech.pinto.time.Periodicities;
import tech.pinto.time.Periodicity;
import tech.pinto.tools.Chart;
import tech.pinto.tools.ID;
import tech.pinto.Pinto.Stack;

package tech.pinto;




public class StandardVocabulary extends Vocabulary {
    
    protected final List<Name.Builder> names;
	
	public StandardVocabulary() {
		names = new ArrayList<>(Arrays.asList(
			terminalNameBuilder("def", StandardVocabulary::def)
				.description("Defines the expression as the preceding name literal."),
			terminalNameBuilder("ifndef", StandardVocabulary::ifndef)
				.description("Defines the expression as the preceding name literal, if the name is not already defined."),
			terminalNameBuilder("undef", StandardVocabulary::undef)
				.description("Defines the expression as the preceding name literal."),
			terminalNameBuilder("help", StandardVocabulary::help)
				.description("Prints help for the preceding name literal or all names if one has not been specified."),
			terminalNameBuilder("list", StandardVocabulary::list)
				.description("Shows description for all names."),
			terminalNameBuilder("eval", StandardVocabulary::eval)
				.description("Evaluates the expression over date range between two *date* columns over *periodicity*.")
				.indexer("[periodicity=B,date=today today,:]"),
			terminalNameBuilder("import", StandardVocabulary::imp)
				.description("Executes pinto expressions contained files specified by file names in string columns."),
			terminalNameBuilder("to_csv", StandardVocabulary::to_csv)
				.indexer("[filename,periodicity=B,date=today today,:]")
				.description("Evaluates the expression over the date range specified by *start, *end* and *freq* columns, exporting the resulting table to csv *filename*."),
			terminalNameBuilder("report", StandardVocabulary::report)
				.indexer("[HTML]")
				.description("Creates an HTML report from any columns labelled HTML."),
			terminalNameBuilder("to_file", StandardVocabulary::to_file)
				.indexer("[string, HTML]")
				.description("Creates an HTML report from any columns labelled HTML."),

	/* stack manipulation */
			nameBuilder("only", StandardVocabulary::only)
				.description("Clears stack except for indexed columns."),
			nameBuilder("clear", StandardVocabulary::clear)
				.description("Clears indexed columns from table."),
			nameBuilder("rev", StandardVocabulary::rev)
				.description("Reverses order of input columns."),
			nameBuilder("pull", StandardVocabulary::pull)
				.description("Brings input columns to the front."),
			nameBuilder("copy", StandardVocabulary::copy)
				.indexer("[c=2,:]")
				.description("Copies indexed columns *c* times."),
			nameBuilder("roll", StandardVocabulary::roll)
				.indexer("[c=1,:]")
				.description("Permutes columns in stack *c* times."),
			nameBuilder("columns", StandardVocabulary::columns)
				.description("Adds a constant double column with the number of input columns."),
			nameBuilder("index", StandardVocabulary::index)
				.indexer("[c]")
				.description( "Indexes by constant double ordinals in c (start inclusive, end exclusive).  Assumes 0 for start if c is one constant."),

	///* dates */
			nameBuilder("today", StandardVocabulary::today)
				.description("Creates a constant date column with today's date."),
			nameBuilder("offset", StandardVocabulary::offset)
				.indexer("[date=today,periodicity=B,c=-1]")
				.description("Offset a *c* periods of *periodicity* from *date*."),
			nameBuilder("day_count", StandardVocabulary::dayCount)
				.description("Creates a column with count of days in each period."),
			nameBuilder("annualization_factor", StandardVocabulary::annualizationFactor)
				.indexer("[periodicity=\"range\"]")
				.description("Creates a column with annualization factor for the specified or evaluated range's periodicity."),
				
				
			nameBuilder("mkt", StandardVocabulary::mkt)
				.indexer("[tickers,fields]")
				.description("Adds columns for market data specified by *tickers* and *fields*."),
			nameBuilder("pi", StandardVocabulary::pi)
				.description("Creates a constant double column with the value pi."),
			nameBuilder("moon", StandardVocabulary::moon)
				.description("Creates a double column with values corresponding the phase of the moon."),
			nameBuilder("range", StandardVocabulary::range)
				.indexer("[c=1 4]")
				.description("Creates double columns corresponding to integers between first (inclusive) and second (exclusive) in *c*."),
			nameBuilder("read_csv", StandardVocabulary::readCsv)
				.indexer("[source,includes_header=\"true\"]")
				.description( "Reads CSV formatted table from file or URL specified as *source*."),
	

	/* data cleanup */
			nameBuilder("fill", StandardVocabulary::fill)
				.indexer("[c=0,:]")
				.description("Fills missing values with *c*."),
			nameBuilder("ffill", StandardVocabulary::ffill)
				.indexer("[periodicity=BQ-DEC,lookback=\"true\",:]")
				.description("Fills missing values with last good value, looking back one period of *freq* if *lookback* is true."),
			nameBuilder("join", StandardVocabulary::join)
				.indexer("[date,:]")
				.description("Joins columns over time, switching between columns on *dates*.\""),
			nameBuilder("upsample", StandardVocabulary::resample)
				.indexer("[periodicity=BM,:]")
				.description("Evaluates prior columns for lower frequency *periodicity*, carrying values forward for higher frequency evaluation periodicity."),
			nameBuilder("downsample", StandardVocabulary::downsample)
				.indexer("[periodicity=B,:]")
				.description("Evaluates prior columns for higher frequency *periodicity*, creating window columns."),
				
				
				
	/* header manipulation */
			nameBuilder("hcopy", StandardVocabulary::hcopy)
				.description("Copies headers to a comma-delimited constant string column."),
			nameBuilder("hpaste", StandardVocabulary::hpaste)
				.indexer("[string,repeat=\"true\",:]")
				.description("Sets headers of other input columns to the values in comma-delimited constant string column."),
			nameBuilder("hformat", StandardVocabulary::hformat)
				.indexer("[string,:]")
				.description("Formats headers, setting new value to *format* and substituting and occurences of \"{}\" with previous header value."),
				
				
				
	/* string manipulation */
			nameBuilder("cat", StandardVocabulary::cat)
				.indexer("[sep=\"\",:]")
				.description("Concatenates values of constant string columns."),
				
				
	/* windows */
			nameBuilder("rolling", StandardVocabulary::rolling_window)
					.description("Creates a rolling window of size *c* for each input.")
					.indexer("[c=2,returnNa=\"true\",:]"),
			nameBuilder("crossing", StandardVocabulary::cross_window)
					.description("Creates a cross sectional window from input columns."),
			nameBuilder("expanding", StandardVocabulary::expanding_window)
					.description("Creates creates an expanding window starting on *start* or the start of the evaluated range.")
					.indexer("[date=\"range\",:]"),
			nameBuilder("rev_expanding", StandardVocabulary::rev_expanding_window)
					.description("Creates a reverse-expanding window containing values from the current period to the end of the range."),
			nameBuilder("resetting", StandardVocabulary::resetting_window)
					.description("Creates a window that resets on na values for each window input."),
					
	/* stats */			
			getStatisticName("sum", Statistic.Sum::new),
			getStatisticName("mean", Statistic.Mean::new),
			getStatisticName("zscore", Statistic.ZScore::new),
			getStatisticName("std", Statistic.StandardDeviation::new),
			getStatisticName("first", () -> Statistic.First),
			getStatisticName("last", () -> Statistic.Last),
			getStatisticName("change", () -> Statistic.Change),
			getStatisticName("pct_change", () -> Statistic.PercentChange),
			getStatisticName("min", Statistic.Min::new),
			getStatisticName("max", Statistic.Max::new),
			getStatisticName("median", Statistic.Median::new),
			getStatisticName("product", Statistic.Product::new),
			getPairStatisticName("covar", Statistic.PairCovariance::new),
			getPairStatisticName("correl", Statistic.PairCorrelation::new),
			nameBuilder("ewma", StandardVocabulary::ewma)
				.description("Exponentially weighted moving average calculated using *alpha* or defaulting to 2 / (N + 1)")
				.indexer("[alpha=\"none\",:]"),
			
	/* reporting */
			nameBuilder("grid", StandardVocabulary::grid)
				.indexer("[columns=3,HTML]")
				.description("Creates a grid layout in a report with all input columns labelled HTML as cells in the grid."),
			nameBuilder("table", StandardVocabulary::table)
				.indexer("[periodicity=B, date=-20 offset today,format=\"decimal\",row_header=\"date\",col_headers=\"true\",:]")
				.description("Creates a const string column with code for a table defined by input columns and with header HTML."),
			nameBuilder("chart", StandardVocabulary::chartSVG)
				.indexer("[title=\"\",date_format=\"\",number_format=\"#,##0.00\",width=750,height=350,background_color=\"#D0ECEC\",color=default_palette,data_labels=\"true\",periodicity=B,date=-20 offset today,:]")
				.description("Creates a const string column with code for an HTML line chart."),
			nameBuilder("bar", StandardVocabulary::barChartSVG)
				.indexer("[title=\"\",date_format=\"\",number_format=\"#,##0.00\",width=750,height=350,background_color=\"#D0ECEC\",color=default_palette,periodicity=B, date=-20 offset today,:]")
				.description("Creates a const string column with code for an HTML bar chart."),
			nameBuilder("histogram", StandardVocabulary::histogramChartSVG)
				.indexer("[title=\"\",date_format=\"\",number_format=\"#,##0.00\",width=750,height=350,background_color=\"#D0ECEC\",color=default_palette,periodicity=B,date=-20 offset today,:]")
				.description("Creates a const string column with code for an HTML histogram chart."),
			nameBuilder("rt", StandardVocabulary::rt)
				.indexer("[functions=\" BA-DEC offset expanding pct_change {YTD} today today eval\",format=\"percent\",digits=2,:]")
				.description("Creates a const string column with code for an HTML ranking table, applying each *function* to input columns and ranking the results."),
			nameBuilder("default_palette", StandardVocabulary::palette)
				.description("Creates const string columns with hex codes for default colors for charts."),
			nameBuilder("blue_palette", StandardVocabulary::bluePalette)
				.description("Creates const string columns with hex codes for blue colors for charts.")
		));

		for(Entry<String, Supplier<Periodicity<?>>> e : Periodicities.map.entrySet()) {
			names.add(nameBuilder(e.getKey(), periodicityConstantFunction.apply(e.getValue().get()))
						.description( "Creates a constant periodcities column for " + e.getKey()));
		}

    	names.add(getBinaryOperatorName("+", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x + y));
    	names.add(getBinaryOperatorName("-", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x - y));
    	names.add(getBinaryOperatorName("*", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x * y));
    	names.add(getBinaryOperatorName("/", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x / y));
    	names.add(getBinaryOperatorName("%", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x % y));
    	names.add(getBinaryOperatorName("^", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : Math.pow(x, y)));
    	names.add(getBinaryOperatorName("==", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x == y ? 1.0 : 0.0));
    	names.add(getBinaryOperatorName("!=", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN :  x != y ? 1.0 : 0.0));
    	names.add(getBinaryOperatorName(">", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x > y ? 1.0 : 0.0));
    	names.add(getBinaryOperatorName("<", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x < y ? 1.0 : 0.0));
    	names.add(getBinaryOperatorName(">=", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x >= y ? 1.0 : 0.0));
    	names.add(getBinaryOperatorName("<=", (x,y) -> Double.isNaN(x) || Double.isNaN(y) ? Double.NaN : x <= y ? 1.0 : 0.0));

    	names.add(getUnaryOperatorName("isna", x -> x != x ? 1 : 0));
    	names.add(getUnaryOperatorName("not", x -> x == 0 ? 1 : 0));
    	names.add(getUnaryOperatorName("abs", Math::abs));
    	names.add(getUnaryOperatorName("sin", Math::sin));
    	names.add(getUnaryOperatorName("cos", Math::cos));
    	names.add(getUnaryOperatorName("tan", Math::tan));
    	names.add(getUnaryOperatorName("sqrt", Math::sqrt));
    	names.add(getUnaryOperatorName("log", Math::log));
    	names.add(getUnaryOperatorName("log10", Math::log10));
    	names.add(getUnaryOperatorName("exp", Math::exp));
    	names.add(getUnaryOperatorName("signum", Math::signum));
    	names.add(getUnaryOperatorName("asin", Math::asin));
    	names.add(getUnaryOperatorName("acos", Math::acos));
    	names.add(getUnaryOperatorName("atan", Math::atan));
    	names.add(getUnaryOperatorName("toRadians", Math::toRadians));
    	names.add(getUnaryOperatorName("toDegrees", Math::toDegrees));
    	names.add(getUnaryOperatorName("cbrt", Math::cbrt));
    	names.add(getUnaryOperatorName("ceil", Math::ceil));
    	names.add(getUnaryOperatorName("floor", Math::floor));
    	names.add(getUnaryOperatorName("rint", Math::rint));
    	names.add(getUnaryOperatorName("ulp", Math::ulp));
    	names.add(getUnaryOperatorName("sinh", Math::sinh));
    	names.add(getUnaryOperatorName("cosh", Math::cosh));
    	names.add(getUnaryOperatorName("tanh", Math::tanh));
    	names.add(getUnaryOperatorName("expm1", Math::expm1));
    	names.add(getUnaryOperatorName("log1p", Math::log1p));
    	names.add(getUnaryOperatorName("nextUp", Math::nextUp));
    	names.add(getUnaryOperatorName("nextDown", Math::nextDown));
    	names.add(getUnaryOperatorName("neg", x -> x * -1.0d));
    	names.add(getUnaryOperatorName("inv", x -> 1.0 / x));
    	names.add(getUnaryOperatorName("zeroToNa", x -> x == 0 ? Double.NaN : x));
    	names.add(getUnaryOperatorName("naToZero", x -> x != x ? 0 : x));
		names.add(getUnaryOperatorName("xmPrice", quote -> {
			double TERM = 10, RATE = 6, price = 0;
			for (int i = 0; i < TERM * 2; i++) {
				price += RATE / 2 / Math.pow(1 + (100 - quote) / 2 / 100, i + 1);
			}
			return price + 100 / Math.pow(1 + (100 - quote) / 2 / 100, TERM * 2);
		}));
		names.add(getUnaryOperatorName("ymPrice", quote -> {
			double TERM = 3, RATE = 6, price = 0;
			for (int i = 0; i < TERM * 2; i++) {
				price += RATE / 2 / Math.pow(1 + (100 - quote) / 2 / 100, i + 1);
			}
			return price + 100 / Math.pow(1 + (100 - quote) / 2 / 100, TERM * 2);
		}));

	}

	private static void rolling_window(Pinto pinto, Stack s) {
		int size = (int) s.removeFirst().cast(Double.class).rows(null).doubleValue();
		boolean returnNa = Boolean.parseBoolean(s.removeFirst().cast(String.class).rows(null));
		s.replaceAll(c -> {
			return new Column<Rolling>(Rolling.class, inputs -> inputs[0].getHeader(),
					inputs -> inputs[0].getTrace() + " rolling", getRollingWindowFunction(size,returnNa), c);
		});
	}

	private static void resetting_window(Pinto pinto, Stack stack) {
		stack.replaceAll(c -> {
			return new Column<ResetOnNa>(ResetOnNa.class, inputs -> inputs[0].getHeader(),
					inputs -> "( " + inputs[0].getTrace() + " resetting)",
					(range, inputs) -> new ResetOnNa(inputs[0].cast(Window.class).rows(range)), c);
		});
	}
	

	private static RowsFunctionGeneric<Rolling> getRollingWindowFunction(int size, boolean returnNa) {
