Phlebas: a live timeseries simulation controlled by the console

This one's a little different. For a weekend project, I wanted to build a utility to generate Brownian time series and graph composable operations on them in real time without having to invent a DSL. Then I remembered I've always wanted to make a website you can control directly from the Developer Tools console. Enter phlebas.dev. It's not fully optimized and definitely not finished, but I put it together in two afternoons and it already lets you experiment with a good range of operators. Just open the console and give it a try:

Define a new series in p:
p.mySeries = new BrownianGenerator(start, mu, sigma)
p.mySeries = new BrownianGenerator() // With default paramsCombine and transform series:
p.ma = p.mySeries.sma(20) // Simple Moving Average
p.ema = p.mySeries.ema(10) // Exponential Moving Average
p.emaCustom = p.mySeries.ema(10, 0.3) // EMA (specify alpha)
p.diff = p.mySeries.subtract(p.ma) // Difference
p.sum = p.mySeries.add(p.ma) // Sum
p.prod = p.mySeries.mul(p.ma) // Multiply
p.prod = p.mySeries.mul(8) // All arithmetic operations work with numbers
p.absdiff = p.diff.abs() // AbsoluteChain series:

p.diff10 = p.mySeries.subtract(p.ma).mul(10)

➤ Render series in the chart:
show(p.mySeries, p.ma) // Show selected series
showAll() // Show all series in p

Demo time series

Demo console

BONUS: you can create generators and operators and use them in console. Here's how sum is implemented:

class AddGenerator extends TimeseriesGenerator {
  sourceA: TimeseriesGenerator;
  sourceB: number | string | TimeseriesGenerator;

  constructor(
    sourceA: TimeseriesGenerator,
    sourceB: number | string | TimeseriesGenerator
  ) {
    super();
    this.sourceA = sourceA;
    this.sourceB = sourceB;
  }

  next(cache: Map<string, number>): number {
    if (cache.has(this.name)) return cache.get(this.name)!;

    const resolvedSourceB =
      typeof this.sourceB === "string"
        ? this.context![this.sourceB]
        : this.sourceB;

    if (resolvedSourceB === undefined) {
      throw new Error(
        `Could not find timeseries '${this.sourceB}' in context.`
      );
    }

    const valueA = this.sourceA.next(cache);
    const valueB =
      typeof resolvedSourceB === "number"
        ? resolvedSourceB
        : resolvedSourceB.next(cache);
    const result = valueA + valueB;
    cache.set(this.name, result);
    return result;
  }
}