src.trading.journal

🦉 AthenaAI is an AI-powered ensemble machine-learning trading bot for PocketOption, built on BinaryOptionsToolsV2Get it on GitLab
💬 Need help? Join the Chipa Discord · ⚡ Go no-code with ChipaEditor

Source: src/trading/journal.py. TradeJournal persists every trade (with its feature vector) and periodic performance snapshots to SQLite. It is what lets the bot retrain its models from history after a restart (see _reload_from_journal() in bot.md).

class TradeJournal

__init__(self, db_path: str)

ParameterTypeMeaning
db_pathstrSQLite file path; the bot passes BotConfig.db_path (default trade_journal.db)

Opens sqlite3.connect(db_path) (creating the file if missing) and runs _init_db(). Note: the connection is created on the calling thread and never closed explicitly; SQLite’s default same-thread check applies if you share the object across threads.

Schema (_init_db)

Two tables, created with CREATE TABLE IF NOT EXISTS:

CREATE TABLE IF NOT EXISTS trades (
    id          TEXT PRIMARY KEY,   -- PocketOption trade id
    direction   TEXT,               -- "call" / "put"
    asset       TEXT,
    stake       REAL,               -- $
    confidence  REAL,               -- fraction 0-1
    regime      TEXT,               -- regime string value at entry
    entry_time  REAL,               -- unix epoch seconds
    exit_time   REAL,               -- NULL until resolved
    result      TEXT,               -- "win"/"loss"/"draw", NULL while pending
    profit      REAL,               -- $, NULL while pending
    features    TEXT,               -- feature vector as a JSON list
    expiry      INTEGER             -- expiry in seconds
);

CREATE TABLE IF NOT EXISTS model_snapshots (
    ts           REAL,     -- unix epoch seconds
    win_rate     REAL,     -- fraction 0-1
    total_trades INTEGER,
    daily_pnl    REAL,     -- $
    regime       TEXT
);

Migration: older databases predate the features and expiry columns, so _init_db also attempts, for each of them:

ALTER TABLE trades ADD COLUMN features TEXT;
ALTER TABLE trades ADD COLUMN expiry INTEGER;

Each ALTER is wrapped in a bare try/except: pass — the “duplicate column” error on an already-migrated DB is silently ignored. The method commits at the end.

save_trade(self, t: TradeRecord) -> None

Runs INSERT OR REPLACE INTO trades VALUES (?,?,?,?,?,?,?,?,?,?,?,?) with, in column order: t.id, t.direction, t.asset, t.stake, t.confidence, t.regime, t.entry_time, t.exit_time, t.result, t.profit, t.features_json, t.expiry, then commits. Because of OR REPLACE on the primary key, the bot calls this twice per trade: once at placement (result columns NULL) and again on resolution — the second call overwrites the row. Note the bot sets features_json after the first save, so the pending row has features = NULL until resolution.

load_completed_trades(self) -> list[dict]

Selects direction, result, features from trades where result IN ('win','loss') AND features IS NOT NULL, ordered by entry_time ASC. Returns a list of {"direction", "result", "features_json"} dicts.

Caveat for extenders: _reload_from_journal() in src/bot.py also reads t.get("regime"), t.get("confidence"), and t.get("entry_time") from these dicts to feed the adaptive strategy — those keys are not returned by this query, so the adaptive reload always uses the fallback defaults ("ranging", 0.6, hour 12).

save_snapshot(self, win_rate, total, daily_pnl, regime) -> None

Inserts (time.time(), win_rate, total, daily_pnl, regime) into model_snapshots and commits. The bot calls it every 10 resolved trades.

recent_win_rate(self, n: int = 50) -> float

Win fraction over the most recent n trades with a non-NULL result (ordered by entry_time DESC). Returns 0.5 when there are no rows. (Currently not called by the bot.)

total_trades(self) -> int

SELECT COUNT(*) FROM trades — includes pending/unresolved rows.

Side effects and failure behavior

Every write method commits immediately. SQLite errors (locked DB, disk full) propagate as sqlite3 exceptions to the caller; the bot’s loops catch and log them at the loop level.

Usage example

from src.trading.journal import TradeJournal
from src.utils.candle import TradeRecord

j = TradeJournal("trade_journal.db")
j.save_trade(TradeRecord(id="1", direction="call", asset="EURUSD",
                         stake=25.0, confidence=0.66, regime="ranging",
                         entry_time=1720569660.0, expiry=300))
print(j.total_trades(), j.recent_win_rate(50))

Inspect from the shell:

sqlite3 trade_journal.db "SELECT direction, result, profit FROM trades ORDER BY entry_time DESC LIMIT 10;"

See also

💬 Unsure how this interacts with the rest of the configuration? Ask in the Chipa Discord — or prototype strategies no-code with ChipaEditor.