src.trading.journal
🦉 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
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)
| Parameter | Type | Meaning |
|---|---|---|
db_path | str | SQLite 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
- Persistence guide
- db_path config field
- candle.md — the
TradeRecordfields
💬 Unsure how this interacts with the rest of the configuration? Ask in the Chipa Discord — or prototype strategies no-code with ChipaEditor.