Skip to content

Memory-aware algorithmic trading

The same signal gets a different trade because the graph remembers.

Trading systems are stateless. They take the same setup, in the same regime, that already cost them money, and they take it again. We put a graph of every past decision and its outcome inside the entry path, so an order is allowed, resized, or refused on the strength of what actually happened last time.

2–8 ms
recall inside the entry path
0 LLM
on the money path
fail-closed
no memory, no entry
TRIGGERED_BYONINRESULTED_INMADE_BYONSIGNALPAIRREGIMEDECISIONAGENTTRADErecall5 of 5 recalled closed trades lostBLOCK

Built on

  • freqtrade
  • FalkorDB
  • LaserData
  • RocketRide.ai
  • Guild.ai
  • FastAPI
  • Bybit / ccxt

The problem

Stateless bots repeat their mistakes, precisely and at speed.

Every part of a modern trading stack is engineered except its memory. Execution is measured in microseconds; the institutional knowledge of what already failed lives in a Postgres table nobody queries at decision time.

  • 01

    It cannot recall its own trades

    A strategy evaluates the present candle. It has no representation of the fact that it took this exact setup four times this week and closed every one of them red. The loss is in your database; it is not in the decision.

  • 02

    Aggregate statistics flatten the regime

    “RSI cross on BTC” is a weak question. “RSI cross on BTC, high volatility, downtrend, after the last four attempts lost” is the useful one. A backtest average answers the first and hides the second.

  • 03

    Nothing links a decision to what it was thinking

    When the book moves against you, the log gives you a timestamp and a fill. It does not give you the rationale that produced the order, or the evidence that rationale was built on.

  • 04

    Learning happens in the retro, not in the loop

    The lesson from Tuesday reaches the system on Friday, as a parameter a human edited by hand. Between those two points the system keeps paying to relearn it.

The fix is not a better predictor. It is a system that asks one narrow historical question before it commits capital, and is willing to stand down when the answer is bad.

How it works

Six stages. Every one of them inspectable.

What follows is the mechanism as built — the real query, the real thresholds, the real prompt. Nothing here is an illustration of something we intend to write.

  1. 01Inputsticks · funding · OI · sentiment
  2. 02Graph memorydecisions + outcomes, written live
  3. 03Recall3-hop Cypher, worst loss first
  4. 04Decisiongate · analyst · risk manager
  5. 05Executiongoverned, sized, fail-closed
  6. 06Outcomecompounds back into the graph

Stage 01

What goes in

Four streams, all of them live. The system computes a Signal and a Regime from them — and the regime is what makes memory situational rather than a flat average.

InputSourceCadenceUsed for
Price ticksExchange public API via ccxt → LaserData market.ticks2 s, batchedsignal detection, regime tagging
Funding rate + open interestSame venue, same batch60 s refreshfunding_extreme signal, context
Market sentimentFear & Greed index → LaserData market.context10 minsentiment bucket on the regime
Trade eventsEngine webhooks → bridge → LaserData bot.eventsper eventwriting outcomes back into memory

A regime is the volatility bucket plus the trend, optionally carrying the sentiment bucket — high-vol/down for example. “This signal on this pair” is a weak question. “This signal, on this pair, in this market regime” is the one worth asking.

Stage 02

What gets remembered

Every decision and every outcome is written to the graph as it happens. Memory compounds during the session and survives restarts.

memory/schema.py — the decision-rationale graph
(:Signal {type, ts, strength})   -[:ON]->            (:Pair {symbol})(:Signal)                       -[:IN]->            (:Regime {vol_bucket, trend, sentiment})(:Decision {action, rationale,              confidence, vetoed}) -[:TRIGGERED_BY]-> (:Signal)(:Decision)                     -[:MADE_BY]->       (:Agent {name})(:Decision)                     -[:VETOED_BY]->     (:Agent) only when blocked(:Decision)                     -[:RESULTED_IN]->   (:Trade {id, side, size, pnl, status})

The property that matters: a Decision keeps its rationale text and links to the Trade that resulted from it. So memory does not store “we lost money” — it stores what we believed at the time, and what happened next.

Stage 03

The question asked of memory

One parameterised Cypher query, roughly three hops. Worst loss first, so the most cautionary evidence is what the decider sees.

memory/recall.py — the 3-hop recall
MATCH (d:Decision)-[:TRIGGERED_BY]->(prior:Signal {type: $sig})-[:ON]->(p:Pair {symbol: $pair}),      (prior)-[:IN]->(r:Regime {vol_bucket: $vol}),      (d)-[:RESULTED_IN]->(t:Trade)WHERE t.status = 'closed'RETURN t.pnl AS pnl, d.rationale AS rationale, d.confidence AS confidenceORDER BY t.pnl ASCLIMIT $limit

In English: every time this same signal fired on this same pair in this same volatility regime, and we acted on it — how did those trades end, and what were we thinking?

Recall is exact-match on those three keys. No embeddings, no fuzzy similarity. A regime it has never seen returns an empty list, and every consumer is told plainly that an empty list means no memory yet.

Stage 04

The decision — path A, the entry gate

The deterministic path. A strategy subclass overrides exactly one engine hook, confirm_trade_entry(), called after the strategy decides to enter and before the order goes out.

bridge/gate.py — the memory gate
POST /gate — called from confirm_trade_entry(), before the order goes out{ "pair": "BTC/USDT:USDT", "side": "long",  "entry_tag": "rsi_cross_up", "stake_proposed": 950.0 } response — 2–8 ms, deterministic, no model in the path{ "allow": false, "stake_factor": 0.0,  "rationale": "Memory gate BLOCK: 5 of 5 recalled closed trades for                BTC/USDT in high-vol regime (any signal) lost (-459.69 USDT)" }
Recalled closed lossesVerdictEffect
≥ 3 recalled closed lossesblockentry refused
≥ 2 recalled closed lossesresizestake × 0.5
otherwiseallowfull stake
recall unavailable (DB down, timeout)blockfail closed — never a silent allow

The gate writes its own Decision node, so every consult — allow, block, or resize — appears in the graph and on the live feed. The thresholds are configuration, not folklore; we tune them against your book.

Stage 05

The decision — path B, judgment and governance

Off the money path, where latency is affordable, the recalled rows are handed to an analyst agent and then to a risk manager with final authority.

Analyst system prompt, verbatim

You are the Signal Analyst of a governed crypto trading pipeline … You receive one computed market signal and the graph memory of prior closed trades triggered by the same signal type on the same pair in the same volatility regime. Weigh the recalled outcomes heavily: repeated past losses in this regime must lower confidence and size_factor, or flip bias to flat.

The reply is validated against a strict schema. An invalid reply is retried once and then raises — the system never trades on an unparsed verdict.

Risk manager — evaluated in order, first match wins
1session drawdown over the configured limitstop — halt the engine (circuit breaker)
2proposal is flathold, no trade
3recalled losses at the veto threshold, position cap reached, or confidence under the floorveto
4size or confidence outside the governed bandask a human — blocks until answered; timeout is a veto
5recalled losses at the cut thresholdresize
6otherwiseapprove

Execution is mirrored so the gated and ungated legs are dispatched from the same approved decision, and a gate block is a recorded outcome rather than an error. Human approval, when it fires, is blocking; an unanswered prompt is a fail-closed veto.

Stage 06

The outcome compounds back

The fill and the final PnL arrive back through webhooks and become a Trade node linked to the decision that opened it — which is what the next recall will find.

  1. 01A detector fires on the tick batch; anti-churn guards check strength, per-pair cooldown and minimum hold.
  2. 02Recall runs the Cypher above and returns the prior closed trades, worst first, with the rationale each one was opened on.
  3. 03The gate allows, resizes or refuses. Off the money path, the analyst and risk manager reason over the same rows.
  4. 04The decision — action, rationale, confidence, veto edges — is written to the graph and streamed to the live UI.
  5. 05The trade's fill and final PnL return through webhooks and attach to that decision.
  6. 06The next identical signal is judged with more history than the last.

What memory does not do: it does not predict prices, it does not read order books or news, and it only ever makes the system more conservative — block, shrink, or step aside.

Architecture

Architecture

A layered system, not a script with an API key.

Streaming, memory, reasoning, governance and execution are separate layers with typed boundaries between them. That is what makes it portable into an environment we did not build.

Data flow

Every service, and what it is load-bearing for

Market data and sentiment enter through the stream layer; the decision loop recalls from the graph, consults the deciders, and acts on the engine; webhooks carry outcomes back into the graph. Remove the stream layer and the system is blind. Remove the graph and it is amnesiac — and it fails closed rather than silently reverting to amnesia.

Every service, and what it is load-bearing for — architecture diagram
One decision

Recall, verdict, governance branches, execution

The full sequence for a single signal: tick batch to detector, the three-hop recall, the analyst verdict, and the risk manager's branches — veto, human approval, circuit breaker, or an approval whose size the recalled losses have already halved.

Recall, verdict, governance branches, execution — architecture diagram

Engineering posture

  • Fail-closed everywhere

    A missing credential aborts the affected service at startup. An unreachable graph blocks the entry. An unparsed verdict raises. Nothing substitutes synthetic behaviour.

  • Deterministic on the money path

    The entry gate is Cypher plus fixed rules. No model, no network round-trip to a vendor, no human — because it sits inside the live entry path.

  • Contracts before integrations

    Every decider backend — local, cloud pipeline, governed agents, or a chain of them — implements the same typed contracts and the same strict parsing.

  • Auditable by construction

    Every consult writes a node. Seeded history is labelled as seeded. The graph is the audit trail, and it is queryable while the system runs.

What it looks like running

The system, on screen, mid-flight.

These are captures from a live run — the operator UI, the gate verdicts, the graph itself. In a demonstration you drive this yourself, against instruments you choose.

Mission control

The operator surface: memory effect, health, decision trace

Every decision card is read straight from the graph, not from a log file: the signal, the regime, the agent that ruled on it, the rationale it wrote, and whether a trade was linked. The live feed narrates the loop as it runs.

The operator surface: memory effect, health, decision trace
What memory knew

The evidence behind a refusal, one click away

A vetoed card with its drawer open — the recalled prior losses and the rationales they were opened on. Demo-seeded history is labelled seeded, in the graph and in the UI.

The evidence behind a refusal, one click away
Gate verdicts

Block, veto, resize — all three are recorded outcomes

A block on five of five recalled closed losses, a risk-manager veto, and a resize cutting the stake by half. None of them is an error path; each writes its own node.

Block, veto, resize — all three are recorded outcomes
The graph

Decision, Signal, Trade and Agent nodes with their edges

The memory graph rendered live in the database browser. It is not a visualisation built for the demo — it is the store the recall queries, inspectable while the system trades.

Decision, Signal, Trade and Agent nodes with their edges
A/B harness

Two engines, byte-identical strategy code

The gated and ungated bots side by side in one engine UI. The harness exists so the mechanism can be observed under control — see the note below on what the first epoch did and did not measure.

Two engines, byte-identical strategy code
Baseline

The ungated leg

The amnesiac baseline: it takes every entry it is handed, with no consult and no memory of the last time it took the same one.

The ungated leg
Memory-gated

The gated leg

The same strategy with one hook overridden. Its open position here was resized by the gate rather than taken at full stake.

The gated leg
Stack

Technology

Everything here is in the build. None of it is a logo on a slide.

Each layer is replaceable at its boundary — that is the point. In your environment the venue, the engine and the model are yours; the memory layer and its contracts are what we bring.

Execution

  • freqtradeThe engine. A custom strategy subclass overrides one hook, confirm_trade_entry, which is where memory enters the order path.
  • ccxt · Bybit futuresVenue connectivity and market data, running dry-run against real prices with simulated fills.
  • DockerBoth engine legs and the graph run as containers, so an A/B harness is reproducible rather than ceremonial.

Memory

  • FalkorDBThe graph store. Six node types, multi-hop Cypher recall over prior decisions and their closed trades, answered in single-digit milliseconds.

Streaming

  • LaserDataApache Iggy streams carrying market ticks, funding rates, open interest, trade events and market sentiment on separate topics.

Pipeline

  • RocketRide.aiThe decision pipeline executed in cloud, spoken over a DAP WebSocket protocol: authenticate, execute, process.

Governance

  • Guild.aiTwo published TypeScript agents — a signal analyst and a risk manager — with human-in-the-loop approval and a drawdown circuit breaker. A timeout on approval is a veto.

Reasoning

  • OpenAI gpt-5.4-miniThe analyst hop, off the money path. Schema-validated output; an unparsed verdict is never traded on.

Services

  • Python 3.12 · uvRuntime and dependency management, locked and reproducible.
  • FastAPIThe webhook bridge, the SSE live feed, the gate endpoint and the mission-control UI.
  • pydanticThe typed contracts every integration codes against, so a decider backend can be swapped without touching the loop.

Verification

  • pytest160+ tests, run live against real services wherever a live service exists rather than against mocks.
  • PlaywrightBrowser-level checks of the operator surface.
  • MermaidArchitecture diagrams generated from source that lives in the repository and moves when the system does.

What we don’t claim

We audited our own headline number and rewrote it.

We built the A/B harness ourselves: two engines, byte-identical strategy code, one of them consulting the graph. The first run produced a result that looked excellent. We went into the raw trade rows before anyone else could, and it did not survive.

  1. Finding 01

    The trades were ours, not the strategy's

    Every closed trade on the baseline leg was opened by our own decision overlay. Not one organic strategy entry was among them.

  2. Finding 02

    They were churn, not positions

    Median hold: six seconds. Each closed at roughly twice the taker fee. That figure was our overlay paying the exchange — it was not a strategy edge being lost.

  3. Finding 03

    The gated leg was never asked

    The loop force-entered the baseline only, so “no closed losses” on the memory-gated side partly meant it was never handed the trade. The advertised “identical entries, memory is the only variable” was not what that epoch measured.

  4. Finding 04

    The fast pair was measuring nothing

    At that cadence, in a flat market, edge-triggered detectors flip-flopped and the gate had no stable history to learn from. A comparison over that data answers no question worth asking.

What we do stand behind

The loop generated real losing setups. The graph learned them. The gate then refused those same setups, and “five of five recalled closed trades lost” is a true recall over trades that really happened. The mechanism works. The comparison was not a controlled study — so we fixed the harness, not the wording.

No backtested edge. No live profit-and-loss claim. No “outperforms”. This page sells a measured mechanism, the engineering around it, and an offer to run it in front of you. If you want a number, it should be one you watched us produce on your own data, under your controls — not one we printed in a deck.

Built to be verified, not asserted.

Talk to us

We would rather show you than tell you.

If you run a book and you suspect your system is paying twice for the same lesson, we will demonstrate this live — the graph, the recall, and a refusal you can trace to the trades that caused it.

  1. 01

    A working session, not a deck

    We stand the system up and drive it in front of you: signals firing, the recall running, the gate allowing, resizing and refusing, and the graph growing as it does.

  2. 02

    Your setups, your instruments

    Bring the entries you suspect you keep repeating. We seed the graph with your own history — honestly labelled as seeded — and you watch what the gate does with them.

  3. 03

    Then it goes into your environment

    Your engine, your venues, your models, your risk limits. What we bring is the memory layer, the contracts around it, and the discipline of failing closed.

Or write directly

info@kodai.com.pl

A real inbox, read by the people who built the system.

Instruments, venues, and the engine or framework you run today.

Goes to info@kodai.com.pl. No list, no sequence, no CRM.