1PAIR = AUDNZD
3function positions(bar):
4 z = zscore(feature(soi_3m), expanding, at least 120 months)
6 return { PAIR: clip(z / 2, -1, 1) }
8# With one instrument, scale_to_gross() reduces any non-zero weight to
9# the same size, so only the sign of z affects the result.
11# ── execution ────────────────────────────────────────────────────────
13COST_PER_TRADE = 15 bps of the notional that changes hands
15function on_bar(bar, book):
16 if not bar.is_month_end:
17 return # decisions are made monthly only
19 target = positions(bar) # the rule above
20 target = scale_to_gross(target, 1.0)
22 rebalance(book, target, bar)
24function scale_to_gross(w, limit):
25 gross = sum(abs(w)) # total capital at work
27 if gross == 0:
28 return w # flat is a valid target
30 return w * limit / gross # leverage fixed, never implicit
32function rebalance(book, target, bar):
33 for symbol in union(book.symbols, target.symbols):
34 delta = target[symbol] - book.weight(symbol)
36 if delta > 0:
37 buy(symbol, delta, fill = next_bar.close)
38 else if delta < 0:
39 sell(symbol, -delta, fill = next_bar.close)
41 book.charge(COST_PER_TRADE * abs(delta))
43# Orders are filled at the next month's close, never the one the decision
44# was made on. There is no stop_loss(), take_profit(), limit order or
45# position cap: a holding changes only when positions() returns a
46# different target at the next month end.