Persistence

πŸ¦‰ 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 keeps three artifacts on disk so learned state and trade history survive restarts. All paths are relative to the working directory unless configured otherwise.

FileDefault pathConfigured by
Brain (model state)athena_brain.pklPO_BRAIN_PATH / --out (note: BotConfig’s own default is athena_po_brain.pkl if you bypass main.py)
Expiry stats<brain>_expiry.pkl (e.g. athena_brain_expiry.pkl)derived: brain_path.replace(".pkl", "_expiry.pkl")
Trade journaltrade_journal.dbBotConfig.db_path (config-only)

The brain pickle

Written by EnsemblePredictor.save_brain() (src/core/models.py) as a single pickled dict:

KeyContents
scalerThe fitted StandardScaler (its n_features_in_ defines the expected feature dimension)
modelsOnline models as (name, classifier, accuracy_ema) tuples: SGD, PA, NB
batch_modelsBatch models as the same tuples: GBM, RF, and β€” if trained via train.py with PyTorch β€” MLP (a TorchMLPClassifier whose weights are stored as a CPU state dict; see GPU training)
batch_fittedWhether batch models are trained
fittedWhether the ensemble is usable at all

save_brain refuses to write an unfitted ensemble (No trained models to save.). On load, saved online models replace the fresh ones positionally, and the batch-model list is rebuilt entirely from the file β€” which is how a trainer-produced MLP travels into the live bot. A failed load logs Failed to load brain: ... and returns False, sending the bot down the journal-reload + dataset path.

When it is written:

  • End of every train.py run (the output directory is created if missing).
  • After in-process dataset pre-training in the bot.
  • Automatically every 10 completed trades during live trading.
  • On clean shutdown (Ctrl+C β†’ bot.stop()).

The expiry stats pickle (<brain>_expiry.pkl)

Whenever the bot saves the brain, _save_brain() in src/bot.py also pickles ExpirySelector.save_state() β€” the per-expiry win/loss counts the AI uses to pick trade durations (Expiry selection) β€” to the brain path with .pkl replaced by _expiry.pkl. On startup it is loaded if present (⏱ Expiry stats loaded: ...); a corrupt file just logs a warning and starts fresh. train.py does not produce this file β€” expiry stats come only from live results.

The trade journal (trade_journal.db)

SQLite database managed by TradeJournal (src/trading/journal.py). Two tables:

trades

ColumnTypeMeaning
idTEXT (primary key)Broker trade ID
directionTEXTcall / put
assetTEXTe.g. EURUSD
stakeREAL$ risked
confidenceREALModel confidence at entry (0–1)
regimeTEXTDetected regime at entry
entry_timeREALUnix epoch seconds
exit_timeREALUnix epoch seconds (NULL until resolved)
resultTEXTwin / loss / draw (NULL while pending)
profitREALRealized $ P&L
featuresTEXTThe entry feature vector as JSON (used for retraining)
expiryINTEGERChosen expiry in seconds

model_snapshots

ColumnTypeMeaning
tsREALSnapshot time (epoch)
win_rateREALOverall win rate at snapshot
total_tradesINTEGERTrades so far
daily_pnlREAL$ P&L for the UTC day
regimeTEXTRegime at snapshot time

Migration: _init_db() runs on every startup with CREATE TABLE IF NOT EXISTS, then attempts ALTER TABLE trades ADD COLUMN for features and expiry, swallowing the error if they already exist β€” so journals from older versions upgrade in place.

When it is written: each trade is inserted (INSERT OR REPLACE) immediately when placed and rewritten when resolved; a model_snapshots row is added every 10 completed trades.

Journal reload on startup

When no brain loads, _reload_from_journal() (src/bot.py) rebuilds model state from history via load_completed_trades(), which selects direction, result, and features for all rows with result IN ('win','loss') and non-NULL features, ordered by entry time. For each trade:

  • Feature-dimension filtering: the first trade’s feature vector sets the expected dimension; any trade whose vector length differs is skipped. Trades matching the current engine’s full dimension also feed the Feature lab.
  • Label derivation: win β†’ the traded direction is the label (callβ†’1, putβ†’0); loss β†’ the opposite direction (1 βˆ’ dir). The samples are fed to the ensemble and partial_fit runs once at the end (πŸ”„ Reloaded N trades from journal β€” models retrained!).
  • Win/loss counts restore the performance tracker (πŸ“Š Restored stats: W:.. L:.. WR:..%), and each trade is replayed into the adaptive strategy. Because the reload query only returns three columns, the adaptive replay uses fallback values for the rest (regime ranging, confidence 0.6, hour 12) β€” so restored adaptive stats are coarser than live ones.
  • Corrupted rows are skipped silently.

Resetting each artifact safely

Stop the bot first in all cases.

GoalActionConsequence
Fresh models, keep historyDelete athena_brain.pkl and <brain>_expiry.pkl (or run once with PO_RETRAIN=1, which deletes the brain but not the expiry file)Next startup retrains from journal + PO_DATASET
Fresh expiry preferences onlyDelete <brain>_expiry.pklExpiry selection restarts from neutral stats
Full factory resetDelete brain, expiry pickle, and trade_journal.dbAll learning and trade history gone; only the dataset pre-train remains
Keep everything, new machineCopy all three filesBrains are portable, including GPU-trained ones (GPU training)

Never delete files while the bot runs: the journal is an open SQLite connection, and an autosave could recreate a half-written brain. Back up by copying the three files while the bot is stopped.

Security note: brain files are Python pickles β€” only load brain files you created yourself.

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.