NOVOSKY uses 56 features for the signal model and 60 for the position model (56 + 4 position-state features).
Feature order is sacred. The StandardScaler and all models require this exact column order as defined in ml_config.json → "features". Never reorder without retraining.
Protected features
These features show near-zero SHAP importance at training time because the Forex Factory calendar only covers the current week — 99.9% of the 2-year training history sees zeros. At inference time, trading.py overwrites them with live values every cycle.
Never drop these based on SHAP analysis:
is_news_near
news_minutes_away
news_count_today
is_news_risk_window
is_london_session
is_ny_session
is_asian_session
Phase 15 SHAP top features
Top features by SHAP importance at the current phase: h4_rsi_norm, h1_rsi_norm, price_vs_ema50, m30_trend, price_vs_ema200
Full feature reference
| # | Feature | Category | What it encodes |
|---|
| 1 | bb_upper | Volatility | Bollinger Band upper level |
| 2 | bb_lower | Volatility | Bollinger Band lower level |
| 3 | range_position | Price action | Price position in 24h range |
| 4 | price_vs_ema200 | Trend | Long-term trend bias |
| 5 | atr_14 | Volatility | Volatility magnitude in $ |
| 6 | drawdown_pct | Price action | Drawdown from recent high |
| 7 | bb_width | Volatility | Bollinger Band width (volatility regime) |
| 8 | trend_strength | Trend | ADX-like directional strength |
| 9 | macd_signal | Momentum | MACD signal line |
| 10 | macd_line | Momentum | MACD line |
| 11 | volume_ratio | Volume | Volume vs 20-bar average |
| 12 | price_vs_ema50 | Trend | Medium-term EMA position |
| 13 | price_vs_ema20 | Trend | Short-term EMA position |
| 14 | rsi_14 | Momentum | RSI overbought/oversold |
| 15 | hourly_return | Price action | 1-bar return |
| 16 | daily_range_position | Price action | 24h range position |
| 17 | ema_stack | Trend | EMA20/50/200 alignment (−1/0/+1) |
| 18 | candle_direction | Price action | Body/ATR signed ratio |
| 19 | volume_delta | Volume | Bull vs bear volume imbalance |
| 20 | rsi_slope | Momentum | RSI momentum over 5 candles |
| 21 | consecutive_direction | Price action | Consecutive same-direction candles |
| 22 | session_hour_sin | Time | Cyclical time of day (sin) |
| 23 | session_hour_cos | Time | Cyclical time of day (cos) |
| 24 | is_london_session | Session | 1 during London session — protected |
| 25 | is_ny_session | Session | 1 during New York session — protected |
| 26 | atr_percentile | Volatility | ATR rank in 30-day window |
| 27 | volume_surge | Volume | Volume spike capped at 5× |
| 28 | dist_to_round_number | Price action | Distance to nearest $1,000 BTC level |
| 29 | near_daily_high_low | Price action | Near 24h extreme (−1/0/+1) |
| 30 | h4_ema_bias | Multi-timeframe | H4 EMA stack direction |
| 31 | h4_rsi_norm | Multi-timeframe | H4 RSI14 normalized [−1, +1] |
| 32 | h4_macd_dir | Multi-timeframe | H4 MACD direction |
| 33 | d1_trend | Multi-timeframe | D1 price vs EMA20 |
| 34 | price_vs_d1_open | Multi-timeframe | H1 close vs daily open |
| 35 | adx_14 | Trend | Wilder’s ADX normalized 0–1 (not 0–100) |
| 36 | bb_squeeze | Volatility | BB width at rolling 20-period low |
| 37 | price_acceleration | Momentum | 2nd derivative of price |
| 38 | market_quality | Composite | ATR_pct × trend_strength |
| 39 | momentum_decay | Momentum | RSI/price divergence |
| 40 | adverse_candle_ratio | Price action | 5-bar choppiness (0=trend, 1=chop) |
| 41 | is_news_near | News | 1 if high-impact FF event within ±30 min — protected |
| 42 | news_minutes_away | News | Minutes to next event (999 = none) — protected |
| 43 | news_count_today | News | High-impact event count today (UTC) — protected |
| 44 | day_of_week_sin | Time | Cyclical day-of-week (sin) |
| 45 | day_of_week_cos | Time | Cyclical day-of-week (cos) |
| 46 | is_news_risk_window | News | Wed/Fri FOMC/NFP proxy — protected |
| 47 | is_asian_session | Session | 1 during 00:00–09:00 UTC — protected |
| 48 | spread_norm | Market | Broker spread as fraction of price |
| 49 | m30_trend | Multi-timeframe | M30 price vs M30 EMA20 (−1/0/+1) |
| 50 | m30_rsi_norm | Multi-timeframe | M30 RSI14 normalized [−1, +1] |
| 51 | h1_ema_bias | Multi-timeframe | H1 EMA20/50 stack |
| 52 | h1_rsi_norm | Multi-timeframe | H1 RSI14 normalized [−1, +1] |
| 53 | h1_macd_dir | Multi-timeframe | H1 MACD direction |
| 54 | news_surprise | News | News impact vs expectation |
| 55 | bars_since_news | News | Bars elapsed since last FF event |
ADX normalization
adx_14 is normalized to 0–1, not the traditional 0–100 scale. Any ADX-related filter must use the normalized scale:
"adx_regime_filter": {
"min_adx": 0.20 // = traditional ADX 20. Passing 20 blocks ALL trades.
}
Position model features (+4)
The position model takes the 56 features above plus these 4 position-state features:
| Feature | Description |
|---|
bars_held_norm | Normalized number of M15 bars the position has been open |
pnl_pct | Unrealized P/L as % of entry price (signed: positive = profit) |
r_multiple | Unrealized P/L in units of initial SL distance |
pos_direction | +1.0 for BUY, −1.0 for SELL |
Risk model features (7 equity-state)
The risk multiplier model does not use the 56 signal features. It uses a separate set of 7 equity-state features computed from the live account equity curve. These are passed to RiskPredictor.get_multiplier() each cycle before lot sizing.
Feature order here matches ml/risk_predictor.py RISK_FEATURES and ml_config.json → risk_model.features. These must stay in sync — the Three-File Rule applies.
| # | Feature | What it measures |
|---|
| 1 | equity_ratio | Current equity / starting equity (1.0 = no loss, 0.8 = down 20%) |
| 2 | drawdown_from_peak | (Peak equity − current equity) / peak equity |
| 3 | rolling_wr_10 | Win rate of the last 10 closed trades |
| 4 | rolling_wr_20 | Win rate of the last 20 closed trades |
| 5 | rolling_pf_10 | Profit factor of the last 10 closed trades |
| 6 | consecutive_losses | Current consecutive loss streak (integer) |
| 7 | atr_norm | ATR at entry / entry price (volatility as fraction of price) |
The model output — a multiplier in [0.10, 1.25] — is applied as:
effective_risk_pct = base_risk_pct × multiplier
It never changes SL/TP or confidence thresholds. If the model files are missing, multiplier defaults to 1.0.