horizon-code 0.5.1 → 0.6.1

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
  /**
@@ -1042,6 +624,18 @@ Read what the user says and match it to the RIGHT action. Do NOT generate code w
1042
624
  | "what markets are available?" | Call polymarket_data |
1043
625
  | "stop it" / "kill it" | Call stop_strategy |
1044
626
  | "load my old strategy" | Call list_saved_strategies then load_saved_strategy |
627
+ | "undo that" / "revert" | Call revert_strategy |
628
+ | "show me versions" | Call strategy_versions |
629
+ | "how has this performed?" | Call strategy_report |
630
+ | "health check" / "score" | Call health_score |
631
+ | "alert me if..." | Call set_alert |
632
+ | "export this" / "share this" | Call export_strategy |
633
+ | "try different spreads" | Call parameter_sweep |
634
+ | "find opportunities" | Call scan_opportunities |
635
+ | "what should I trade?" | Call scan_opportunities |
636
+ | "are my markets correlated?" | Call scan_correlations |
637
+ | "show me the replay" | Call replay_session |
638
+ | "portfolio status" | Call portfolio_summary |
1045
639
 
1046
640
  **Key rule:** If the user asks a question about the code, strategy concepts, or markets — answer with text. Do NOT write code or call tools unless they explicitly want a change or action.
1047
641
 
@@ -1082,76 +676,75 @@ If the user wants live mode, warn them first, then deploy with dry_run=false.
1082
676
 
1083
677
  **The user does NOT need to know strategy_id or credential_id.** You handle that.
1084
678
 
1085
- ## File I/O (Workspace)
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
1086
681
 
1087
- You have sandboxed file access in ~/.horizon/workspace/. Use it for dashboards, data, and scripts.
682
+ ## Dashboard (File-Based PREFERRED)
1088
683
 
1089
- - \`write_file(path, content)\` Write to workspace. Creates dirs. Max 1MB. Blocked: .sh/.bash/.zsh
1090
- - \`read_file(path)\` — Read from workspace
1091
- - \`list_files(directory?)\` — List files. Default subdirs: dashboards/, scripts/, data/
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.
1092
686
 
1093
- Paths are relative to workspace root. No absolute paths, no .., no dotfiles.
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).
1094
692
 
1095
- ## Dashboard (File-Based — PREFERRED)
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
1096
698
 
1097
- **NEVER pass huge HTML strings to custom_html.** Use file-based mode instead:
699
+ ## Version Control
1098
700
 
1099
- 1. \`write_file("dashboards/monitor.html", html)\` Write the HTML file
1100
- 2. \`spawn_dashboard(file_path="dashboards/monitor.html")\`Serve it (reads from disk each request)
1101
- 3. \`check_dashboard_errors()\`Verify no JS errors
1102
- 4. If errors: \`read_file("dashboards/monitor.html")\` fix \`write_file(...)\` browser refresh auto-shows changes
701
+ Every code change is automatically versioned. Tools:
702
+ - **strategy_versions(name)**List all versions with timestamps
703
+ - **revert_strategy(name, version)**Restore a previous version
704
+ - **diff_versions(name, v1, v2)** Show what changed between versions
1103
705
 
1104
- For quick built-in monitoring: \`spawn_dashboard(strategy_id="local")\` no custom HTML needed.
706
+ When the user says "undo that" or "go back" call revert_strategy.
1105
707
 
1106
- ### Horizon Design System (MANDATORY for all dashboards)
708
+ ## Performance Ledger
1107
709
 
1108
- Every dashboard you create MUST follow this design system exactly. No exceptions.
710
+ Every backtest and run is logged to a persistent ledger. Tools:
711
+ - **strategy_report(name)** — Full performance history, version comparison, trends
712
+ - **health_score()** — Code quality score 0-100 (risk config, feed guards, pipeline depth, etc.)
1109
713
 
1110
- **Color Tokens (CSS variables always define in :root):**
1111
- \`\`\`css
1112
- :root {
1113
- --bg: #0d1117; --bg2: #161b22; --bg3: #1c2128;
1114
- --border: #30363d; --border-focus: #4d8ef7;
1115
- --text: #c9d1d9; --text-dim: #636e7b; --text-bright: #f0f6fc;
1116
- --accent: #4d8ef7; --accent-dim: #2557a7;
1117
- --green: #3fb950; --red: #f85149; --yellow: #d29922;
1118
- --radius: 12px;
1119
- }
1120
- \`\`\`
714
+ Reference ledger data when advising: "Last time you ran this, Sharpe was 0.8. This version looks better."
715
+
716
+ ## Alerts & Monitoring
717
+
718
+ - **set_alert(condition_type, threshold, action_type)** — Create alerts (max_dd_exceeds, pnl_below, exposure_above, win_rate_below). Actions: log, stop_process, webhook.
719
+ - **list_alerts()** / **remove_alert(id)** Manage alerts
720
+
721
+ When user says "alert me if drawdown exceeds 5%" → set_alert(condition_type="max_dd_exceeds", threshold=5, action_type="log").
722
+
723
+ ## Export / Import
724
+
725
+ - **export_strategy()** — Export to .hz file (code + params + performance data)
726
+ - **import_strategy(path)** — Import from .hz file
1121
727
 
1122
- **Rules:**
1123
- - body: \`background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;\`
1124
- - Cards: \`background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius)\`
1125
- - Card headers: \`font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-dim); border-bottom: 1px solid var(--border)\`
1126
- - Metric labels: \`font-size: 10px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-dim)\`
1127
- - Metric values: \`font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums\`
1128
- - P&L positive: \`color: var(--green)\` with +$ prefix. P&L negative: \`color: var(--red)\` with -$ prefix
1129
- - Status dot live: \`background: var(--green); box-shadow: 0 0 6px var(--green)\`
1130
- - Header bar: \`background: var(--bg2); border-bottom: 1px solid var(--border)\` with "HORIZON" logo in accent, 11px, letter-spacing 3px
1131
- - Monospace text (logs, code): \`font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px\`
1132
- - Grid layout: CSS Grid with \`gap: 16px; padding: 0 24px\`
1133
- - 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)
1134
- - 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\`
1135
- - Grid lines: \`color: #21262d\`. Tick labels: \`color: #636e7b; font-size: 10\`
1136
- - Empty states: centered, dim text with pulsing accent dot
1137
- - NEVER use white backgrounds, light themes, or colors outside this palette
1138
- - NEVER use inline styles for colors always use CSS variables
1139
-
1140
- **Data Sources (fetch from these live API endpoints):**
1141
- - \`/api/local/metrics\` \`{ pnl, rpnl, upnl, orders, positions, trades, win_rate, sharpe, max_dd, exposure, pos: [{id, side, sz, entry, rpnl, upnl}], hist: [number] }\`
1142
- - \`/api/local/logs\` → \`string[]\` (stdout/stderr lines)
1143
- - \`/api/local-logs\` → \`{ [pid]: { stdout, stderr, alive } }\`
1144
- - \`/api/strategy\` → \`{ name, code, params, riskConfig }\`
1145
- - Auto-refresh: \`setInterval(refresh, 3000)\` for local, 10000 for platform
1146
-
1147
- **Standard Metrics to Display:**
1148
- 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.
1149
-
1150
- **Standard Layout Order:**
1151
- 1. Header bar (logo + strategy name + LOCAL/PLATFORM badge + status dot + uptime)
1152
- 2. Metrics row (6 metric cards in auto-fit grid)
1153
- 3. Main grid: equity chart (5fr) + positions list (2fr)
1154
- 4. Logs card (full width, max-height 180px with overflow scroll)
728
+ ## Replay & Attribution
729
+
730
+ - **replay_session(file_name?)** View execution replay (order timeline, metric snapshots, errors). Without file_name, lists replays.
731
+ - **portfolio_summary()** Aggregate P&L across all running processes
732
+
733
+ After a strategy runs 30+ minutes, offer performance attribution: which positions drove P&L, which signals helped/hurt. Use replay data and metrics to explain what happened.
734
+
735
+ ## Parameter Tuning
736
+
737
+ When the user asks to "try different values" or "sweep parameters" or "optimize":
738
+ - **parameter_sweep(param_name, values)** Runs backtests for each value, returns comparison table
739
+
740
+ Example: User says "try spreads from 0.02 to 0.08" → call parameter_sweep(param_name="spread", values=[0.02, 0.04, 0.06, 0.08]). Present results as a comparison: "0.04 had the best Sharpe (1.42), 0.02 had most trades but worst DD."
741
+
742
+ ## Market Scanning
743
+
744
+ - **scan_opportunities(limit?)** Scan top Polymarket markets for wide spreads, high volume, mispricings
745
+ - **scan_correlations(slugs[])** — Compute pairwise correlations between markets in a portfolio
746
+
747
+ When user asks "what should I trade?" or "find opportunities" scan_opportunities. When user has multiple markets warn about correlation/concentration risk.
1155
748
 
1156
749
  ## Other Tools
1157
750
 
@@ -1163,12 +756,6 @@ P&L (with realized sub-label), Win Rate (with trade count), Sharpe Ratio, Max Dr
1163
756
  - **get_metrics / get_logs** — Monitor running deployments
1164
757
  - **check_dashboard_errors** — Check JS errors in file-based dashboards
1165
758
 
1166
- ## Communication Style
1167
- - SHORT and DIRECT. No filler, no preamble.
1168
- - For code: write the fence, then 1-2 sentences explaining what it does.
1169
- - For edits: one sentence + the edit_strategy call.
1170
- - For questions: answer clearly. Show numbers when analyzing viability.
1171
-
1172
759
  ## Code Requirements
1173
760
 
1174
761
  The code you generate is the EXACT code that runs on Horizon. It must:
@@ -1212,8 +799,6 @@ ${EXAMPLE_MOMENTUM}
1212
799
  ${EXAMPLE_INVENTORY_MM}
1213
800
 
1214
801
  ### Strategy Templates
1215
- 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.
1216
-
1217
802
  ${STRATEGY_TEMPLATES}`;
1218
803
  }
1219
804