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.
- package/package.json +1 -1
- package/src/ai/client.ts +13 -6
- package/src/ai/system-prompt.ts +20 -1
- package/src/app.ts +184 -11
- package/src/chat/renderer.ts +106 -24
- package/src/components/footer.ts +1 -1
- package/src/keys/handler.ts +8 -0
- package/src/platform/auth.ts +3 -3
- package/src/platform/profile.ts +202 -0
- package/src/platform/session-sync.ts +3 -3
- package/src/platform/supabase.ts +10 -9
- package/src/platform/sync.ts +9 -1
- package/src/research/apis.ts +13 -13
- package/src/research/scanner.ts +212 -0
- package/src/research/tools.ts +58 -0
- package/src/strategy/alerts.ts +190 -0
- package/src/strategy/export.ts +159 -0
- package/src/strategy/health.ts +127 -0
- package/src/strategy/ledger.ts +185 -0
- package/src/strategy/prompts.ts +136 -551
- package/src/strategy/replay.ts +191 -0
- package/src/strategy/tools.ts +495 -1
- package/src/strategy/versioning.ts +168 -0
package/src/strategy/prompts.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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=[
|
|
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)
|
|
433
|
-
hz.discover_events(exchange="polymarket")
|
|
372
|
+
hz.top_markets(exchange="polymarket", limit=20)
|
|
373
|
+
hz.discover_events(exchange="polymarket")
|
|
434
374
|
|
|
435
375
|
## Portfolio Management
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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)
|
|
475
|
-
hz.
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
hz.
|
|
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)
|
|
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)
|
|
494
|
-
hz.
|
|
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)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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")
|
|
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)
|
|
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)
|
|
425
|
+
hz.queue_fill_prob(queue_pos, total_depth, cancel_rate)
|
|
527
426
|
|
|
528
427
|
### Data Preparation (AFML)
|
|
529
|
-
hz.tick_bars(trades, threshold)
|
|
530
|
-
hz.
|
|
531
|
-
hz.
|
|
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)
|
|
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)
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
557
|
+
# TUI output markers (REQUIRED for dashboard parsing):
|
|
700
558
|
m = result.metrics
|
|
701
559
|
print("---BACKTEST_JSON---")
|
|
702
|
-
print(json.dumps({
|
|
703
|
-
"
|
|
704
|
-
"
|
|
705
|
-
"
|
|
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
|
-
#
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
682
|
+
## Dashboard (File-Based — PREFERRED)
|
|
1088
683
|
|
|
1089
|
-
|
|
1090
|
-
- \`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
699
|
+
## Version Control
|
|
1098
700
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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
|
-
|
|
706
|
+
When the user says "undo that" or "go back" → call revert_strategy.
|
|
1105
707
|
|
|
1106
|
-
|
|
708
|
+
## Performance Ledger
|
|
1107
709
|
|
|
1108
|
-
Every
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
-
|
|
1123
|
-
|
|
1124
|
-
-
|
|
1125
|
-
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
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
|
|