How the fund executes tradesTHE ENGINE, IN PLAIN ENGLISH · from strike.py

← Dashboard

Two programs do everything. strike.py is the trader — it runs on your command (the weekly "re-strike"), scores the whole universe, and writes a frozen book. refresh.py is the bookkeeper — every 30 min it only marks that frozen book to live prices (P&L, NAV, stops). refresh.py never trades. A trade happens only when you run a strike.

It is a paper system. It decides and records trades against a virtual $100k. It is not connected to any broker — no real order is ever placed. You act on the book in your own account. Long/cash only — it can never short.

1 When it runs

A strike is a deliberate weekly event — you run python strike.py. Between strikes the book is frozen; the 30-min auto-refresh only updates prices and P&L. So the fund does not day-trade or react intraday — it re-decides on a weekly cadence, exactly like the master-prompt pulse.

2 Pull the data (point-in-time, no look-ahead)

It downloads 1 year of daily closes from yfinance (free) for the entire universe — 30 US ETFs, 45 Bursa names, plus S&P, KLCI and USD/MYR. Then it enforces the fairness rule you insisted on:

BAR_CUTOFF = { US: 2026-07-09, MY: 2026-07-10 } # any bar AFTER the cutoff is the future relative to the strike → dropped.

So a US session that finishes overnight KL-time can't leak into a decision dated the evening before. Every price used is a completed bar.

3 Set the regime → the base unit

First it computes the regime from the S&P vs its 200-day and breadth:

RegimeConditionBase unit
GREENS&P > 200D & breadth ≥55% & RSP/SPY rising8% of NAV
YELLOW> 200D but breadth diverging4% of NAV
REDS&P < 200D0% — no new buys

The base unit is the size of one "full" conviction slot. Today it's GREEN → 8%.

4 Score every name

Each of the ~75 names gets a scorecard row. The trend gate is mandatory and comes first — nothing below trend can be bought, no matter how good it looks:

GATE: last close > 200-day average ? → if NO, score = 0, no position, full stop. within +1% of the 200D → "marginal", size ×0.75.

If it clears the gate, it earns 1 point per pillar:

🟢 MomentumEMA20 > EMA50 (short trend up)
🟢 Relative strength3-month return beats its benchmark (SPY / KLCI)
🟢 Flowmoney flowing IN (weekly research read)
🟢 ValueCHEAP or NORMAL — not RICH (weekly read)
🟢 Catalysta named, dated catalyst (weekly read)
🟢 Small-cap (MY only)market cap < RM3B — your upside bonus

Momentum, RS and the trend gate are computed from price. Flow, Value and Catalyst are the weekly human layer (a research read stored in the engine). The score becomes a size multiplier:

ScoreMultiplierMeaning
5–6, or TRIPLE×2.0TRIPLE = gate + momentum + flow IN + CHEAP
4×1.5strong
3×1.0one full unit
2 (only if Mom + RS)×0.5starter
≤1 or gate fail×0no position

5 Turn score into a target size

target % = base unit (8% GREEN) × multiplier × gate haircut then capped at 10% of NAV per name.

Example: a score-4 name in GREEN → 8% × 1.5 = 12% → capped to 10%. A score-3 → 8% × 1.0 = 8%.

6 Fill the book (the rules that shape it)

Qualifiers are ranked by score, then by relative strength, and added top-down until a limit bites:

Anything that qualifies but doesn't fit (cap or 12-slot limit) goes to the watch list, not the trash.

7 Generate the actual trades + fees

It diffs the new book against the frozen one and emits real orders in this order:

Every order is charged real costs, per side:

US: max($0.35, $0.0035 × shares) MY: value × (0.238% brokerage + 0.20% FX)

A new position's stop is set to max(200-day, entry × 0.92) and flagged provisional; a kept position keeps its original frozen stop.

8 Freeze it, then just mark it

The result is written to book.json (the frozen truth) and book-data.js (what the dashboard reads). From then until the next strike, refresh.py only marks it — recomputing P&L, NAV and whether price has hit a stop (the exit-ladder rung). A kept position never has its entry re-written by later prices — that's the point-in-time discipline.

9 The watch list & re-arming

Two kinds of names sit on watch: (a) qualified-but-no-room (a slot/cap was full), and (b) strong scorers that only failed the 200-day gate — these carry a concrete trigger like close > 152.40 (200D). When a watched name crosses back above its 200-day, it re-arms for the next strike.

Worked example — the last strike (2026-07-10, GREEN):
Scored 75 names. TNB was SOLD (score 2, failed Momentum+RS — exactly your "why hold a sleepy mega-cap" instinct, enforced). ViTrox, MPI, Penta, XBI, QQQ were BOUGHT (score-3/4 tech-semis + biotech that cleared the gate). XLF & KRE kept as TRIPLEs. Result: 10 positions, ~79% deployed, ~20% cash, all fees booked. Every one traces to a score → a size → a fill rule. No number was typed by hand.
What it deliberately does NOT do:

Source of truth: strike.py (the trader) and refresh.py (the marker) in your app folder. This page describes the engine exactly as coded on 2026-07-11; if the rules change, re-read the engine — it, not this page, is authoritative. Paper/practice system — not investment advice, not a broker.