DB - ai_journal

abstract

Stores every LLM trading signal decision — the signal type, confidence score, rationale, and indicator snapshot — as an auditable record of AI reasoning before execution.

Table Info

PropertyValue
Table Nameai_journal
SQLAlchemy Modelbackend/db/models.py :: AIJournal
Pydantic Schemabackend/api/routes/accounts.py (referenced via response)
Migration Filealembic/versions/
TimescaleDB HypertableNo
Partition Column

Columns

ColumnSQLAlchemy TypeNullableDefaultDescription
idIntegerNoautoPrimary key
account_idInteger FKNoFK → accounts.id (indexed)
trade_idInteger FKYesnullFK → trades.id (UNIQUE — 1:1 optional link)
symbolString(20)NoTrading pair (indexed)
timeframeString(10)NoAnalysis timeframe (e.g., M15, H1)
signalString(15)NoBUY | SELL | BUY_LIMIT | SELL_LIMIT | HOLD
confidenceFloatNoLLM confidence score (0.0–1.0)
rationaleTextNoFull LLM explanation text
indicators_snapshotTextNoJSON snapshot of indicator values at decision time
llm_providerString(50)NoProvider used: anthropic, openai, gemini, etc.
model_nameString(100)No""Model ID used (e.g., claude-opus-4-7)
created_atDateTime(timezone=True)Nodatetime.now(UTC)Decision timestamp
strategy_idInteger FKYesnullFK → strategies.id (SET NULL on delete)

Constraints & Indexes

NameTypeColumnsPurpose
pk_ai_journalPRIMARY KEYidRow uniqueness
uq_ai_journal_trade_idUNIQUEtrade_idEnforce 1:1 with trades
idx_ai_journal_account_idINDEXaccount_idFilter by account
idx_ai_journal_symbolINDEXsymbolFilter by symbol
fk_ai_journal_accountFOREIGN KEYaccount_id → accounts.idReferential integrity
fk_ai_journal_tradeFOREIGN KEYtrade_id → trades.idOptional link to executed trade
fk_ai_journal_strategyFOREIGN KEYstrategy_id → strategies.idSET NULL on strategy delete

Entity Relationships

SQLAlchemy Model (reference snapshot)

class AIJournal(Base):
    __tablename__ = "ai_journal"
 
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    account_id: Mapped[int] = mapped_column(Integer, ForeignKey("accounts.id"), index=True)
    trade_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("trades.id"), unique=True, nullable=True)
    symbol: Mapped[str] = mapped_column(String(20), index=True)
    timeframe: Mapped[str] = mapped_column(String(10))
    signal: Mapped[str] = mapped_column(String(15))       # BUY | SELL | BUY_LIMIT | SELL_LIMIT | HOLD
    confidence: Mapped[float] = mapped_column(Float)
    rationale: Mapped[str] = mapped_column(Text)
    indicators_snapshot: Mapped[str] = mapped_column(Text)  # JSON string
    llm_provider: Mapped[str] = mapped_column(String(50))
    model_name: Mapped[str] = mapped_column(String(100), default="")
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC))
    strategy_id: Mapped[int | None] = mapped_column(
        Integer, ForeignKey("strategies.id", ondelete="SET NULL"), nullable=True
    )
 
    account: Mapped["Account"] = relationship("Account", back_populates="journal_entries")
    trade: Mapped["Trade | None"] = relationship("Trade", back_populates="journal", uselist=False)
    strategy: Mapped["Strategy | None"] = relationship("Strategy", back_populates="journal_entries_strategy")

Service Layer

LayerFilePurpose
AI Pipelinebackend/ai/vision.pyCreates journal entries from LLM responses
Routerbackend/api/routes/accounts.pyResearch cycle reads journal to count decisions
Routerbackend/api/routes/analytics.pyQueries journal for LLM performance metrics
RoleLink
API EndpointAPI-GET-v1-Analytics
Frontend PagePage - Analytics
Related TableDB - trades
Related TableDB - accounts
Related TableDB - pipeline_runs