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.
- package/package.json +1 -1
- package/src/ai/client.ts +13 -6
- package/src/ai/system-prompt.ts +1 -1
- package/src/app.ts +47 -16
- package/src/chat/renderer.ts +106 -24
- package/src/components/code-panel.ts +15 -8
- package/src/platform/auth.ts +3 -3
- 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 +70 -25
- package/src/research/widgets.ts +34 -30
- package/src/strategy/prompts.ts +80 -557
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
|
/**
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
:
|
|
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
|
|