ChipaFlow is the .chipa strategy language — a small, strict DSL for trading logic. Exact-decimal floats, indicators as first-class values, crossover operators built into the grammar, and one source that compiles to either a positions target (stock) or a fixed-payout target (binary).
Only main is required, and its signature is fixed:
func main(candle: Candle, ctx: Context) -> TradeAction { Hold }snake_case for values and functions; PascalCase for types, variants, and indicators. let Foo = 1; is a parse error.bool — if 1 {} is an error.int and float never mix; write x as float.if is an expression. Both branches must produce the same type. As a statement it needs a trailing ;.=, not : — P { a = 1.0 }. Colons only annotate types.lets and are only readable inside functions.0.1 + 0.2 == 0.3 holds. Literals need digits on both sides of the dot: 1.0, never 1.Buy / Sell / Hold — setting an amount compiles but warns (E0332).;; a block's last expression is its value; return is only for early exit.| Type | Literals | Notes |
|---|---|---|
int | 0, -14, 1_000 | 128-bit, overflow traps |
float | 1.5, 2.5e3, 50bp | exact decimal; 50bp = 0.005 |
bool | true, false | |
string | "close" | + concatenates |
Array<T> | [1.0, 2.0] | immutable, indexable, no methods |
range | 0..n, 0..=n | for-iterable |
Window<T> | Window(64) | a value — write back on push |
Tensor<f32> | via std.tensor.* | IEEE f32 — the exact-decimal carve-out |
Declared once at the top level, read inside functions. Crossovers are language operators: a >^ b is true on the candle where a crosses above b; a <^ b the reverse.
let fast = Sma(9);
let slow = Sma(21);
let rsi = Rsi(14);
let bb = Bb(20, 2.0); # Bands: .upper .middle .lower
let macd = Macd(12, 26, 9); # MacdOut: .macd .signal .histogram
func main(candle: Candle, ctx: Context) -> TradeAction {
if fast >^ slow && rsi < 70.0 { Buy }
else if fast <^ slow { Sell }
else { Hold }
}Built-ins include Sma, Ema, Wma, Smma, Rsi, Atr, Roc, Momentum, Highest/Lowest, WilliamsR, Obv, Lag, Bb, Kc, Stoch, Macd, Alligator, SuperTrend, and the stream sources Aggregate and WaveletDenoise.
target = "stock" | target = "binary" | |
|---|---|---|
| Market model | positions — main opens, @trade manages | fixed-payout bets that settle at expiry |
| Context extras | ctx.position() | ctx.payout() |
| Canonical actions | Buy / Sell | Call / Put |
| Per-trade handler | @trade → TradeCommand | — |
On the stock target a @trade handler runs once per open trade, per candle. Qualify every command — bare Hold is TradeAction.Hold and will not type-check in a handler:
@trade func manage(trade: TradeContext, c: Candle) -> TradeCommand {
if trade.stop_loss() == 0.0 {
# 0 means "unchanged", so this brackets the trade exactly once.
TradeCommand.Update {
stop_loss = trade.entry() * 0.97,
take_profit = trade.entry() * 1.05,
}
} else if trade.bars() > 10 && trade.profit() < 0.0 {
TradeCommand.Close
} else {
TradeCommand.Hold
}
}