1UNIVERSE = [ZW=F ZC=F ZS=F ZM=F ZR=F KE=F]
2TRIGGER = +1.0 standard deviations
4function positions(bar):
5 z = zscore(feature(soi_3m), expanding, at least 120 months)
7 if z <= TRIGGER:
8 return {} # only while La Nina is established
10 return equal_risk(UNIVERSE, bar)
12# ── execution ────────────────────────────────────────────────────────
14COST_PER_TRADE = 15 bps of the notional that changes hands
16function on_bar(bar, book):
17 if not bar.is_month_end:
18 return # decisions are made monthly only
20 target = positions(bar) # the rule above
21 target = scale_to_gross(target, 1.0)
23 rebalance(book, target, bar)
25function scale_to_gross(w, limit):
26 gross = sum(abs(w)) # total capital at work
28 if gross == 0:
29 return w # flat is a valid target
31 return w * limit / gross # leverage fixed, never implicit
33function rebalance(book, target, bar):
34 for symbol in union(book.symbols, target.symbols):
35 delta = target[symbol] - book.weight(symbol)
37 if delta > 0:
38 buy(symbol, delta, fill = next_bar.close)
39 else if delta < 0:
40 sell(symbol, -delta, fill = next_bar.close)
42 book.charge(COST_PER_TRADE * abs(delta))
44# Orders are filled at the next month's close, never the one the decision
45# was made on. There is no stop_loss(), take_profit(), limit order or
46# position cap: a holding changes only when positions() returns a
47# different target at the next month end.