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() ➤ Combine and transform series: p.ma = p.mySeries.sma(20) p.ema = p.mySeries.ema(10) p.emaCustom = p.mySeries.ema(10, 0.3) p.diff = p.mySeries.subtract(p.ma) p.sum = p.mySeries.add(p.ma) p.prod = p.mySeries.mul(p.ma) p.prod = p.mySeries.mul(8) p.absdiff = p.diff.abs() ➤ Chain series: p.diff10 = p.mySeries.subtract(p.ma).mul(10) ➤ Render series in the chart: show(p.mySeries, p.ma) showAll()
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; } }.png)

