Skip to main content
NOVOSKY has three main layers: the MT5 API server (Windows VM inside Docker), the Python trading bot, and external cloud services.

System diagram

Linux VPS (Ubuntu 22.04+)

├── Docker Container (mt5api/)
│   └── QEMU/KVM Windows 11 VM
│       ├── MetaTrader 5 Terminal + Expert Advisor (MQL5)
│       │       │ IPC bridge
│       │       ▼
│       └── Python Flask API  ←─ HTTP :6542 Bearer token

└── trading/bot.py  (main loop, ~60s cycle)

        ├── 1. Market open check (timezone-aware)
        ├── 2. Fetch 250 BTCUSD M15 bars
        ├── 3. ml/feature_engineering.py → 56 features
        ├── 4. Signal model (RF + XGBoost + LightGBM ensemble)
        │       └── Majority vote → BUY / SELL / HOLD
        ├── 5. Filter pipeline
        │       └── ATR floor · news block · EMA trend · circuit breaker
        ├── 6. Risk multiplier model (LightGBM regression)
        │       └── equity-state → multiplier [0.10–1.25]
        │           effective_risk_pct = base_risk_pct × multiplier
        ├── 7. Position sizing (Kelly criterion or dynamic risk%)
        ├── 8. Execute (POST /orders, SL=0.8×ATR, TP=0.8×ATR)
        ├── 9. Position model (77% acc) monitors open trades
        │       └── HOLD / EXIT / ADD
        └── 10. Telegram + Supabase sync

Three-model stack

NOVOSKY uses three ML models that each handle a distinct responsibility:
ModelTypeInputOutputPurpose
SignalRF + XGB + LGB ensemble56 market featuresBUY / SELL / HOLDDecides whether to open a trade
PositionRF + XGB + LGB ensemble56 features + 4 position-stateHOLD / EXIT / ADDManages open positions
RiskLightGBM regression7 equity-state featuresmultiplier [0.10–1.25]Scales lot size based on account health
The models are additive — they never conflict. The risk model only adjusts position size; it never blocks signals or changes SL/TP. If the risk model files are missing (e.g. first run before training), it falls back to multiplier = 1.0 with no effect. All three models are retrained together in the weekly pipeline: python train_ml_model.py --ensemble --position --risk.

External services

ServicePurpose
Cloudflare R2Model binary storage (.pkl, .onnx, .json). Auto-pulled at startup if missing.
Supabase (PostgreSQL)Cloud trade dashboard — bot_status, trades, open_positions, position_events, news_events, account_snapshots, model_metrics.
TelegramReal-time notifications for trades, daily reports, and risk events.
Local trainerGPU-first local retraining with automatic CPU fallback.

ML training pipeline

datasets/training_data_btcusd.csv  (70,031 bars, 2yr BTCUSD M15)

    ▼  ml/feature_engineering.py → 56 features + ATR-aware labels
    │   BUY if long TP (1.5×ATR) hit first  ─┐
    │   SELL if short TP hit first           ├── lookahead 48 bars
    │   HOLD otherwise (+ news override)    ─┘

ml/ensemble_trainer.py + ml/position_trainer.py  (always together)
    │   Optuna 50 trials · 5-split walk-forward validation

ml/risk_trainer.py  (runs OOS backtest → generates equity-curve training data)
    │   7 equity-state features → optimal risk multiplier labels
    │   LightGBM regression · ~300 estimators

models/  →  ml/r2_hub.py --push  →  Cloudflare R2 (tag vYYYYMMDD)

    ▼  backtest_config.py --oos-only
Validate OOS · update strategy_params.json

Repository layout

trading/
  bot.py              # Main bot — all execution logic
  logger.py           # Structured CSV logging
  supabase.py         # Supabase cloud sync

backtest/
  run.py              # Config-faithful backtester (mirrors bot.py bar-by-bar)

ml/
  feature_engineering.py  # All 56 signal features
  ensemble_trainer.py     # Train RF+XGB+LGB signal models
  ensemble_predictor.py   # ONNX inference: BUY/SELL/HOLD + confidence
  position_trainer.py     # Train position management model
  position_predictor.py   # ONNX inference: HOLD/EXIT/ADD
  risk_trainer.py         # Train risk multiplier model (LGB regression)
  risk_predictor.py       # Risk inference: equity-state → multiplier [0.10–1.25]
  shap_analysis.py        # SHAP feature importance
  r2_hub.py               # Cloudflare R2 push/pull/tag
  tune/
    hyperparams.py        # Optuna signal model tuning
    position.py           # Optuna position model tuning

models/
  ensemble_btcusd-live_metadata.json  # Feature names, accuracy, train_cutoff_date
  model_compat.json                   # Feature compatibility manifest (hash + count)
  position_metadata.json
  risk_metadata.json                  # Risk model metadata + feature list
  # Binaries (.pkl, .onnx, .txt) are on Cloudflare R2, not in git

scripts/
  onboarding.py           # First-time setup wizard (run once after installation)
  run_agent.sh            # Claude AI agent runner
  optimize_loop.py        # Full ML optimization: SHAP→tune→retrain→evaluate
  sweep.py                # Unified sweeps: signal, position, or both

config.json     # Runtime config (strategy, SL/TP, risk, filters, risk_model.enabled)
ml_config.json  # ML config (56 features list, labeling, model paths, risk_model block)
Entry-point shims:
  • trading.pytrading/bot.py
  • backtest_config.pybacktest/run.py
  • train_ml_model.pyml/train.py

Key design decisions

DecisionReason
ONNX inference at runtime10–50× faster than sklearn/xgb/lgb native predict
No PyTorch / deep learningHardware focus: Xeon Platinum, sklearn/XGBoost/LightGBM stack is sufficient
No MT5 Python packageSelf-hosted REST API avoids Windows dependency on the trading host
Warmstart training by defaultIncremental training preserves accumulated model knowledge
model_compat.json as source of truthSingle manifest to detect feature mismatches across all components
All config in JSON, not hardcodedRuntime-swappable without retraining or redeployment
Risk model additive, never blockingSeparates sizing decisions from signal decisions — no model conflicts
Risk model fallback = 1.0Graceful degradation: system works correctly before the risk model is trained