horizon-code 0.6.0 → 0.6.2

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.
@@ -76,8 +76,7 @@ After EVERY code fence, add a brief **Capital & Risk** section explaining in pla
76
76
  - **Position sizing**: How big each trade is (max_order_size contracts, ~$X at current prices)
77
77
  - **Risk level**: Conservative / Moderate / Aggressive — based on notional-to-capital ratio
78
78
 
79
- Example:
80
- > **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).
79
+ Example: "Capital & Risk: $1,000 paper. Max $500 exposure. Kill switch at 5% ($25). 10 contracts/order. Conservative (50% exposure)."
81
80
 
82
81
  ### 4. Risk Scaling Rules
83
82
  - Conservative: max_notional ≤ 30% of capital, max_drawdown_pct ≤ 3%
@@ -220,22 +219,14 @@ class NWSFeed: def __init__(self, office: str = "", grid_x: int = 0, grid_y: int
220
219
  class RESTJsonPathFeed: def __init__(self, url: str, price_path: str | None = None, bid_path: str | None = None, ask_path: str | None = None, interval: float = 5.0): ...
221
220
  class ChainlinkFeed: def __init__(self, contract_address: str, rpc_url: str, decimals: int = 8, interval: float = 10.0): ...
222
221
  class MempoolFeed: def __init__(self, rpc_url: str): ... # Polygon mempool watcher
223
- class AlpacaFeed: def __init__(self, symbols: list[str], data_source: str = "iex"): ... # Stock real-time
224
- class CoinbaseFeed: def __init__(self, product_ids: list[str]): ... # Crypto WebSocket
225
- class RobinhoodFeed: def __init__(self, symbols: list[str], interval: float = 5.0): ...
226
- class IBKRFeed: def __init__(self, conids: list[int], paper: bool = True): ... # Interactive Brokers
227
- class CalendarFeed: def __init__(self, events_json: str | None = None, interval: float = 60.0): ... # Economic events
228
- class TreasuryFeed: def __init__(self, series_ids: list[str] | None = None, interval: float = 3600.0): ... # FRED yields
222
+ # Also available: AlpacaFeed, CoinbaseFeed, RobinhoodFeed, IBKRFeed, CalendarFeed, TreasuryFeed
229
223
 
230
224
  # ── Exchange Types (for hz.run exchange=... or exchanges=[...]) ──
231
225
 
232
226
  class Polymarket: def __init__(self, private_key: str | None = None): ...
233
227
  class Kalshi: def __init__(self, email: str | None = None, password: str | None = None, api_key: str | None = None): ...
234
228
  class Limitless: def __init__(self, api_key: str | None = None, private_key: str | None = None): ...
235
- class Alpaca: def __init__(self, api_key: str | None = None, api_secret: str | None = None, paper: bool = True): ...
236
- class Coinbase: def __init__(self, api_key: str | None = None, api_secret: str | None = None): ...
237
- class Robinhood: def __init__(self, username: str | None = None, password: str | None = None): ...
238
- class InteractiveBrokers: def __init__(self, host: str = "127.0.0.1", port: int = 7497, client_id: int = 0): ...
229
+ # Also available: Alpaca, Coinbase, Robinhood, InteractiveBrokers
239
230
 
240
231
  # ── Built-in Pipeline Factories ──
241
232
 
@@ -262,16 +253,6 @@ class InteractiveBrokers: def __init__(self, host: str = "127.0.0.1", port: int
262
253
  # hz.flow_signal(feed_name) -> Signal # Net buy/sell pressure
263
254
  # hz.imbalance_signal(feed_name) -> Signal # Orderbook imbalance
264
255
  # Signal(name="my_signal", fn=my_function, weight=1.0)
265
-
266
- # ── Advanced Modules (use lookup_sdk_docs for full API) ──
267
- # Arbitrage: parity_arb, event_arb, spread_convergence, stat_arb, mm_arb, latency_arb, composite_arb
268
- # Copy-Trading: copy_trades, copy_trader
269
- # Wallet Intelligence: score_wallet, analyze_wallet, scan_bots, reverse_copy
270
- # Wallet Profiler: profile_wallet, hunt, hunter
271
- # Stealth Execution (Ultra): estimate_impact, smart_route, stealth_execute — TWAP, iceberg, sniper
272
- # Sentinel (Ultra): sentinel_report, suggest_hedges, sentinel — continuous risk monitoring
273
- # Oracle (Ultra): forecast_market, scan_edges, oracle — AI-driven forecasting
274
- # Whale Galaxy (Ultra): scan_galaxy, detect_clusters, auto_target, galaxy_tracker
275
256
  `;
276
257
 
277
258
  const BACKTEST_REFERENCE = `
@@ -370,205 +351,98 @@ hz.dashboard(plot_bundle, width=72, height=16) # Full multi-section ASCI
370
351
  result = hz.backtest(...)
371
352
  bundle = hz.from_backtest(result)
372
353
  print(hz.dashboard(bundle))
373
-
374
- # ── Walk-Forward Optimization ──
375
-
376
- from horizon.walkforward import walk_forward
377
- result = walk_forward(
378
- data=tick_data,
379
- pipeline_factory=lambda params: [make_fair(params), make_quoter(params)],
380
- param_grid={"spread": [0.02, 0.04, 0.06], "size": [5, 10, 20]},
381
- n_splits=5,
382
- train_ratio=0.7,
383
- expanding=True,
384
- objective="sharpe_ratio",
385
- purge_gap=3600.0,
386
- )
387
-
388
- # ── Calibration Analytics ──
389
-
390
- from horizon._horizon import calibration_curve, log_loss, edge_decay
391
- # calibration_curve(predictions, outcomes, n_bins) -> (bin_centers, actual_freqs, ece, brier)
392
- # log_loss(predictions, outcomes) -> float
393
- # edge_decay(edge_series, timestamps) -> half_life_seconds
394
354
  `;
395
355
 
396
356
  const ADVANCED_SDK_REFERENCE = `
397
357
  # ── Advanced SDK Capabilities ──
398
358
 
399
359
  ## Volatility Suite
400
- Six Rust-native estimators for adaptive spread sizing:
401
- hz.volatility(lookback=20, method="yang_zhang") # Pipeline function
360
+ hz.volatility(lookback=20, method="yang_zhang") # Pipeline function, injects VolatilitySnapshot into ctx
402
361
  # Methods: "close_to_close", "parkinson", "garman_klass", "yang_zhang", "ewma", "rolling"
403
- # Injects VolatilitySnapshot into ctx with .best property (best estimator auto-selected)
404
- # Use: widen spreads in high-vol regimes, tighten in low-vol
405
-
406
- ## Example — volatility-adaptive market maker:
407
- def adaptive_quoter(ctx, fair):
408
- if fair is None:
409
- return []
410
- vol = ctx.params.get("_volatility", None) # Injected by hz.volatility() pipeline
411
- base_spread = ctx.params.get("spread", 0.06)
412
- if vol and vol.best > 0:
413
- spread = base_spread * (1 + vol.best * 2) # widen on high vol
414
- else:
415
- spread = base_spread
416
- return hz.quotes(fair, spread=spread, size=ctx.params.get("size", 5))
362
+ # Access via ctx.params.get("_volatility").best widen spreads in high-vol, tighten in low-vol
417
363
 
418
364
  ## Execution Algorithms
419
- For large orders that need careful execution:
420
- from horizon import TWAP, VWAP, Iceberg
421
-
422
365
  TWAP(engine, side, size, duration_secs=300, num_slices=10) # Equal time slices
423
- VWAP(engine, side, size, duration_secs=300, volume_profile=[0.1, 0.15, ...]) # Volume-weighted
366
+ VWAP(engine, side, size, duration_secs=300, volume_profile=[...]) # Volume-weighted
424
367
  Iceberg(engine, side, total_size, show_size=5) # Hidden size
425
-
426
368
  # All have: .start(), .on_tick(), .is_complete, .total_filled
427
- # Use TWAP for uniform execution, VWAP for volume-aware, Iceberg for hiding size
428
369
 
429
370
  ## Market Discovery
430
- Find real markets programmatically instead of hardcoding slugs:
431
371
  hz.discover_markets(exchange="polymarket", query="bitcoin", min_volume=10000, sort_by="volume")
432
- hz.top_markets(exchange="polymarket", limit=20) # Highest-volume active markets
433
- hz.discover_events(exchange="polymarket") # Multi-outcome events
372
+ hz.top_markets(exchange="polymarket", limit=20)
373
+ hz.discover_events(exchange="polymarket")
434
374
 
435
375
  ## Portfolio Management
436
- For multi-strategy coordination and risk budgeting:
437
- port = hz.Portfolio(name="my_fund", capital=10000)
438
- port.add_position(market_id, side, size, entry_price)
439
- port.update_price(market_id, current_price)
440
- # Optimization: port.optimize("kelly") | "equal_weight" | "risk_parity" | "min_variance"
441
- # Risk: port.var_95(), port.cvar_95(), port.correlation_matrix()
442
- # Rebalance: port.needs_rebalance(threshold=0.05), port.rebalance_orders()
443
-
444
- ## Kelly Criterion (built-in)
445
- hz.edge(fair_prob=0.55, market_price=0.50) # Expected value
446
- hz.kelly(prob=0.55, price=0.50) # Full Kelly fraction
447
- hz.fractional_kelly(prob=0.55, price=0.50, fraction=0.25) # Conservative (recommended)
448
- hz.kelly_size(prob=0.55, price=0.50, bankroll=1000, max_size=50) # Contract units
449
- hz.kelly_sizer(fraction=0.25, bankroll=1000) # Pipeline factory
450
- hz.kelly_sizer_with_liquidity(fraction=0.25, bankroll=1000) # Dampens for illiquid markets
376
+ hz.Portfolio(name, capital) .add_position(), .update_price(), .optimize("kelly"|"equal_weight"|"risk_parity"|"min_variance"), .var_95(), .cvar_95(), .needs_rebalance(threshold), .rebalance_orders()
377
+
378
+ ## Kelly Criterion
379
+ hz.edge(fair_prob, market_price) # Expected value
380
+ hz.kelly(prob, price) # Full Kelly fraction
381
+ hz.fractional_kelly(prob, price, fraction=0.25) # Conservative (recommended)
382
+ hz.kelly_size(prob, price, bankroll, max_size) # Contract units
383
+ hz.kelly_sizer(fraction=0.25, bankroll=1000) # Pipeline factory
384
+ hz.kelly_sizer_with_liquidity(fraction=0.25, bankroll=1000) # Dampens for illiquid markets
451
385
 
452
386
  ## Sentinel — Continuous Risk Monitor
453
- hz.sentinel(portfolio, config={
454
- "drawdown_levels": [-5, -10, -20, -30], # alert, reduce, pause, exit
455
- "regime_lookback": 50,
456
- "var_confidence": 0.95,
457
- })
458
- # Auto-reduces position size in high-drawdown regimes
459
- # Detects: drawdown escalation, regime change, correlation spikes
387
+ hz.sentinel(portfolio, config={"drawdown_levels": [-5,-10,-20,-30], "regime_lookback": 50, "var_confidence": 0.95})
388
+ hz.suggest_hedges(portfolio, budget=500)
460
389
  # Actions: alert, reduce_positions, pause_trading, emergency_exit
461
- hz.suggest_hedges(portfolio, budget=500) # Hedge recommendations
462
-
463
- ## Promotion Gates (paper → live)
464
- Before deploying live, strategies should pass:
465
- - Sharpe ratio >= 1.0
466
- - At least 30 trades
467
- - Walk-forward p-value < 0.05
468
- - Probability of Backtest Overfitting (PBO) < 50%
469
- - Max drawdown < 10% of capital
470
390
 
471
391
  ## Quant Functions (Rust-native, call as hz.function_name)
472
392
 
473
393
  ### Risk Analytics
474
- hz.var(returns, confidence=0.95) # Value at Risk
475
- hz.cvar(returns, confidence=0.95) # Conditional VaR (expected shortfall)
476
- hz.max_drawdown(equity_curve) # Maximum drawdown
477
- hz.sharpe_ratio(returns, rf=0.0) # Annualized Sharpe
478
- hz.sortino_ratio(returns, rf=0.0) # Downside-only Sharpe
479
-
480
- ### Volatility (individual functions)
481
- hz.parkinson_vol(highs, lows) # Range-based
482
- hz.garman_klass_vol(opens, highs, lows, closes)
483
- hz.yang_zhang_vol(opens, highs, lows, closes)
484
- hz.ewma_vol(returns, lambda_=0.94) # Exponentially weighted
485
- hz.rolling_vol(returns, window=20)
394
+ hz.var(returns, confidence=0.95); hz.cvar(returns, confidence=0.95)
395
+ hz.max_drawdown(equity_curve); hz.sharpe_ratio(returns, rf=0.0); hz.sortino_ratio(returns, rf=0.0)
396
+
397
+ ### Volatility (individual)
398
+ hz.parkinson_vol(highs, lows); hz.garman_klass_vol(opens, highs, lows, closes)
399
+ hz.yang_zhang_vol(opens, highs, lows, closes); hz.ewma_vol(returns, lambda_=0.94); hz.rolling_vol(returns, window=20)
486
400
 
487
401
  ### Information Theory
488
- hz.shannon_entropy(distribution) # Bits of uncertainty
489
- hz.kl_divergence(p, q) # Distribution divergence
490
- hz.mutual_information(x, y) # Shared information
402
+ hz.shannon_entropy(distribution); hz.kl_divergence(p, q); hz.mutual_information(x, y)
491
403
 
492
404
  ### Market Microstructure
493
- hz.kyles_lambda(prices, volumes) # Price impact coefficient
494
- hz.amihud_ratio(returns, volumes) # Illiquidity measure
495
- hz.roll_spread(returns) # Implicit bid-ask
496
- hz.lob_imbalance(bids, asks, levels=5) # Orderbook pressure
497
- hz.weighted_mid(bids, asks) # Depth-weighted mid
405
+ hz.kyles_lambda(prices, volumes); hz.amihud_ratio(returns, volumes); hz.roll_spread(returns)
406
+ hz.lob_imbalance(bids, asks, levels=5); hz.weighted_mid(bids, asks)
498
407
 
499
408
  ### Statistical Testing
500
- hz.deflated_sharpe(sharpe, n_trials, n_obs) # Backtest overfitting check
501
- hz.benjamini_hochberg(p_values, alpha=0.05) # False discovery rate
502
-
503
- ### Advanced Filters & Detectors
504
- KalmanFilter(dim_state, dim_obs) # Linear state estimation
505
- UnscentedKF(dim_state, dim_obs) # Non-linear state estimation
506
- ParticleFilter(n_particles, dim_state) # Sequential Monte Carlo
507
- BocpdDetector(hazard_rate=100) # Bayesian changepoint detection
508
- MarkovRegimeModel(n_regimes=2) # Regime switching
509
- VpinDetector(volume_bucket_size) # Informed trading probability
510
- CusumDetector(threshold, drift) # Sequential change detection
511
- OfiTracker(window=100) # Order flow imbalance
409
+ hz.deflated_sharpe(sharpe, n_trials, n_obs); hz.benjamini_hochberg(p_values, alpha=0.05)
410
+
411
+ ### Filters & Detectors
412
+ KalmanFilter(dim_state, dim_obs); UnscentedKF(dim_state, dim_obs); ParticleFilter(n_particles, dim_state)
413
+ BocpdDetector(hazard_rate=100); MarkovRegimeModel(n_regimes=2); VpinDetector(volume_bucket_size)
414
+ CusumDetector(threshold, drift); OfiTracker(window=100)
512
415
 
513
416
  ### Copulas & Dependence
514
- hz.fit_copula(u, v, family="gaussian") # Bivariate copula
515
- hz.best_copula(u, v) # Auto-select family
516
- hz.fit_vine(data) # Vine copula for >2 variables
417
+ hz.fit_copula(u, v, family="gaussian"); hz.best_copula(u, v); hz.fit_vine(data)
517
418
 
518
419
  ### Portfolio Optimization
519
- hz.hrp_weights(returns) # Hierarchical Risk Parity
520
- hz.denoise_covariance(cov_matrix, n_obs) # Marcenko-Pastur shrinkage
521
- hz.robust_optimize(returns, gamma=1.0) # Worst-case robust
420
+ hz.hrp_weights(returns); hz.denoise_covariance(cov_matrix, n_obs); hz.robust_optimize(returns, gamma=1.0)
522
421
 
523
422
  ### Optimal Execution
524
423
  hz.gp_optimal_trajectory(total_size, urgency, risk_aversion, n_steps) # Garleanu-Pedersen
525
424
  hz.ac_optimal_schedule(total_size, volatility, n_steps, risk_aversion) # Almgren-Chriss
526
- hz.queue_fill_prob(queue_pos, total_depth, cancel_rate) # Fill probability
425
+ hz.queue_fill_prob(queue_pos, total_depth, cancel_rate)
527
426
 
528
427
  ### Data Preparation (AFML)
529
- hz.tick_bars(trades, threshold) # Fixed-count bars
530
- hz.volume_bars(trades, threshold) # Fixed-volume bars
531
- hz.dollar_bars(trades, threshold) # Fixed-dollar bars
532
- hz.triple_barrier_labels(prices, upper, lower, max_holding) # Event-driven labels
533
- hz.frac_diff_weights(d, threshold=1e-5) # Fractional differentiation
534
- hz.min_frac_diff(series, max_d=1.0) # Min d for stationarity
428
+ hz.tick_bars(trades, threshold); hz.volume_bars(trades, threshold); hz.dollar_bars(trades, threshold)
429
+ hz.triple_barrier_labels(prices, upper, lower, max_holding)
430
+ hz.frac_diff_weights(d, threshold=1e-5); hz.min_frac_diff(series, max_d=1.0)
535
431
 
536
432
  ### Lead-Lag & Causality
537
- hz.granger_causality(x, y, max_lag=10) # Causal relationships
538
- hz.cross_correlation_lags(x, y, max_lag) # Temporal alignment
539
- hz.lead_lag_network(series_dict) # Multi-asset structure
433
+ hz.granger_causality(x, y, max_lag=10); hz.cross_correlation_lags(x, y, max_lag); hz.lead_lag_network(series_dict)
540
434
 
541
435
  ### Stat Arb
542
- hz.cointegration_test(x, y) # Engle-Granger test
543
- hz.spread_zscore(x, y, lookback=60) # Mean-reversion z-score
436
+ hz.cointegration_test(x, y); hz.spread_zscore(x, y, lookback=60)
544
437
 
545
- ## hz.run() — Full Parameter Reference
546
- hz.run(
547
- exchange=hz.Polymarket(), # Single exchange (or exchanges=[...] for multi)
548
- markets=["slug-1", "slug-2"], # Market slugs
549
- feeds={"mid": hz.PolymarketBook("slug-1")},
550
- pipeline=[signal_fn, quoter_fn], # Or dict: {"slug-1": [fn1], "*": [default_fn]}
551
- risk=hz.Risk(...),
552
- mode="paper", # "paper" | "live"
553
- params={"spread": 0.06},
554
- interval=0.5, # Seconds between cycles (default 0.5)
555
- events=[hz.Event(...)], # For multi-outcome events
556
- db_path="./strategy.db", # SQLite persistence (default: enabled)
557
- netting_pairs=[("mkt-a", "mkt-b")], # Cross-hedge pairs
558
- dashboard=True, # Enable built-in TUI dashboard
559
- )
438
+ ### Walk-Forward Optimization
439
+ from horizon.walkforward import walk_forward
440
+ walk_forward(data, pipeline_factory, param_grid, n_splits=5, train_ratio=0.7, expanding=True, objective="sharpe_ratio", purge_gap=3600.0)
560
441
 
561
- ## hz.backtest() — Advanced Parameters
562
- hz.backtest(
563
- pipeline=[...], markets=[...], feeds={...}, risk=hz.Risk(...),
564
- data_points=500, initial_capital=1000, base_price=0.50,
565
- fill_model="deterministic", # "deterministic" | "probabilistic" | "glft"
566
- fill_model_params={}, # Model-specific: {"fill_rate": 0.7}
567
- impact_temporary_bps=2.0, # Temporary market impact
568
- impact_permanent_fraction=0.1, # Permanent price impact
569
- latency_ms=50.0, # Simulated execution latency
570
- rng_seed=42, # For reproducibility
571
- )
442
+ ### Calibration Analytics
443
+ from horizon._horizon import calibration_curve, log_loss, edge_decay
444
+ # calibration_curve(predictions, outcomes, n_bins) -> (bin_centers, actual_freqs, ece, brier)
445
+ # log_loss(predictions, outcomes) -> float; edge_decay(edge_series, timestamps) -> half_life_seconds
572
446
  `;
573
447
 
574
448
 
@@ -671,55 +545,23 @@ hz.run(
671
545
 
672
546
  const EXAMPLE_BACKTEST = `
673
547
  import horizon as hz
674
- from horizon.context import FeedData
675
548
  import json
676
549
 
677
- def fair_value(ctx):
678
- feed = ctx.feeds.get("default", FeedData())
679
- return feed.price if feed.price > 0 else 0.50
680
-
681
- def quoter(ctx, fair):
682
- skew = ctx.inventory.net * 0.002
683
- return hz.quotes(fair - skew, spread=0.06, size=5)
684
-
685
- data = [
686
- {"timestamp": float(i), "price": 0.50 + 0.05 * ((-1) ** i) * (i % 10) / 10}
687
- for i in range(500)
688
- ]
689
-
690
550
  result = hz.backtest(
691
- name="mm_backtest",
692
- markets=["test-market"],
693
- data=data,
551
+ name="mm_backtest", markets=["test-market"],
552
+ data=[{"timestamp": float(i), "price": 0.50 + 0.05*((-1)**i)*(i%10)/10} for i in range(500)],
694
553
  pipeline=[fair_value, quoter],
695
- risk=hz.Risk(max_position=50, max_drawdown_pct=10),
696
- initial_capital=100.0,
554
+ risk=hz.Risk(max_position=50, max_drawdown_pct=10), initial_capital=100.0,
697
555
  )
698
556
 
699
- # Output structured JSON for TUI parsing
557
+ # TUI output markers (REQUIRED for dashboard parsing):
700
558
  m = result.metrics
701
559
  print("---BACKTEST_JSON---")
702
- print(json.dumps({
703
- "strategy_name": "mm_backtest",
704
- "summary": result.summary(),
705
- "equity_curve": [e for _, e in result.equity_curve],
706
- "trade_count": len(result.trades),
707
- "metrics": {
708
- "total_return": m.total_return,
709
- "max_drawdown": m.max_drawdown,
710
- "sharpe_ratio": m.sharpe_ratio,
711
- "sortino_ratio": m.sortino_ratio,
712
- "win_rate": m.win_rate,
713
- "profit_factor": m.profit_factor,
714
- "total_trades": m.trade_count,
715
- "expectancy": m.expectancy,
716
- "total_fees": m.total_fees,
717
- },
718
- "pnl_by_market": result.pnl_by_market(),
719
- }))
560
+ print(json.dumps({"strategy_name": "mm_backtest", "summary": result.summary(),
561
+ "equity_curve": [e for _, e in result.equity_curve], "trade_count": len(result.trades),
562
+ "metrics": {k: getattr(m, k) for k in ["total_return","max_drawdown","sharpe_ratio","sortino_ratio","win_rate","profit_factor","trade_count","expectancy","total_fees"]},
563
+ "pnl_by_market": result.pnl_by_market()}))
720
564
  print("---END_BACKTEST_JSON---")
721
-
722
- # ASCII dashboard
723
565
  bundle = hz.from_backtest(result)
724
566
  print("---ASCII_DASHBOARD---")
725
567
  print(hz.dashboard(bundle))
@@ -728,275 +570,15 @@ print("---END_ASCII_DASHBOARD---")
728
570
 
729
571
  const STRATEGY_TEMPLATES = `
730
572
  # ── Strategy Templates ──
731
- # Reference these when the user asks for a specific strategy type.
732
- # Adapt parameters to the user's market, capital, and risk tolerance.
733
-
734
- ## Template 1: Conservative Market Maker
735
- # Style: Wide spreads, small size, low risk. Good for beginners.
736
- # Capital: $500-2000 | Risk: Conservative | Expected Sharpe: 0.5-1.5
737
- # Best for: High-volume markets with tight spreads
738
-
739
- import horizon as hz
740
-
741
- def fair_value(ctx):
742
- feed = ctx.feeds.get("mid")
743
- if not feed or feed.is_stale(30):
744
- return None
745
- return feed.price
746
-
747
- def conservative_quoter(ctx, fair):
748
- if fair is None:
749
- return []
750
- inv = ctx.inventory.net
751
- max_pos = ctx.params.get("max_position", 50)
752
- skew = inv / max_pos if max_pos > 0 else 0
753
- spread = ctx.params.get("spread", 0.10) * (1 + abs(skew) * 0.5)
754
- size = ctx.params.get("size", 5)
755
- if abs(inv) > max_pos * 0.8:
756
- size = max(1, size // 2)
757
- return hz.quotes(fair, spread=spread, size=size)
758
-
759
- hz.run(
760
- name="ConservativeMarketMaker",
761
- exchange=hz.Polymarket(),
762
- markets=["your-market-slug"],
763
- feeds={"mid": hz.PolymarketBook("your-market-slug")},
764
- pipeline=[fair_value, conservative_quoter],
765
- risk=hz.Risk(
766
- max_position=50,
767
- max_notional=500,
768
- max_drawdown_pct=3,
769
- max_order_size=5,
770
- ),
771
- mode="paper",
772
- params={"initial_capital": 1000, "spread": 0.10, "size": 5, "max_position": 50}
773
- )
774
- # Capital & Risk: $1,000 paper. Max $500 exposure. Kill switch at 3% ($15). Conservative.
775
-
776
- ## Template 2: Momentum Follower with Kelly Sizing
777
- # Style: Directional, follows price trends. Uses Kelly for position sizing.
778
- # Capital: $1000-5000 | Risk: Moderate | Expected Sharpe: 1.0-2.0
779
- # Best for: Trending markets (elections, crypto events)
780
-
781
- import horizon as hz
782
- from collections import deque
783
-
784
- _prices: dict[str, deque] = {}
785
-
786
- def momentum_signal(ctx):
787
- feed = ctx.feeds.get("mid")
788
- if not feed or feed.is_stale(30):
789
- return None
790
- slug = ctx.market.slug
791
- lookback = int(ctx.params.get("lookback", 20))
792
- if slug not in _prices:
793
- _prices[slug] = deque(maxlen=lookback)
794
- _prices[slug].append(feed.price)
795
- if len(_prices[slug]) < lookback:
796
- return None
797
- oldest = _prices[slug][0]
798
- if oldest == 0:
799
- return None
800
- momentum = (feed.price - oldest) / oldest
801
- return {"mid": feed.price, "momentum": momentum}
802
-
803
- def kelly_quoter(ctx, signal):
804
- if signal is None:
805
- return []
806
- momentum = signal["momentum"]
807
- threshold = ctx.params.get("threshold", 0.02)
808
- if abs(momentum) < threshold:
809
- return []
810
- fair = signal["mid"]
811
- prob = 0.5 + min(0.2, abs(momentum) * 2)
812
- fraction = ctx.params.get("kelly_fraction", 0.25)
813
- kelly_f = max(0, (prob - (1 - prob)) * fraction)
814
- size = max(1, int(kelly_f * ctx.params.get("bankroll", 1000) / fair))
815
- size = min(size, ctx.params.get("max_size", 20))
816
- spread = ctx.params.get("spread", 0.04)
817
- return hz.quotes(fair, spread=spread, size=size)
818
-
819
- hz.run(
820
- name="MomentumFollower",
821
- exchange=hz.Polymarket(),
822
- markets=["your-market-slug"],
823
- feeds={"mid": hz.PolymarketBook("your-market-slug")},
824
- pipeline=[momentum_signal, kelly_quoter],
825
- risk=hz.Risk(
826
- max_position=100,
827
- max_notional=1000,
828
- max_drawdown_pct=5,
829
- max_order_size=20,
830
- ),
831
- mode="paper",
832
- params={"initial_capital": 2000, "lookback": 20, "threshold": 0.02, "spread": 0.04, "kelly_fraction": 0.25, "bankroll": 2000, "max_size": 20}
833
- )
834
- # Capital & Risk: $2,000 paper. Max $1,000 exposure. Kill switch at 5% ($50). Moderate.
835
-
836
- ## Template 3: Mean Reversion
837
- # Style: Fades extreme prices toward fair value using z-score.
838
- # Capital: $500-2000 | Risk: Conservative-Moderate | Expected Sharpe: 0.8-1.5
839
- # Best for: Range-bound markets, markets with known fair value
840
-
841
- import horizon as hz
842
- from collections import deque
843
- import math
844
-
845
- _history: dict[str, deque] = {}
846
-
847
- def mean_reversion_signal(ctx):
848
- feed = ctx.feeds.get("mid")
849
- if not feed or feed.is_stale(30):
850
- return None
851
- slug = ctx.market.slug
852
- window = int(ctx.params.get("window", 30))
853
- if slug not in _history:
854
- _history[slug] = deque(maxlen=window)
855
- _history[slug].append(feed.price)
856
- if len(_history[slug]) < window:
857
- return None
858
- prices = list(_history[slug])
859
- mean = sum(prices) / len(prices)
860
- std = math.sqrt(sum((p - mean) ** 2 for p in prices) / len(prices))
861
- if std < 0.001:
862
- return None
863
- zscore = (feed.price - mean) / std
864
- return {"mid": feed.price, "mean": mean, "zscore": zscore}
865
-
866
- def reversion_quoter(ctx, signal):
867
- if signal is None:
868
- return []
869
- z = signal["zscore"]
870
- entry_z = ctx.params.get("entry_z", 1.5)
871
- if abs(z) < entry_z:
872
- return []
873
- fair = signal["mean"]
874
- spread = ctx.params.get("spread", 0.06)
875
- size = ctx.params.get("size", 10)
876
- return hz.quotes(fair, spread=spread, size=size)
877
-
878
- hz.run(
879
- name="MeanReversion",
880
- exchange=hz.Polymarket(),
881
- markets=["your-market-slug"],
882
- feeds={"mid": hz.PolymarketBook("your-market-slug")},
883
- pipeline=[mean_reversion_signal, reversion_quoter],
884
- risk=hz.Risk(
885
- max_position=80,
886
- max_notional=800,
887
- max_drawdown_pct=5,
888
- max_order_size=10,
889
- ),
890
- mode="paper",
891
- params={"initial_capital": 1000, "window": 30, "entry_z": 1.5, "spread": 0.06, "size": 10}
892
- )
893
- # Capital & Risk: $1,000 paper. Max $800 exposure. Kill switch at 5% ($40). Moderate.
894
-
895
- ## Template 4: Cross-Market Arb (Polymarket vs Kalshi)
896
- # Style: Captures price discrepancies between exchanges.
897
- # Capital: $2000-10000 | Risk: Low (market-neutral) | Expected Sharpe: 1.5-3.0
898
- # Best for: Markets listed on both Polymarket and Kalshi
899
-
900
- import horizon as hz
901
-
902
- def arb_detector(ctx):
903
- poly = ctx.feeds.get("poly")
904
- kalshi = ctx.feeds.get("kalshi")
905
- if not poly or poly.is_stale(30) or not kalshi or kalshi.is_stale(30):
906
- return None
907
- edge = abs(poly.price - kalshi.price)
908
- min_edge = ctx.params.get("min_edge", 0.03)
909
- if edge < min_edge:
910
- return None
911
- if poly.price < kalshi.price:
912
- return {"buy_exchange": "poly", "fair": poly.price, "edge": edge}
913
- else:
914
- return {"buy_exchange": "kalshi", "fair": kalshi.price, "edge": edge}
915
-
916
- def arb_quoter(ctx, signal):
917
- if signal is None:
918
- return []
919
- size = ctx.params.get("size", 10)
920
- spread = ctx.params.get("spread", 0.02)
921
- return hz.quotes(signal["fair"], spread=spread, size=size)
922
-
923
- hz.run(
924
- name="CrossMarketArb",
925
- exchanges=[hz.Polymarket(), hz.Kalshi()],
926
- markets=["your-market-slug"],
927
- feeds={
928
- "poly": hz.PolymarketBook("your-market-slug"),
929
- "kalshi": hz.KalshiBook("your-kalshi-ticker"),
930
- },
931
- pipeline=[arb_detector, arb_quoter],
932
- risk=hz.Risk(
933
- max_position=100,
934
- max_notional=2000,
935
- max_drawdown_pct=3,
936
- max_order_size=10,
937
- ),
938
- mode="paper",
939
- params={"initial_capital": 5000, "min_edge": 0.03, "spread": 0.02, "size": 10}
940
- )
941
- # Capital & Risk: $5,000 paper. Max $2,000 exposure. Kill switch at 3% ($60). Low risk (market-neutral).
942
-
943
- ## Template 5: Multi-Signal Ensemble
944
- # Style: Combines price, spread, momentum, and flow signals.
945
- # Capital: $1000-5000 | Risk: Moderate | Expected Sharpe: 1.0-2.5
946
- # Best for: Liquid markets with diverse data
947
-
948
- import horizon as hz
949
- from collections import deque
950
-
951
- _momentum_history: dict[str, deque] = {}
952
-
953
- def multi_signal(ctx):
954
- feed = ctx.feeds.get("mid")
955
- if not feed or feed.is_stale(30):
956
- return None
957
- slug = ctx.market.slug
958
- lookback = int(ctx.params.get("lookback", 15))
959
- if slug not in _momentum_history:
960
- _momentum_history[slug] = deque(maxlen=lookback)
961
- _momentum_history[slug].append(feed.price)
962
- price_signal = feed.price
963
- spread_signal = (feed.ask - feed.bid) if feed.ask > 0 and feed.bid > 0 else 0.05
964
- history = _momentum_history[slug]
965
- momentum_signal = (feed.price - history[0]) / history[0] if len(history) >= lookback and history[0] > 0 else 0
966
- inventory_signal = -ctx.inventory.net * 0.001
967
- weights = ctx.params.get("weights", [0.4, 0.2, 0.2, 0.2])
968
- combined = (
969
- weights[0] * price_signal +
970
- weights[1] * (1 - spread_signal * 10) * price_signal +
971
- weights[2] * momentum_signal * price_signal +
972
- weights[3] * inventory_signal
973
- )
974
- fair = max(0.01, min(0.99, combined))
975
- return fair
976
-
977
- def ensemble_quoter(ctx, fair):
978
- if fair is None:
979
- return []
980
- spread = ctx.params.get("spread", 0.05)
981
- size = ctx.params.get("size", 8)
982
- return hz.quotes(fair, spread=spread, size=size)
983
-
984
- hz.run(
985
- name="MultiSignalEnsemble",
986
- exchange=hz.Polymarket(),
987
- markets=["your-market-slug"],
988
- feeds={"mid": hz.PolymarketBook("your-market-slug")},
989
- pipeline=[multi_signal, ensemble_quoter],
990
- risk=hz.Risk(
991
- max_position=80,
992
- max_notional=1000,
993
- max_drawdown_pct=5,
994
- max_order_size=15,
995
- ),
996
- mode="paper",
997
- params={"initial_capital": 2000, "lookback": 15, "spread": 0.05, "size": 8, "weights": [0.4, 0.2, 0.2, 0.2]}
998
- )
999
- # Capital & Risk: $2,000 paper. Max $1,000 exposure. Kill switch at 5% ($50). Moderate.
573
+ # Adapt patterns from EXAMPLE_MOMENTUM and EXAMPLE_INVENTORY_MM above. Key templates:
574
+
575
+ | Template | Style | Capital | Risk | Sharpe | Best For |
576
+ |---|---|---|---|---|---|
577
+ | Conservative MM | Wide spreads, small size, inventory skew with size reduction at 80% capacity | $500-2K | Conservative (3% DD) | 0.5-1.5 | High-volume markets |
578
+ | Momentum+Kelly | Directional trend-following, hz.fractional_kelly for sizing, threshold filter | $1K-5K | Moderate (5% DD) | 1.0-2.0 | Trending markets (elections, crypto) |
579
+ | Mean Reversion | Z-score of rolling window, fade extremes toward mean, entry_z threshold | $500-2K | Moderate (5% DD) | 0.8-1.5 | Range-bound markets |
580
+ | Cross-Market Arb | Two feeds (poly+kalshi), edge detection, market-neutral. Uses exchanges=[...] | $2K-10K | Low (3% DD) | 1.5-3.0 | Dual-listed markets |
581
+ | Multi-Signal Ensemble | Weighted combination of price, spread, momentum, inventory signals via hz.combine_signals | $1K-5K | Moderate (5% DD) | 1.0-2.5 | Liquid markets |
1000
582
  `;
1001
583
 
1002
584
  /**
@@ -1094,76 +676,25 @@ If the user wants live mode, warn them first, then deploy with dry_run=false.
1094
676
 
1095
677
  **The user does NOT need to know strategy_id or credential_id.** You handle that.
1096
678
 
1097
- ## File I/O (Workspace)
1098
-
1099
- You have sandboxed file access in ~/.horizon/workspace/. Use it for dashboards, data, and scripts.
1100
-
1101
- - \`write_file(path, content)\` — Write to workspace. Creates dirs. Max 1MB. Blocked: .sh/.bash/.zsh
1102
- - \`read_file(path)\` — Read from workspace
1103
- - \`list_files(directory?)\` — List files. Default subdirs: dashboards/, scripts/, data/
1104
-
1105
- Paths are relative to workspace root. No absolute paths, no .., no dotfiles.
679
+ ## File I/O (Workspace: ~/.horizon/workspace/)
680
+ \`write_file(path, content)\`, \`read_file(path)\`, \`list_files(directory?)\` — sandboxed, relative paths only. Subdirs: dashboards/, scripts/, data/. Max 1MB. Blocked: .sh/.bash/.zsh
1106
681
 
1107
682
  ## Dashboard (File-Based — PREFERRED)
1108
683
 
1109
- **NEVER pass huge HTML strings to custom_html.** Use file-based mode instead:
1110
-
1111
- 1. \`write_file("dashboards/monitor.html", html)\` — Write the HTML file
1112
- 2. \`spawn_dashboard(file_path="dashboards/monitor.html")\` — Serve it (reads from disk each request)
1113
- 3. \`check_dashboard_errors()\` — Verify no JS errors
1114
- 4. If errors: \`read_file("dashboards/monitor.html")\` → fix → \`write_file(...)\` → browser refresh auto-shows changes
1115
-
1116
- For quick built-in monitoring: \`spawn_dashboard(strategy_id="local")\` — no custom HTML needed.
684
+ 1. \`write_file("dashboards/monitor.html", html)\` \`spawn_dashboard(file_path="dashboards/monitor.html")\` \`check_dashboard_errors()\`
685
+ 2. Quick built-in: \`spawn_dashboard(strategy_id="local")\` — no custom HTML needed.
1117
686
 
1118
- ### Horizon Design System (MANDATORY for all dashboards)
687
+ ### Horizon Design System (MANDATORY)
688
+ CSS vars: \`--bg:#0d1117; --bg2:#161b22; --bg3:#1c2128; --border:#30363d; --text:#c9d1d9; --text-dim:#636e7b; --text-bright:#f0f6fc; --accent:#4d8ef7; --green:#3fb950; --red:#f85149; --yellow:#d29922; --radius:12px\`
689
+ - Dark theme only. Cards: bg2+border+radius. Headers/labels: 11px uppercase, text-dim. Values: 22px weight-600 tabular-nums.
690
+ - P&L: green (+$) / red (-$). Chart.js: animation:false, accent border, 0 pointRadius, 0.2 tension.
691
+ - Layout: Header bar → 6-metric grid → equity chart (5fr) + positions (2fr) → logs (max-height 180px scroll).
1119
692
 
1120
- Every dashboard you create MUST follow this design system exactly. No exceptions.
1121
-
1122
- **Color Tokens (CSS variables always define in :root):**
1123
- \`\`\`css
1124
- :root {
1125
- --bg: #0d1117; --bg2: #161b22; --bg3: #1c2128;
1126
- --border: #30363d; --border-focus: #4d8ef7;
1127
- --text: #c9d1d9; --text-dim: #636e7b; --text-bright: #f0f6fc;
1128
- --accent: #4d8ef7; --accent-dim: #2557a7;
1129
- --green: #3fb950; --red: #f85149; --yellow: #d29922;
1130
- --radius: 12px;
1131
- }
1132
- \`\`\`
1133
-
1134
- **Rules:**
1135
- - body: \`background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\`
1136
- - Cards: \`background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius)\`
1137
- - Card headers: \`font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-dim); border-bottom: 1px solid var(--border)\`
1138
- - Metric labels: \`font-size: 10px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-dim)\`
1139
- - Metric values: \`font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums\`
1140
- - P&L positive: \`color: var(--green)\` with +$ prefix. P&L negative: \`color: var(--red)\` with -$ prefix
1141
- - Status dot live: \`background: var(--green); box-shadow: 0 0 6px var(--green)\`
1142
- - Header bar: \`background: var(--bg2); border-bottom: 1px solid var(--border)\` with "HORIZON" logo in accent, 11px, letter-spacing 3px
1143
- - Monospace text (logs, code): \`font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px\`
1144
- - Grid layout: CSS Grid with \`gap: 16px; padding: 0 24px\`
1145
- - Metrics row: \`display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1px; background: var(--border)\` with each metric cell on var(--bg2)
1146
- - Chart.js config: \`animation: false; borderColor: rgba(77,142,247,0.8); fill.above: rgba(77,142,247,0.06); fill.below: rgba(248,81,73,0.06); pointRadius: 0; tension: 0.2\`
1147
- - Grid lines: \`color: #21262d\`. Tick labels: \`color: #636e7b; font-size: 10\`
1148
- - Empty states: centered, dim text with pulsing accent dot
1149
- - NEVER use white backgrounds, light themes, or colors outside this palette
1150
- - NEVER use inline styles for colors — always use CSS variables
1151
-
1152
- **Data Sources (fetch from these live API endpoints):**
1153
- - \`/api/local/metrics\` → \`{ pnl, rpnl, upnl, orders, positions, trades, win_rate, sharpe, max_dd, exposure, pos: [{id, side, sz, entry, rpnl, upnl}], hist: [number] }\`
1154
- - \`/api/local/logs\` → \`string[]\` (stdout/stderr lines)
1155
- - \`/api/local-logs\` → \`{ [pid]: { stdout, stderr, alive } }\`
1156
- - \`/api/strategy\` → \`{ name, code, params, riskConfig }\`
1157
- - Auto-refresh: \`setInterval(refresh, 3000)\` for local, 10000 for platform
1158
-
1159
- **Standard Metrics to Display:**
1160
- P&L (with realized sub-label), Win Rate (with trade count), Sharpe Ratio, Max Drawdown %, Exposure $, Orders count. Color-code: green for good (P&L>0, win_rate>=50%, sharpe>1), red for bad, yellow for warnings.
1161
-
1162
- **Standard Layout Order:**
1163
- 1. Header bar (logo + strategy name + LOCAL/PLATFORM badge + status dot + uptime)
1164
- 2. Metrics row (6 metric cards in auto-fit grid)
1165
- 3. Main grid: equity chart (5fr) + positions list (2fr)
1166
- 4. Logs card (full width, max-height 180px with overflow scroll)
693
+ **Data Sources:**
694
+ - \`/api/local/metrics\` → \`{pnl, rpnl, upnl, orders, positions, trades, win_rate, sharpe, max_dd, exposure, pos, hist}\`
695
+ - \`/api/local/logs\` \`string[]\` | \`/api/local-logs\` \`{[pid]: {stdout, stderr, alive}}\`
696
+ - \`/api/strategy\` → \`{name, code, params, riskConfig}\`
697
+ - Refresh: 3s local, 10s platform
1167
698
 
1168
699
  ## Version Control
1169
700
 
@@ -1225,12 +756,6 @@ When user asks "what should I trade?" or "find opportunities" → scan_opportuni
1225
756
  - **get_metrics / get_logs** — Monitor running deployments
1226
757
  - **check_dashboard_errors** — Check JS errors in file-based dashboards
1227
758
 
1228
- ## Communication Style
1229
- - SHORT and DIRECT. No filler, no preamble.
1230
- - For code: write the fence, then 1-2 sentences explaining what it does.
1231
- - For edits: one sentence + the edit_strategy call.
1232
- - For questions: answer clearly. Show numbers when analyzing viability.
1233
-
1234
759
  ## Code Requirements
1235
760
 
1236
761
  The code you generate is the EXACT code that runs on Horizon. It must:
@@ -1274,8 +799,6 @@ ${EXAMPLE_MOMENTUM}
1274
799
  ${EXAMPLE_INVENTORY_MM}
1275
800
 
1276
801
  ### Strategy Templates
1277
- 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.
1278
-
1279
802
  ${STRATEGY_TEMPLATES}`;
1280
803
  }
1281
804