Invent a formula over EnsoTrade's data, and get back whether it actually predicts
forward returns — validated on a holdout split, not just fit to the whole window.
`formula` is a math expression combining any of the fields listed in fetch_series'
docstring (for the same `timeframe`) with +, -, *, /, **, %, unary +/-, and abs/min/max/
sqrt/log/log1p/exp/sign/clip/mean/std, e.g. "ofi1 * vpin - dofi / 2" or
"sign(qi) * sqrt(abs(obi))". No other Python is executed — this runs through a restricted,
default-deny expression evaluator, not eval().
`timeframe="scalp"` (default, WDE order-flow, second-scale): `horizon` is which forward
return to correlate against — ret_1s_bp, ret_5s_bp, ret_30s_bp, or ret_60s_bp. `hours` max
720 (30 days).
`timeframe` = "15m"/"1h"/"4h"/"1d" for day/swing strategies (real OKX candles, always
available): use `horizon_bars` instead of `horizon` — the forward % return N candles
ahead (e.g. horizon_bars=4 on timeframe="1h" = predicting the move 4 hours out). `hours`
max ~1500 bars worth; a small `hours` still fetches at least 150 bars (the minimum needed
for a meaningful 70/30 split) rather than failing outright, so the actual window tested
can be wider than requested for a small `hours` value.
Either mode needs enough rows that a 70/30 split leaves >=150 total. Returns train (first
70% chronologically) and holdout (untouched final 30%) Spearman/Pearson correlations plus
a verdict: 'validated' only if holdout |spearman| >= 0.15 AND same-signed as train — this
guards against keeping a formula that only looked good by chance on one slice of data.
ALSO returns, computed on the holdout portion only:
- `net`: risk-adjusted performance AFTER trading costs — sharpe, sortino,
max_drawdown_pct, calmar, ann_return_pct, ann_volatility_pct, win_rate_pct,
profit_factor. Sharpe is annualized and corrected for overlapping horizons
(a horizon spanning N bars sampled every bar is subsampled to non-overlapping
periods first, which removes the ~sqrt(N) inflation naive Sharpe would show).
- `gross`: the same metrics before costs, so the cost drag is visible.
- `costs`: fee/slippage assumptions, position_changes (turnover), total_cost_pct.
Costs are charged on position CHANGES only, not per bar — holding one side is
cheap, flipping every bar is not.
- `cost_verdict`: survives_costs / marginal_after_costs / killed_by_costs / unknown.
IMPORTANT: `verdict` is a correlation test and says nothing about profitability;
a formula can be 'validated' and still be killed_by_costs. Check both.
- `walk_forward`: the same formula re-scored on 5 consecutive time blocks, with
consistency_pct (share of blocks agreeing on direction) and a `stable` flag.
An edge that passes one holdout but flips sign between blocks is usually noise.
`fee_bp`/`slippage_bp` are per side, defaulting to 5bp taker + 2bp slippage; raise
them for illiquid coins or a worse fee tier. Iterate: call this repeatedly with
different formulas, keep what validates AND survives costs, discard what doesn't.
Requires an EnsoTrade Pro API key.