horizon-code 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/horizon.js +18 -1
- package/package.json +1 -1
- package/src/app.ts +85 -11
- package/src/components/code-panel.ts +141 -14
- package/src/platform/session-sync.ts +1 -1
- package/src/state/types.ts +1 -0
- package/src/strategy/code-stream.ts +3 -1
- package/src/strategy/dashboard.ts +189 -18
- package/src/strategy/prompts.ts +426 -6
- package/src/strategy/tools.ts +311 -54
- package/src/strategy/validator.ts +98 -0
- package/src/updater.ts +118 -0
package/src/strategy/prompts.ts
CHANGED
|
@@ -22,6 +22,52 @@ is_stale() returns True if no data in N seconds OR feed never connected.
|
|
|
22
22
|
- Submitting identical orders rapidly — dedup risk check rejects them
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
|
+
const RISK_CAPITAL_GUIDE = `
|
|
26
|
+
## Risk & Capital Requirements (MANDATORY)
|
|
27
|
+
|
|
28
|
+
Every strategy you generate MUST include:
|
|
29
|
+
|
|
30
|
+
### 1. Explicit Risk Configuration
|
|
31
|
+
NEVER use default values. Every hz.Risk() must specify ALL fields with values appropriate to the user's context:
|
|
32
|
+
\`\`\`python
|
|
33
|
+
risk=hz.Risk(
|
|
34
|
+
max_position=50, # max contracts per market
|
|
35
|
+
max_notional=500, # max $ total exposure across all markets
|
|
36
|
+
max_drawdown_pct=5, # kill switch activates at this % loss
|
|
37
|
+
max_order_size=10, # max contracts per single order
|
|
38
|
+
)
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
### 2. Capital Context in hz.run()
|
|
42
|
+
Always include initial_capital context in params so the user knows the assumed capital:
|
|
43
|
+
\`\`\`python
|
|
44
|
+
params={
|
|
45
|
+
"initial_capital": 1000, # assumed starting capital
|
|
46
|
+
"spread": 0.06,
|
|
47
|
+
"size": 10,
|
|
48
|
+
}
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
### 3. Money Explanation (after code)
|
|
52
|
+
After EVERY code fence, add a brief **Capital & Risk** section explaining in plain English:
|
|
53
|
+
- **Capital**: How much money the strategy assumes (e.g., "$1,000 paper mode")
|
|
54
|
+
- **Max exposure**: Maximum $ at risk at any time (max_notional)
|
|
55
|
+
- **Max loss**: Worst case before kill switch (max_drawdown_pct of max_notional)
|
|
56
|
+
- **Position sizing**: How big each trade is (max_order_size contracts, ~$X at current prices)
|
|
57
|
+
- **Risk level**: Conservative / Moderate / Aggressive — based on notional-to-capital ratio
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
> **Capital & Risk**: This strategy assumes $1,000 in paper mode. Max exposure is $500 across all markets. The kill switch triggers at 5% drawdown ($25 loss). Each order is at most 10 contracts (~$5-7 at typical prices). Risk level: **Conservative** (50% max exposure).
|
|
61
|
+
|
|
62
|
+
### 4. Risk Scaling Rules
|
|
63
|
+
- Conservative: max_notional ≤ 30% of capital, max_drawdown_pct ≤ 3%
|
|
64
|
+
- Moderate: max_notional ≤ 60% of capital, max_drawdown_pct ≤ 5%
|
|
65
|
+
- Aggressive: max_notional ≤ 90% of capital, max_drawdown_pct ≤ 10%
|
|
66
|
+
|
|
67
|
+
If the user doesn't specify risk tolerance, default to **Conservative**.
|
|
68
|
+
If the user doesn't specify capital, default to **$1,000 paper mode**.
|
|
69
|
+
`;
|
|
70
|
+
|
|
25
71
|
const SDK_PIPELINE_SOURCE = `
|
|
26
72
|
import horizon as hz
|
|
27
73
|
|
|
@@ -44,7 +90,12 @@ hz.run(
|
|
|
44
90
|
markets=["market-slug"],
|
|
45
91
|
feeds={"mid": hz.PolymarketBook("market-slug")},
|
|
46
92
|
pipeline=[fair_value, quoter],
|
|
47
|
-
risk=hz.Risk(
|
|
93
|
+
risk=hz.Risk(
|
|
94
|
+
max_position=100, # max 100 contracts per market
|
|
95
|
+
max_notional=1000, # max $1,000 total exposure
|
|
96
|
+
max_drawdown_pct=5, # kill switch at 5% loss ($50)
|
|
97
|
+
max_order_size=50, # max 50 contracts per order
|
|
98
|
+
),
|
|
48
99
|
mode="paper",
|
|
49
100
|
params={"spread": 0.06, "size": 5}
|
|
50
101
|
)
|
|
@@ -311,6 +362,82 @@ from horizon._horizon import calibration_curve, log_loss, edge_decay
|
|
|
311
362
|
# edge_decay(edge_series, timestamps) -> half_life_seconds
|
|
312
363
|
`;
|
|
313
364
|
|
|
365
|
+
const ADVANCED_SDK_REFERENCE = `
|
|
366
|
+
# ── Advanced SDK Capabilities ──
|
|
367
|
+
|
|
368
|
+
## Volatility Suite
|
|
369
|
+
Six Rust-native estimators for adaptive spread sizing:
|
|
370
|
+
hz.volatility(lookback=20, method="yang_zhang") # Pipeline function
|
|
371
|
+
# Methods: "close_to_close", "parkinson", "garman_klass", "yang_zhang", "ewma", "rolling"
|
|
372
|
+
# Injects VolatilitySnapshot into ctx with .best property (best estimator auto-selected)
|
|
373
|
+
# Use: widen spreads in high-vol regimes, tighten in low-vol
|
|
374
|
+
|
|
375
|
+
## Example — volatility-adaptive market maker:
|
|
376
|
+
def adaptive_quoter(ctx, fair):
|
|
377
|
+
if fair is None:
|
|
378
|
+
return []
|
|
379
|
+
vol = getattr(ctx, 'volatility', None)
|
|
380
|
+
base_spread = ctx.params.get("spread", 0.06)
|
|
381
|
+
if vol and vol.best > 0:
|
|
382
|
+
spread = base_spread * (1 + vol.best * 2) # widen on high vol
|
|
383
|
+
else:
|
|
384
|
+
spread = base_spread
|
|
385
|
+
return hz.quotes(fair, spread=spread, size=ctx.params.get("size", 5))
|
|
386
|
+
|
|
387
|
+
## Execution Algorithms
|
|
388
|
+
For large orders that need careful execution:
|
|
389
|
+
from horizon import TWAP, VWAP, Iceberg
|
|
390
|
+
|
|
391
|
+
TWAP(engine, side, size, duration_secs=300, num_slices=10) # Equal time slices
|
|
392
|
+
VWAP(engine, side, size, duration_secs=300, volume_profile=[0.1, 0.15, ...]) # Volume-weighted
|
|
393
|
+
Iceberg(engine, side, total_size, show_size=5) # Hidden size
|
|
394
|
+
|
|
395
|
+
# All have: .start(), .on_tick(), .is_complete, .total_filled
|
|
396
|
+
# Use TWAP for uniform execution, VWAP for volume-aware, Iceberg for hiding size
|
|
397
|
+
|
|
398
|
+
## Market Discovery
|
|
399
|
+
Find real markets programmatically instead of hardcoding slugs:
|
|
400
|
+
hz.discover_markets(exchange="polymarket", query="bitcoin", min_volume=10000, sort_by="volume")
|
|
401
|
+
hz.top_markets(exchange="polymarket", limit=20) # Highest-volume active markets
|
|
402
|
+
hz.discover_events(exchange="polymarket") # Multi-outcome events
|
|
403
|
+
|
|
404
|
+
## Portfolio Management
|
|
405
|
+
For multi-strategy coordination and risk budgeting:
|
|
406
|
+
port = hz.Portfolio(name="my_fund", capital=10000)
|
|
407
|
+
port.add_position(market_id, side, size, entry_price)
|
|
408
|
+
port.update_price(market_id, current_price)
|
|
409
|
+
# Optimization: port.optimize("kelly") | "equal_weight" | "risk_parity" | "min_variance"
|
|
410
|
+
# Risk: port.var_95(), port.cvar_95(), port.correlation_matrix()
|
|
411
|
+
# Rebalance: port.needs_rebalance(threshold=0.05), port.rebalance_orders()
|
|
412
|
+
|
|
413
|
+
## Kelly Criterion (built-in)
|
|
414
|
+
hz.edge(fair_prob=0.55, market_price=0.50) # Expected value
|
|
415
|
+
hz.kelly(prob=0.55, price=0.50) # Full Kelly fraction
|
|
416
|
+
hz.fractional_kelly(prob=0.55, price=0.50, fraction=0.25) # Conservative (recommended)
|
|
417
|
+
hz.kelly_size(prob=0.55, price=0.50, bankroll=1000, max_size=50) # Contract units
|
|
418
|
+
hz.kelly_sizer(fraction=0.25, bankroll=1000) # Pipeline factory
|
|
419
|
+
hz.kelly_sizer_with_liquidity(fraction=0.25, bankroll=1000) # Dampens for illiquid markets
|
|
420
|
+
|
|
421
|
+
## Sentinel — Continuous Risk Monitor
|
|
422
|
+
hz.sentinel(portfolio, config={
|
|
423
|
+
"drawdown_levels": [-5, -10, -20, -30], # alert, reduce, pause, exit
|
|
424
|
+
"regime_lookback": 50,
|
|
425
|
+
"var_confidence": 0.95,
|
|
426
|
+
})
|
|
427
|
+
# Auto-reduces position size in high-drawdown regimes
|
|
428
|
+
# Detects: drawdown escalation, regime change, correlation spikes
|
|
429
|
+
# Actions: alert, reduce_positions, pause_trading, emergency_exit
|
|
430
|
+
hz.suggest_hedges(portfolio, budget=500) # Hedge recommendations
|
|
431
|
+
|
|
432
|
+
## Promotion Gates (paper → live)
|
|
433
|
+
Before deploying live, strategies should pass:
|
|
434
|
+
- Sharpe ratio >= 1.0
|
|
435
|
+
- At least 30 trades
|
|
436
|
+
- Walk-forward p-value < 0.05
|
|
437
|
+
- Probability of Backtest Overfitting (PBO) < 50%
|
|
438
|
+
- Max drawdown < 10% of capital
|
|
439
|
+
`;
|
|
440
|
+
|
|
314
441
|
const EXAMPLE_MOMENTUM = `
|
|
315
442
|
import horizon as hz
|
|
316
443
|
from collections import deque
|
|
@@ -354,9 +481,14 @@ hz.run(
|
|
|
354
481
|
markets=["market-slug"],
|
|
355
482
|
feeds={"mid": hz.PolymarketBook("market-slug")},
|
|
356
483
|
pipeline=[track_prices, quoter],
|
|
357
|
-
risk=hz.Risk(
|
|
484
|
+
risk=hz.Risk(
|
|
485
|
+
max_position=100, # max 100 contracts per market
|
|
486
|
+
max_notional=1000, # max $1,000 total exposure
|
|
487
|
+
max_drawdown_pct=5, # kill switch at 5% loss ($50)
|
|
488
|
+
max_order_size=25, # max 25 contracts per order
|
|
489
|
+
),
|
|
358
490
|
mode="paper",
|
|
359
|
-
params={"lookback": 15, "threshold": 0.03, "spread": 0.04, "size": 25}
|
|
491
|
+
params={"initial_capital": 1000, "lookback": 15, "threshold": 0.03, "spread": 0.04, "size": 25}
|
|
360
492
|
)
|
|
361
493
|
`;
|
|
362
494
|
|
|
@@ -392,9 +524,14 @@ hz.run(
|
|
|
392
524
|
markets=["market-slug"],
|
|
393
525
|
feeds={"mid": hz.PolymarketBook("market-slug")},
|
|
394
526
|
pipeline=[compute_inventory_skew, generate_quotes],
|
|
395
|
-
risk=hz.Risk(
|
|
527
|
+
risk=hz.Risk(
|
|
528
|
+
max_position=100, # max 100 contracts per market
|
|
529
|
+
max_notional=1000, # max $1,000 total exposure
|
|
530
|
+
max_drawdown_pct=5, # kill switch at 5% loss ($50)
|
|
531
|
+
max_order_size=25, # max 25 contracts per order
|
|
532
|
+
),
|
|
396
533
|
mode="paper",
|
|
397
|
-
params={"spread": 0.06, "size": 10, "max_position": 100}
|
|
534
|
+
params={"initial_capital": 1000, "spread": 0.06, "size": 10, "max_position": 100}
|
|
398
535
|
)
|
|
399
536
|
`;
|
|
400
537
|
|
|
@@ -455,6 +592,279 @@ print(hz.dashboard(bundle))
|
|
|
455
592
|
print("---END_ASCII_DASHBOARD---")
|
|
456
593
|
`;
|
|
457
594
|
|
|
595
|
+
const STRATEGY_TEMPLATES = `
|
|
596
|
+
# ── Strategy Templates ──
|
|
597
|
+
# Reference these when the user asks for a specific strategy type.
|
|
598
|
+
# Adapt parameters to the user's market, capital, and risk tolerance.
|
|
599
|
+
|
|
600
|
+
## Template 1: Conservative Market Maker
|
|
601
|
+
# Style: Wide spreads, small size, low risk. Good for beginners.
|
|
602
|
+
# Capital: $500-2000 | Risk: Conservative | Expected Sharpe: 0.5-1.5
|
|
603
|
+
# Best for: High-volume markets with tight spreads
|
|
604
|
+
|
|
605
|
+
import horizon as hz
|
|
606
|
+
|
|
607
|
+
def fair_value(ctx):
|
|
608
|
+
feed = ctx.feeds.get("mid")
|
|
609
|
+
if not feed or feed.is_stale(30):
|
|
610
|
+
return None
|
|
611
|
+
return feed.price
|
|
612
|
+
|
|
613
|
+
def conservative_quoter(ctx, fair):
|
|
614
|
+
if fair is None:
|
|
615
|
+
return []
|
|
616
|
+
inv = ctx.inventory.net
|
|
617
|
+
max_pos = ctx.params.get("max_position", 50)
|
|
618
|
+
skew = inv / max_pos if max_pos > 0 else 0
|
|
619
|
+
spread = ctx.params.get("spread", 0.10) * (1 + abs(skew) * 0.5)
|
|
620
|
+
size = ctx.params.get("size", 5)
|
|
621
|
+
if abs(inv) > max_pos * 0.8:
|
|
622
|
+
size = max(1, size // 2)
|
|
623
|
+
return hz.quotes(fair, spread=spread, size=size)
|
|
624
|
+
|
|
625
|
+
hz.run(
|
|
626
|
+
name="ConservativeMarketMaker",
|
|
627
|
+
exchange=hz.Polymarket(),
|
|
628
|
+
markets=["your-market-slug"],
|
|
629
|
+
feeds={"mid": hz.PolymarketBook("your-market-slug")},
|
|
630
|
+
pipeline=[fair_value, conservative_quoter],
|
|
631
|
+
risk=hz.Risk(
|
|
632
|
+
max_position=50,
|
|
633
|
+
max_notional=500,
|
|
634
|
+
max_drawdown_pct=3,
|
|
635
|
+
max_order_size=5,
|
|
636
|
+
),
|
|
637
|
+
mode="paper",
|
|
638
|
+
params={"initial_capital": 1000, "spread": 0.10, "size": 5, "max_position": 50}
|
|
639
|
+
)
|
|
640
|
+
# Capital & Risk: $1,000 paper. Max $500 exposure. Kill switch at 3% ($15). Conservative.
|
|
641
|
+
|
|
642
|
+
## Template 2: Momentum Follower with Kelly Sizing
|
|
643
|
+
# Style: Directional, follows price trends. Uses Kelly for position sizing.
|
|
644
|
+
# Capital: $1000-5000 | Risk: Moderate | Expected Sharpe: 1.0-2.0
|
|
645
|
+
# Best for: Trending markets (elections, crypto events)
|
|
646
|
+
|
|
647
|
+
import horizon as hz
|
|
648
|
+
from collections import deque
|
|
649
|
+
|
|
650
|
+
_prices: dict[str, deque] = {}
|
|
651
|
+
|
|
652
|
+
def momentum_signal(ctx):
|
|
653
|
+
feed = ctx.feeds.get("mid")
|
|
654
|
+
if not feed or feed.is_stale(30):
|
|
655
|
+
return None
|
|
656
|
+
slug = ctx.market.slug
|
|
657
|
+
lookback = int(ctx.params.get("lookback", 20))
|
|
658
|
+
if slug not in _prices:
|
|
659
|
+
_prices[slug] = deque(maxlen=lookback)
|
|
660
|
+
_prices[slug].append(feed.price)
|
|
661
|
+
if len(_prices[slug]) < lookback:
|
|
662
|
+
return None
|
|
663
|
+
oldest = _prices[slug][0]
|
|
664
|
+
if oldest == 0:
|
|
665
|
+
return None
|
|
666
|
+
momentum = (feed.price - oldest) / oldest
|
|
667
|
+
return {"mid": feed.price, "momentum": momentum}
|
|
668
|
+
|
|
669
|
+
def kelly_quoter(ctx, signal):
|
|
670
|
+
if signal is None:
|
|
671
|
+
return []
|
|
672
|
+
momentum = signal["momentum"]
|
|
673
|
+
threshold = ctx.params.get("threshold", 0.02)
|
|
674
|
+
if abs(momentum) < threshold:
|
|
675
|
+
return []
|
|
676
|
+
fair = signal["mid"]
|
|
677
|
+
prob = 0.5 + min(0.2, abs(momentum) * 2)
|
|
678
|
+
fraction = ctx.params.get("kelly_fraction", 0.25)
|
|
679
|
+
kelly_f = max(0, (prob - (1 - prob)) * fraction)
|
|
680
|
+
size = max(1, int(kelly_f * ctx.params.get("bankroll", 1000) / fair))
|
|
681
|
+
size = min(size, ctx.params.get("max_size", 20))
|
|
682
|
+
spread = ctx.params.get("spread", 0.04)
|
|
683
|
+
return hz.quotes(fair, spread=spread, size=size)
|
|
684
|
+
|
|
685
|
+
hz.run(
|
|
686
|
+
name="MomentumFollower",
|
|
687
|
+
exchange=hz.Polymarket(),
|
|
688
|
+
markets=["your-market-slug"],
|
|
689
|
+
feeds={"mid": hz.PolymarketBook("your-market-slug")},
|
|
690
|
+
pipeline=[momentum_signal, kelly_quoter],
|
|
691
|
+
risk=hz.Risk(
|
|
692
|
+
max_position=100,
|
|
693
|
+
max_notional=1000,
|
|
694
|
+
max_drawdown_pct=5,
|
|
695
|
+
max_order_size=20,
|
|
696
|
+
),
|
|
697
|
+
mode="paper",
|
|
698
|
+
params={"initial_capital": 2000, "lookback": 20, "threshold": 0.02, "spread": 0.04, "kelly_fraction": 0.25, "bankroll": 2000, "max_size": 20}
|
|
699
|
+
)
|
|
700
|
+
# Capital & Risk: $2,000 paper. Max $1,000 exposure. Kill switch at 5% ($50). Moderate.
|
|
701
|
+
|
|
702
|
+
## Template 3: Mean Reversion
|
|
703
|
+
# Style: Fades extreme prices toward fair value using z-score.
|
|
704
|
+
# Capital: $500-2000 | Risk: Conservative-Moderate | Expected Sharpe: 0.8-1.5
|
|
705
|
+
# Best for: Range-bound markets, markets with known fair value
|
|
706
|
+
|
|
707
|
+
import horizon as hz
|
|
708
|
+
from collections import deque
|
|
709
|
+
import math
|
|
710
|
+
|
|
711
|
+
_history: dict[str, deque] = {}
|
|
712
|
+
|
|
713
|
+
def mean_reversion_signal(ctx):
|
|
714
|
+
feed = ctx.feeds.get("mid")
|
|
715
|
+
if not feed or feed.is_stale(30):
|
|
716
|
+
return None
|
|
717
|
+
slug = ctx.market.slug
|
|
718
|
+
window = int(ctx.params.get("window", 30))
|
|
719
|
+
if slug not in _history:
|
|
720
|
+
_history[slug] = deque(maxlen=window)
|
|
721
|
+
_history[slug].append(feed.price)
|
|
722
|
+
if len(_history[slug]) < window:
|
|
723
|
+
return None
|
|
724
|
+
prices = list(_history[slug])
|
|
725
|
+
mean = sum(prices) / len(prices)
|
|
726
|
+
std = math.sqrt(sum((p - mean) ** 2 for p in prices) / len(prices))
|
|
727
|
+
if std < 0.001:
|
|
728
|
+
return None
|
|
729
|
+
zscore = (feed.price - mean) / std
|
|
730
|
+
return {"mid": feed.price, "mean": mean, "zscore": zscore}
|
|
731
|
+
|
|
732
|
+
def reversion_quoter(ctx, signal):
|
|
733
|
+
if signal is None:
|
|
734
|
+
return []
|
|
735
|
+
z = signal["zscore"]
|
|
736
|
+
entry_z = ctx.params.get("entry_z", 1.5)
|
|
737
|
+
if abs(z) < entry_z:
|
|
738
|
+
return []
|
|
739
|
+
fair = signal["mean"]
|
|
740
|
+
spread = ctx.params.get("spread", 0.06)
|
|
741
|
+
size = ctx.params.get("size", 10)
|
|
742
|
+
return hz.quotes(fair, spread=spread, size=size)
|
|
743
|
+
|
|
744
|
+
hz.run(
|
|
745
|
+
name="MeanReversion",
|
|
746
|
+
exchange=hz.Polymarket(),
|
|
747
|
+
markets=["your-market-slug"],
|
|
748
|
+
feeds={"mid": hz.PolymarketBook("your-market-slug")},
|
|
749
|
+
pipeline=[mean_reversion_signal, reversion_quoter],
|
|
750
|
+
risk=hz.Risk(
|
|
751
|
+
max_position=80,
|
|
752
|
+
max_notional=800,
|
|
753
|
+
max_drawdown_pct=5,
|
|
754
|
+
max_order_size=10,
|
|
755
|
+
),
|
|
756
|
+
mode="paper",
|
|
757
|
+
params={"initial_capital": 1000, "window": 30, "entry_z": 1.5, "spread": 0.06, "size": 10}
|
|
758
|
+
)
|
|
759
|
+
# Capital & Risk: $1,000 paper. Max $800 exposure. Kill switch at 5% ($40). Moderate.
|
|
760
|
+
|
|
761
|
+
## Template 4: Cross-Market Arb (Polymarket vs Kalshi)
|
|
762
|
+
# Style: Captures price discrepancies between exchanges.
|
|
763
|
+
# Capital: $2000-10000 | Risk: Low (market-neutral) | Expected Sharpe: 1.5-3.0
|
|
764
|
+
# Best for: Markets listed on both Polymarket and Kalshi
|
|
765
|
+
|
|
766
|
+
import horizon as hz
|
|
767
|
+
|
|
768
|
+
def arb_detector(ctx):
|
|
769
|
+
poly = ctx.feeds.get("poly")
|
|
770
|
+
kalshi = ctx.feeds.get("kalshi")
|
|
771
|
+
if not poly or poly.is_stale(30) or not kalshi or kalshi.is_stale(30):
|
|
772
|
+
return None
|
|
773
|
+
edge = abs(poly.price - kalshi.price)
|
|
774
|
+
min_edge = ctx.params.get("min_edge", 0.03)
|
|
775
|
+
if edge < min_edge:
|
|
776
|
+
return None
|
|
777
|
+
if poly.price < kalshi.price:
|
|
778
|
+
return {"buy_exchange": "poly", "fair": poly.price, "edge": edge}
|
|
779
|
+
else:
|
|
780
|
+
return {"buy_exchange": "kalshi", "fair": kalshi.price, "edge": edge}
|
|
781
|
+
|
|
782
|
+
def arb_quoter(ctx, signal):
|
|
783
|
+
if signal is None:
|
|
784
|
+
return []
|
|
785
|
+
size = ctx.params.get("size", 10)
|
|
786
|
+
spread = ctx.params.get("spread", 0.02)
|
|
787
|
+
return hz.quotes(signal["fair"], spread=spread, size=size)
|
|
788
|
+
|
|
789
|
+
hz.run(
|
|
790
|
+
name="CrossMarketArb",
|
|
791
|
+
exchanges=[hz.Polymarket(), hz.Kalshi()],
|
|
792
|
+
markets=["your-market-slug"],
|
|
793
|
+
feeds={
|
|
794
|
+
"poly": hz.PolymarketBook("your-market-slug"),
|
|
795
|
+
"kalshi": hz.KalshiBook("your-kalshi-ticker"),
|
|
796
|
+
},
|
|
797
|
+
pipeline=[arb_detector, arb_quoter],
|
|
798
|
+
risk=hz.Risk(
|
|
799
|
+
max_position=100,
|
|
800
|
+
max_notional=2000,
|
|
801
|
+
max_drawdown_pct=3,
|
|
802
|
+
max_order_size=10,
|
|
803
|
+
),
|
|
804
|
+
mode="paper",
|
|
805
|
+
params={"initial_capital": 5000, "min_edge": 0.03, "spread": 0.02, "size": 10}
|
|
806
|
+
)
|
|
807
|
+
# Capital & Risk: $5,000 paper. Max $2,000 exposure. Kill switch at 3% ($60). Low risk (market-neutral).
|
|
808
|
+
|
|
809
|
+
## Template 5: Multi-Signal Ensemble
|
|
810
|
+
# Style: Combines price, spread, momentum, and flow signals.
|
|
811
|
+
# Capital: $1000-5000 | Risk: Moderate | Expected Sharpe: 1.0-2.5
|
|
812
|
+
# Best for: Liquid markets with diverse data
|
|
813
|
+
|
|
814
|
+
import horizon as hz
|
|
815
|
+
from collections import deque
|
|
816
|
+
|
|
817
|
+
_momentum_history: dict[str, deque] = {}
|
|
818
|
+
|
|
819
|
+
def multi_signal(ctx):
|
|
820
|
+
feed = ctx.feeds.get("mid")
|
|
821
|
+
if not feed or feed.is_stale(30):
|
|
822
|
+
return None
|
|
823
|
+
slug = ctx.market.slug
|
|
824
|
+
lookback = int(ctx.params.get("lookback", 15))
|
|
825
|
+
if slug not in _momentum_history:
|
|
826
|
+
_momentum_history[slug] = deque(maxlen=lookback)
|
|
827
|
+
_momentum_history[slug].append(feed.price)
|
|
828
|
+
price_signal = feed.price
|
|
829
|
+
spread_signal = (feed.ask - feed.bid) if feed.ask > 0 and feed.bid > 0 else 0.05
|
|
830
|
+
history = _momentum_history[slug]
|
|
831
|
+
momentum_signal = (feed.price - history[0]) / history[0] if len(history) >= lookback and history[0] > 0 else 0
|
|
832
|
+
inventory_signal = -ctx.inventory.net * 0.001
|
|
833
|
+
weights = ctx.params.get("weights", [0.4, 0.2, 0.2, 0.2])
|
|
834
|
+
combined = (
|
|
835
|
+
weights[0] * price_signal +
|
|
836
|
+
weights[1] * (1 - spread_signal * 10) * price_signal +
|
|
837
|
+
weights[2] * momentum_signal * price_signal +
|
|
838
|
+
weights[3] * inventory_signal
|
|
839
|
+
)
|
|
840
|
+
fair = max(0.01, min(0.99, combined))
|
|
841
|
+
return fair
|
|
842
|
+
|
|
843
|
+
def ensemble_quoter(ctx, fair):
|
|
844
|
+
if fair is None:
|
|
845
|
+
return []
|
|
846
|
+
spread = ctx.params.get("spread", 0.05)
|
|
847
|
+
size = ctx.params.get("size", 8)
|
|
848
|
+
return hz.quotes(fair, spread=spread, size=size)
|
|
849
|
+
|
|
850
|
+
hz.run(
|
|
851
|
+
name="MultiSignalEnsemble",
|
|
852
|
+
exchange=hz.Polymarket(),
|
|
853
|
+
markets=["your-market-slug"],
|
|
854
|
+
feeds={"mid": hz.PolymarketBook("your-market-slug")},
|
|
855
|
+
pipeline=[multi_signal, ensemble_quoter],
|
|
856
|
+
risk=hz.Risk(
|
|
857
|
+
max_position=80,
|
|
858
|
+
max_notional=1000,
|
|
859
|
+
max_drawdown_pct=5,
|
|
860
|
+
max_order_size=15,
|
|
861
|
+
),
|
|
862
|
+
mode="paper",
|
|
863
|
+
params={"initial_capital": 2000, "lookback": 15, "spread": 0.05, "size": 8, "weights": [0.4, 0.2, 0.2, 0.2]}
|
|
864
|
+
)
|
|
865
|
+
# Capital & Risk: $2,000 paper. Max $1,000 exposure. Kill switch at 5% ($50). Moderate.
|
|
866
|
+
`;
|
|
867
|
+
|
|
458
868
|
/**
|
|
459
869
|
* Build the system prompt for strategy mode.
|
|
460
870
|
* Code streams via ```python fences — no tool calls for code generation.
|
|
@@ -577,6 +987,8 @@ The code you generate is the EXACT code that runs on Horizon. It must:
|
|
|
577
987
|
7. \`hz.quotes(fair, spread, size)\` returns \`list[Quote]\`
|
|
578
988
|
8. \`ctx.inventory\` is InventorySnapshot (NOT dict). Use \`.positions\`, \`.net\`, \`.net_for_market(id)\`
|
|
579
989
|
|
|
990
|
+
${RISK_CAPITAL_GUIDE}
|
|
991
|
+
|
|
580
992
|
## SDK Reference
|
|
581
993
|
|
|
582
994
|
### Pipeline Structure
|
|
@@ -588,13 +1000,21 @@ ${STATIC_MODELS_SOURCE}
|
|
|
588
1000
|
### Backtesting & Analytics
|
|
589
1001
|
${BACKTEST_REFERENCE}
|
|
590
1002
|
|
|
1003
|
+
### Advanced Capabilities
|
|
1004
|
+
${ADVANCED_SDK_REFERENCE}
|
|
1005
|
+
|
|
591
1006
|
${STRATEGY_GUIDE}
|
|
592
1007
|
|
|
593
1008
|
### Example: MomentumScalper
|
|
594
1009
|
${EXAMPLE_MOMENTUM}
|
|
595
1010
|
|
|
596
1011
|
### Example: InventoryAwareMarketMaker
|
|
597
|
-
${EXAMPLE_INVENTORY_MM}
|
|
1012
|
+
${EXAMPLE_INVENTORY_MM}
|
|
1013
|
+
|
|
1014
|
+
### Strategy Templates
|
|
1015
|
+
When a user asks for a specific strategy type (market maker, momentum, arb, etc.), reference the corresponding template below. Adapt the parameters to their market, capital, and risk tolerance.
|
|
1016
|
+
|
|
1017
|
+
${STRATEGY_TEMPLATES}`;
|
|
598
1018
|
}
|
|
599
1019
|
|
|
600
1020
|
/**
|