Architecture

πŸ¦‰ AthenaAI is an AI-powered ensemble machine-learning trading bot for PocketOption, built on BinaryOptionsToolsV2 β†’ Get it on GitLab
πŸ’¬ Need help? Join the Chipa Discord Β· ⚑ Go no-code with ChipaEditor

AthenaAI (src/bot.py, class AITradingBot) is an asyncio application built around three concurrent loops that share in-memory state: a candle stream feeding a deque of recent candles, a decision loop that turns candles into trades, and a result checker that resolves trades and feeds outcomes back into the models.

Component diagram

                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                       β”‚   PocketOption (websocket)    β”‚
                       β”‚   via BinaryOptionsToolsV2    β”‚
                       β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              candle stream   β”‚               β”‚  buy/sell, check_win
                              β–Ό               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        AITradingBot (src/bot.py)                   β”‚
β”‚                                                                    β”‚
β”‚  _candle_stream ──► candles: deque(maxlen=lookback)                β”‚
β”‚                            β”‚                                       β”‚
β”‚  _trade_loop:              β–Ό                                       β”‚
β”‚   β”Œ FeatureEngine ──── feature vector (core + experimental)        β”‚
β”‚   β”œ RegimeDetector ─── trending / ranging / volatile               β”‚
β”‚   β”œ EnsemblePredictor─ direction + confidence                      β”‚
β”‚   β”‚    (SGD, PA, NB  +  GBM, RF, optional MLP)                     β”‚
β”‚   β”œ Signal gates ───── conf band, alignment, confirmations, …      β”‚
β”‚   β”œ AdaptiveStrategy ─ blocked regimes/hours, conf adj, cooldown   β”‚
β”‚   β”œ MoneyManager ───── Kelly stake, daily loss limit               β”‚
β”‚   β”” ExpirySelector ─── best expiry from expiry_options             β”‚
β”‚                            β”‚  buy()/sell()                        β”‚
β”‚  _result_checker:          β–Ό                                       β”‚
β”‚   check_win ──► PerformanceTracker, MoneyManager, TradeJournal,    β”‚
β”‚                 AdaptiveStrategy, ExpirySelector, FeatureLab,      β”‚
β”‚                 EnsemblePredictor (online learning)                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                        β”‚                       β”‚
        β–Ό                        β–Ό                       β–Ό
  athena_brain.pkl     athena_brain_expiry.pkl    trade_journal.db
  (ensemble state)     (expiry win stats)         (SQLite journal)

Key modules:

ModuleRole
src/core/feature_engine.pyTurns a candle window into a numeric feature vector (core + experimental features) β€” see Feature engine
src/core/models.pyEnsemblePredictor: online + batch models, voting, persistence β€” see Ensemble
src/core/regime.pyMarket regime classification β€” see Regime detection
src/core/expiry.pyExpirySelector: per-expiry win statistics β€” see Expiry selection
src/trading/strategy.pyAdaptiveStrategy: self-review every 25 trades β€” see Adaptive strategy
src/trading/money_manager.pyKelly staking + daily loss limit β€” see Risk management
src/trading/journal.pySQLite trade journal β€” see Persistence
src/trading/feature_lab.pyTracks experimental feature usefulness β€” see Feature lab

Startup sequence

bot.start() runs, in order:

  1. Banner β€” prints asset, timeframe, expiry options, model roster, confidence band, trading hours, feature counts.
  2. Brain load or retrain β€” if PO_RETRAIN=1, the saved brain file is deleted and retraining is forced. Otherwise _load_brain() tries brain_path; on success it also loads <brain>_expiry.pkl (expiry stats) and logs βœ… Loaded saved brain β€” skipping dataset training!.
  3. Fallback if no brain:
    • _reload_from_journal() β€” replays completed trades from trade_journal.db into the ensemble, restores win/loss stats, and feeds the adaptive strategy.
    • _load_dataset(dataset_path) β€” if PO_DATASET is set, pre-trains on the CSV with a 70/30 walk-forward split, then saves the brain.
  4. Connect β€” creates PocketOptionAsync(ssid=...), waits 3 seconds for the websocket handshake, logs Connected! Balance: $X.XX.
  5. Warmup candles β€” fetches history via get_candles(asset, timeframe, offset_seconds). The offset is seconds of history (effective_warmup Γ— timeframe), and warmup is floored at 60 candles because features need at least 50 (SMA50).
  6. Run β€” asyncio.gather(_candle_stream(), _trade_loop(), _result_checker()).

The three concurrent loops

1. _candle_stream β€” market data in

Prefers subscribe_symbol_time_aligned(asset, timedelta(seconds=timeframe)), which delivers completed candles at interval boundaries, so features are always computed on closed candles rather than noisy in-progress ticks. If the installed BinaryOptionsToolsV2 version lacks it, it falls back to subscribe_symbol(asset, timeframe). Each incoming candle is appended to the deque if its timestamp is newer; a candle with the same timestamp replaces the last entry (same candle updating). On stream error the loop logs Candle stream error: ... and sets _running = False, stopping the whole bot.

2. _trade_loop β€” decisions

Ticks every poll_interval (2 s default) and walks a sequence of gates; the first failing gate skips the tick. A ⏸ <reason> diagnostic is logged at most every 30 seconds. Gate order:

  1. Warmup (< max(warmup_candles, 60) candles)
  2. Max concurrent trades (max_concurrent_trades, default 1)
  3. Minimum wait between trades (min_wait_between_trades, default 60 s)
  4. Daily loss limit (MoneyManager.can_trade())
  5. Consecutive-loss cooldown window
  6. Trading-hours filter (UTC hour must be in trading_hours)
  7. Adaptive extra cooldown (cold-streak penalty from AdaptiveStrategy)
  8. Feature computation (skip if None)
  9. Volatile-regime skip (if skip_volatile_regime, off by default)
  10. Confidence band: min_confidence ≀ conf ≀ max_confidence
  11. Low-range candle skip (candle range < 0.00005, ~0.5 pips on EURUSD)
  12. Indicator alignment (2-of-3 of RSI / MACD histogram / SMA cross must agree)
  13. Signal confirmation (signal_confirmations consecutive candles agreeing; default 1)
  14. 5-minute multi-timeframe trend confirmation (SMA5 vs SMA20 on every-5th-candle series)
  15. Adaptive strategy gate (blocked regimes/hours, direction preference, adjusted threshold)

If everything passes: MoneyManager.compute_stake() sizes the trade, ExpirySelector.select() picks the expiry, the bot logs β–Ά TRADE ... and calls client.buy() or client.sell(). The trade is stored in pending_trades, journaled immediately, and its feature vector kept on the record for later learning. Gate details: Signal gates.

3. _result_checker β€” outcomes and learning

Every 3 seconds it scans pending_trades:

  • A trade is only checked once elapsed β‰₯ expiry + 2 seconds β€” the 2 s pads for broker settlement.
  • check_win(trade_id) responses are parsed defensively: dict (result/status keys) or string, lowercased; if the parsed value isn’t win/loss/draw, it falls back to substring matching ("win", "loss"/"lose", "draw") anywhere in the raw response, else retries next cycle.
  • On a resolved trade: profit is computed (win = stake Γ— 0.85, loss = -stake, draw = 0), then fed to the performance tracker, money manager, journal, adaptive strategy, and expiry selector, and a βœ… WIN / ❌ LOSS / βž– DRAW line is logged.
  • Online learning: the stored features are labeled (win β†’ predicted direction was correct; loss β†’ the opposite direction), added to the ensemble, partial_fit runs every retrain_every (10) new results, and batch models (GBM/RF/MLP) retrain on a sliding window of the last 500 samples once at least 200 are buffered (every 100 thereafter).
  • Every 10 completed trades: a performance snapshot row is written and the brain autosaved.
  • Stale abandonment: if check_win keeps erroring and the trade is older than expiry Γ— 5, it is dropped with Abandoning stale trade <id> after timeout (never resolved, never learned from).

Data flow summary

candles β†’ FeatureEngine.compute β†’ nan_to_num β†’ RegimeDetector
       β†’ EnsemblePredictor.predict β†’ (direction, confidence)
       β†’ gates β†’ MoneyManager.compute_stake β†’ ExpirySelector.select
       β†’ buy/sell β†’ pending_trades + journal
       β†’ check_win β†’ profit β†’ perf/money/journal/adaptive/expiry stats
       β†’ label = win ? direction : 1 - direction β†’ ensemble.add_sample
       β†’ partial_fit every 10 results; batch retrain on last 500 samples

File artifacts

FileWritten byContents
athena_brain.pkl (PO_BRAIN_PATH)trainer, pre-training, autosave every 10 trades, shutdownPickled ensemble: scaler, online models, batch models (incl. optional MLP), fitted flags
athena_brain_expiry.pkl (brain_path with .pkl β†’ _expiry.pkl)alongside every brain saveExpirySelector per-expiry win/loss statistics
trade_journal.db (BotConfig.db_path)continuouslySQLite: trades and model_snapshots tables

Full details and how to reset each: Persistence.

Shutdown

Ctrl+C raises KeyboardInterrupt in main.py, which calls bot.stop(): sets _running = False, saves the brain and expiry stats, and logs Bot stopped. Final stats: ....

See also

πŸ’¬ Questions about running AthenaAI? Ask in the #trading-bots channel on the Chipa Discord β€” and if you’d rather design strategies without touching Python, try ChipaEditor.