Strategy grammar
Every TinyCorp Signal strategy is a JSON config. The Builder lets you assemble it visually, but the raw shape is documented here so power users can fork-and-edit. The engine runs your config against live market data every 5 minutes and logs simulated fills.
Reference
Overall shape
A strategy config is a JSON object with five top-level keys:
{
"platforms": ["kalshi", "poly", "manifold"],
"categories": ["weather", "sports", "crypto", ...],
"entry": { /* all filter fields below go here */ },
"exit": { "at": "expiry" },
"sizing": { "type": "flat", "amount_cents": 500 },
"side": "YES"
}
Every cycle (every 5 minutes), the engine fetches markets matching platforms + categories, then applies the entry filter rules in order. Matching markets get a simulated fill at the current ask.
platforms + categories
| Field | Type | Notes |
|---|---|---|
| platforms | list<str> | Subset of kalshi, poly, manifold. Defaults to ["kalshi"] if omitted. |
| categories | list<str> | Optional. Empty list = all categories. Kalshi has weather. Poly has crypto, sports, politics, economics, commodities, stocks, other, world-events, entertainment. |
Price filters
| Field | Type | Notes |
|---|---|---|
| entry.yes_ask_min | float 0-1 | Minimum YES ask (e.g., 0.85 = 85¢). |
| entry.yes_ask_max | float 0-1 | Maximum YES ask. Use both for a band. |
| entry.yes_bid_min | float 0-1 | Minimum YES bid. |
| entry.yes_bid_max | float 0-1 | Maximum YES bid. |
Liquidity filters
| Field | Type | Notes |
|---|---|---|
| entry.vol_24h_min | int | Minimum 24h volume in USD (or MANA on Manifold). |
| entry.open_interest_min | int | Minimum open interest contracts. |
volume_24h is NULL on most weather brackets. If you set vol_24h_min on a kalshi+weather strategy, you'll exclude most candidates. Leave it off for weather; use it for poly markets.Timing filters
| Field | Type | Notes |
|---|---|---|
| entry.hours_to_close_min | float | Min hours until market close. 0.5 = at least 30 min out. |
| entry.hours_to_close_max | float | Max hours until close. 8 = within 8 hours of close. |
| entry.title_contains | list<str> | Market title must contain each substring (case-insensitive). E.g., ["minimum"] for LOW brackets. |
Momentum signals
Price-delta filters that require a lookback window. Engine reads from market_price_snapshots (cached every 1-3 min per platform).
| Field | Type | Notes |
|---|---|---|
| entry.ya_change_window_min | int (minutes) | Required when using ya_change filters. Defines the lookback. E.g., 30 = look 30 min back. |
| entry.ya_change_min_cents | int | YES ask must have RISEN by at least this many cents in the window. Positive value. |
| entry.ya_change_max_cents | int | YES ask must have DROPPED by at least this many cents (use negative). E.g., -5 = dropped 5+ cents. |
// "Buy when ya dropped 5+¢ in the last 30 min" { "yes_ask_min": 0.40, "yes_ask_max": 0.85, "ya_change_max_cents": -5, "ya_change_window_min": 30 }
Volume-spike signals
| Field | Type | Notes |
|---|---|---|
| entry.vol_spike_ratio_min | float | Current 24h volume / lookback-ago volume. 2.0 = doubled. Requires ya_change_window_min. |
Calendar gates
Short-circuit filters that fire before any market query. Use for strategies that should only run during specific times.
| Field | Type | Notes |
|---|---|---|
| entry.hour_of_day_min | int 0-23 | UTC hour. Strategy skips entire cycle outside the range. |
| entry.hour_of_day_max | int 0-23 | UTC hour. |
| entry.day_of_week_in | list<int> | Mon=0..Sun=6. [0,1,2,3,4] = weekdays only. |
External anchors
Signals that reference external data sources we poll.
| Field | Type | Notes |
|---|---|---|
| entry.crypto_anchor | object |
{"symbol": "bitcoin", "usd_above": 70000} or "usd_below". Spot price from CoinGecko (free, polled every 5 min). Supported symbols: bitcoin, ethereum, solana, ripple, dogecoin. Fails closed if no fresh data in 30 min.
|
| entry.nws_anchor | object |
{"value_above": 50} or {"value_below": 60} — degrees F. Per-candidate filter: parses the Kalshi weather ticker (e.g. KXLOWTSFO-26MAY24-B49.5 → SFO low for May 24), fetches the latest NWS forecast for that station/date, applies the threshold. Supported stations: KSFO, KAUS, KATL, KBOS, KDCA, KDEN, KDAL, KHOU, KLAS, KLAX, KMDW, KMIA, KMSP, KNYC, KOKC, KPHL, KPHX, KSAT, KSEA. NWS forecasts updated hourly. Fails closed when no forecast available.
|
| entry.finance_anchor | object |
{"symbol": "^VIX", "above": 20} — also below, change_pct_above, change_pct_below (% move today). Gates on stocks / indices / commodities / rates / macro from our finance feed (Stooq quotes + FRED). Supported symbols: ^GSPC (S&P 500), ^IXIC (Nasdaq), ^DJI (Dow), ^VIX, GC=F (gold), CL=F (WTI oil), AAPL, TSLA, NVDA, MSFT, AMZN, GOOGL, META, US10Y, FEDFUNDS, UNRATE, CPIYOY, YC2S10S, GDPGROWTH. Fails closed on stale data.
|
| entry.energy_anchor | object |
{"metric": "crude", "draw_above": 1000} — also build_above. Gates on the latest weekly EIA report: crude oil stocks (kbbl) or natgas storage (Bcf), keyed on the week-over-week draw / build. Fails closed when no fresh report.
|
| entry.funding_anchor | object |
{"symbol": "BTC", "above": 0.05} — also below. Perp funding rate (% per 8h) from OKX. High = crowded longs; negative = crowded shorts. Supported symbols: BTC, ETH, SOL. Fails closed on stale data.
|
| entry.sports_anchor | object |
{"min_edge": 5}. Restricts to Kalshi MLB winner markets where our in-house win-probability model (Elo + starter-ERA, forward-tracked) beats the market price by ≥ min_edge percentage points. Uses precomputed model edges; fails closed when none.
|
| entry.launch_anchor | object |
{"provider": "SpaceX", "within_days": 7} — or {"rocket": "Falcon 9"} / {"any": true}; optional status. Gates on upcoming rocket launches. Fails closed when no matching launch in the window.
|
| entry.tropical_anchor | object |
{"min_classification": "HU"} (TD / TS / HU / MH) — also active, active_count_above, formation_7d_above, formation_48h_above (NHC formation odds %); optional basin (atlantic / east_pacific / central_pacific). Source: NHC CurrentStorms + Tropical Weather Outlook. Fails closed when no fresh advisory.
|
entry block. E.g., a weather-fade strategy can require both nws_anchor.value_above AND a price band — both must pass.Exit + sizing
| Field | Type | Notes |
|---|---|---|
| exit.at | str | "expiry" is the only supported value in v1. Strategies hold to market close; engine settles based on official result (Kalshi) or last_price (Poly/Manifold). |
| sizing.type | str | "flat" only in v1. |
| sizing.amount_cents | int | Dollars-per-trade in cents. 500 = $5/trade. House strategies use $5. |
| sizing.max_fills_per_day | int | Optional cap on total fills per UTC day. Useful for broad-match strategies (cheap-price lottery, vol-spike on Poly's "other" category) that would otherwise fire on dozens of markets per cycle. Once the cap is reached, the engine stops opening new positions for the strategy until tomorrow. |
| sizing.max_concurrent | int | Optional cap on simultaneously-open positions for this strategy; the engine skips new entries while the cap is full. A surprisingly powerful risk control — losing trades tend to cluster, so capping concurrency (e.g. 5) can turn a losing config profitable. |
| side | str | "YES" or "NO". Most strategies are YES. |
Backtesting
The backtester replays a strategy config against captured market-price history (market_price_snapshots, recorded ~every 5 minutes) and settles each simulated trade with the same exit math the live engine uses. It answers: "how would these rules have done over the data we've recorded so far?"
The snapshot window grows continuously as history accrues. Delta signals (ya_change_*, vol_spike_*) are replayed with a sliding per-ticker buffer that mirrors the live engine's lookback, so a backtest verdict matches what the cron-paced runner would actually have done. max_concurrent is applied as a post-pass over the time-sorted fills.
Signals the backtester faithfully replays
| Replayed from captured history |
|---|
Price (yes_ask_min/max), liquidity (vol_24h_min, open_interest_min), timing (hour_of_day_min/max, day_of_week_in, title_contains) |
Momentum (ya_change_window_min, ya_change_min_cents, ya_change_max_cents) and volume-spike (vol_spike_ratio_min, vol_spike_window_min) — snapshot-delta replay |
wx_anchor (against wx_cf6_projections history) and eco_anchor (against the dated macro calendar) |
Signals not yet replayable
These work in the live engine but aren't backtested yet — they need historical series we don't capture retroactively. When a config uses one, the backtester flags it as unsupported rather than silently skipping it (so a backtest never quietly ignores part of your rules):
| Field | Why not yet |
|---|---|
| crypto_anchor | Needs a historical crypto price series (forward capture in progress). |
| nws_anchor | Needs a historical NWS observation series. |
| injury_anchor | Only current injury state is stored; needs forward injury-history snapshots. |
| lineup_anchor | Only today's probable starters are stored; needs forward lineup-history snapshots. |
| post_peak_only, dewpoint_gate | Need derived weather series not yet captured per-snapshot. |
| finance_anchor, energy_anchor, funding_anchor | Live in the engine; need a forward-captured quote / report / funding-rate history (accruing now). |
| sports_anchor | Depends on the precomputed MLB model-edge table; backtest replay needs point-in-time model edges (forward-tracked from day 1). |
| launch_anchor, tropical_anchor | Only current launch schedule / NHC advisories are stored; need forward snapshots of past state. |
How we measure performance
Every number on a strategy card, the leaderboard, and the detail page is computed the same way, in engine.perf_stats. All P&L is paper-traded at a flat $5 stake per trade — so "+$6" means +$6 on $5 bets, not on $50. Only closed trades (settled at expiry against the real market outcome) count toward P&L, win rate, EV, Sharpe, and drawdown; open positions are not marked-to-market. Headline figures are lifetime (every closed trade, across all versions of the strategy) unless a surface explicitly says "30d".
| Metric | Exactly how it's computed |
|---|---|
| Realized P&L | Sum of every closed trade's profit/loss, in dollars, at the flat $5 stake. |
| EV / trade | Realized P&L ÷ number of closed trades — the average dollars a single trade has actually returned. |
| Win rate | Closed trades that settled profitable (P&L > $0) ÷ all closed trades. |
| Sharpe (per-trade) | Mean ÷ standard deviation of the per-trade percent returns (each trade's P&L ÷ its stake). Unannualized: a value of 1.0 means the average trade lands one standard deviation above breakeven. Shown only with ≥ 5 closed trades (otherwise "—"); we deliberately skip √252 annualization, which inflates tiny samples into fake 80+ Sharpes. |
| Max drawdown | The largest peak-to-trough drop of the cumulative-P&L curve, walked trade by trade across the whole history — i.e. the deepest the running total ever fell below a prior high-water mark. A Max DD of −$30 means a losing run gave back $30 from a peak before recovering. It is not the worst single day, and not the worst single trade. Dollars, on flat $5 stakes. |
| Avg win / Avg loss | Average dollars of a winning / losing closed trade — the fade-asymmetry check (many small wins vs the occasional large loss, or vice-versa). |
| Sample n | Number of closed (settled) trades — the denominator for win rate, EV, and Sharpe. Small n = low confidence. |
| Days live | Calendar days since the strategy went live and started forward paper-trading. |
Full examples
Fade weather brackets in the late window:
{
"platforms": ["kalshi"],
"categories": ["weather"],
"entry": {
"yes_ask_min": 0.85,
"yes_ask_max": 0.95,
"hours_to_close_max": 18
},
"exit": { "at": "expiry" },
"sizing": { "type": "flat", "amount_cents": 500 },
"side": "YES"
}
Multi-signal: weekday US morning Poly mover with vol spike:
{
"platforms": ["poly"],
"categories": ["other", "politics", "economics"],
"entry": {
"yes_ask_min": 0.25,
"yes_ask_max": 0.75,
"vol_24h_min": 10000,
"vol_spike_ratio_min": 1.3,
"ya_change_window_min": 60,
"hour_of_day_min": 13,
"hour_of_day_max": 17,
"day_of_week_in": [0,1,2,3,4]
},
"exit": { "at": "expiry" },
"sizing": { "type": "flat", "amount_cents": 500 },
"side": "YES"
}
POST /api/quants/preview.API reference
| Endpoint | Method | Notes |
|---|---|---|
| /api/quants/strategies | GET | List public strategies. Params: category, status, sort (sharpe/pnl/win_rate/newest), limit. |
| /api/quants/strategies/<slug> | GET | Single strategy preview + config (when entitled). |
| /api/quants/strategies/<slug>/pnl | GET | Cumulative realized P&L points for the chart. |
| /api/quants/strategies/<slug>/trades | GET | Simulated trade log (5 for preview, 50 for unlocked). |
| /api/quants/strategies/<slug>/candidates | GET | Markets currently matching all entry rules. |
| /api/quants/strategies/<slug>/versions | GET | Edit history snapshots. |
| /api/quants/preview | POST | Evaluate an ad-hoc config (used by the Builder). Body: {"config": <config>}. |
| /api/quants/leaderboard | GET | Sortable list. Params: sort, limit. |
| /api/quants/activity | GET | Recent fills + publishes + unlocks platform-wide. |
| /api/quants/stats | GET | Topline platform numbers. |
House strategies using each signal
Live snapshot from /api/quants/strategies. Click any strategy name to see its full config + live performance.