NOVOSKY runs a second ML model on every open position every cycle. This is the position model — it decides whether to hold, exit early, or add to a position.
How it works
The position model takes 60 features as input:
- 56 market features (the same as the signal model)
- 4 position-state features appended after the market features
| Feature | Description |
|---|
bars_held_norm | Normalized number of bars the position has been open |
pnl_pct | Unrealized P/L as a percentage of entry price (signed) |
r_multiple | Unrealized P/L in units of SL distance |
pos_direction | +1.0 for BUY, −1.0 for SELL |
Three-class output
| Class | Meaning | What happens |
|---|
HOLD | Stay in the trade | Nothing |
EXIT | Close now | Position closed early before SL/TP |
ADD | Pyramid | Logged only — auto-add is disabled for risk management |
Model accuracy
| Model | Accuracy |
|---|
| Random Forest | 72.6% |
| XGBoost | 76.9% |
| LightGBM | 78.4% |
| Ensemble | 77.16% |
EXIT precision: 85% — EXIT recall: 82%
Inference thresholds
These are configured in ml_config.json → position_model.prediction:
| Key | Default | Description |
|---|
exit_threshold | 0.80 | Min EXIT probability to close early. |
min_prob_diff | 0.25 | EXIT must beat HOLD by this margin. |
add_threshold | 0.65 | Min ADD probability to pyramid. |
These thresholds were tuned on 2026-04-15 via a 13-config sweep (python scripts/sweep.py --target pos --full). At the defaults above, the model fires ~1 exit per 38 days — only on extreme EXIT confidence — which preserves TP-bound winners. Historical short-window validation reached PF 4.71; recent weekly OOS snapshots are lower in different market regimes (for example PF 2.43), but still benefit from conservative EXIT firing.
Kelly lot sizing
At average confidence of 61.5%, the Kelly criterion cuts effective risk from 6% to 2.1%.
For growth accounts: Phase 8 Kelly OFF + fixed risk_percent outperforms Phase 8 ON at every tested level. Enable Phase 8 (ml_active_management.enabled = true) only for capital preservation mode.
Trailing stop and partial close
Both features are effectively inactive at the symmetric 1:1 SL/TP ratio (0.8×ATR):
- Trailing stop activates after the position is already at TP
- Partial close halves a winner with no added upside
Only re-enable these if tp_atr_multiplier is widened to ≥1.5.
Enabling and disabling
// config.json
{
"ml_active_management": {
"enabled": true
}
}
When disabled, the bot falls back to signal-flip detection for position management (closes on opposite signal).
Training
Developer/operator lane only. Regular users should run onboarding, select profile 1-5, pull approved model revisions from R2, and run trading.
The signal model and position model are always trained together — they share the same StandardScaler. Training one without the other breaks scaler dimensions and causes wrong predictions or crashes at inference.
# Always train both together
python train_ml_model.py --ensemble --position