In brief
- The new Trade Planner reviews one initial Stop Loss and one primary Take Profit for Bybit and OKX only. Entry price, setup acceptance, risk, leverage, quantity, approval state, and exchange execution remain outside its authority.
- The Entry Agent and Trade Planner run concurrently from one frozen market snapshot, so deeper planning does not add their latencies together.
- Every proposal must carry exact technical provenance and pass deterministic checks for direction, tick-size normalization, RR, freshness, and immutable entry before it can reach the existing execution pipeline.
- A user's minimum RR remains a personal execution preference. The Planner has an independent recommendation floor of at least 2R and cannot endorse a 0.5R baseline merely because one user allows it.
- Fallback is atomic but conditional: LiquidMind may keep a complete safe baseline, while active mode rejects the setup before user fan-out when a sub-2R baseline has no valid planner replacement.
- Validated active management levels are executable reassessment triggers: a price touch emits an `opposite_poi_detected` event with exact technical context, never an automatic partial close.
- Three runtime modes make rollout measurable: deterministic makes no planner call, shadow records proposals without applying them, and active may apply only a fully validated plan.
There was a gap in LiquidMind's entry pipeline that became more obvious as the rest of the system got better.
The Entry Agent could answer the high-level question: is this setup worth taking? It had market context, scoring, POI quality, structure, and a clear right to reject the trade.
Deterministic code could answer the mechanical question: where do the initial Stop Loss and Take Profit go? It found the last valid swing, selected a target, used the existing higher-ranking fallback when necessary, and passed those levels into position sizing and exchange validation.
What neither layer did was challenge the plan as a complete technical object.
Is the stop sitting just before an obvious liquidity pool? Is the target technically reachable, or is there a strong opposing structure in front of it? Does moving the stop to a better invalidation point destroy the risk/reward ratio? Is an attractive target actually a real detected level, or just a round number that looks good in a response?
That is the job of the new Trade Planner.
Not to find an entry. Not to decide whether the trade is allowed. Not to choose risk. Not to place an order. Its job is narrower: review the already-detected setup, compare the deterministic SL/TP baseline against current technical evidence, and return either a validated improvement or a reason to keep the original plan.
The difficult part was not asking a model for two prices. The difficult part was building a boundary where the answer can be useful without ever becoming authority by accident.
Why add an agent when deterministic SL/TP already works?
The existing calculation is deliberately conservative. For the stop, it uses the last structurally relevant swing. For the target, it searches for a valid objective and falls back through known alternatives when the ideal level is unavailable. That is a good production baseline because it is repeatable, testable, and available even when every external AI service is down.
But a deterministic function sees exactly what it was written to see.
It can identify a swing without weighing every nearby unswept liquidity pool. It can return a valid target without judging that a fresh opposing Order Block or FVG makes the path unusually difficult. Adding every possible relationship to one procedural function would turn it into a growing tree of special cases — and each new market concept would make the tree harder to reason about.
The planner adds a bounded qualitative review on top:
- Is this the actual invalidation point, or merely the nearest convenient swing?
- Is there meaningful liquidity beyond the baseline stop that the setup is likely to raid first?
- Is the primary target supported by external liquidity, PDH/PDL, EQH/EQL, a session level, an opposing FVG/OB, or another exact detected level?
- What obstacles sit between entry and target?
- After the proposed stop and target are considered together, does the plan still meet the minimum RR?
The deterministic plan is not removed. It becomes the baseline and the fallback.
That distinction shaped the entire implementation: the agent is allowed to review the plan, but only deterministic application code is allowed to resolve it.
The authority boundary
The fastest way to make an agent dangerous is to describe its responsibility with a vague verb like "optimize." Optimize what? RR? Win rate? Distance to liquidity? Margin use? A model can satisfy one of those goals by quietly changing another variable you assumed was fixed.
So the Trade Planner's contract is intentionally small:
| The planner may | The planner may not |
|---|---|
| Keep the deterministic SL and TP | Change the entry price |
| Propose one technically sourced SL | Accept or reject the setup |
| Propose one primary technically sourced TP | Choose risk amount, leverage, or quantity |
| Add future reassessment levels | Place, edit, cancel, or close an order |
| Explain the technical evidence | Change Approval or Autonomous mode |
Return NO_VALID_PLAN |
Turn a reassessment level into a partial TP |
Entry remains immutable because the entry model has already completed by this point. Letting a downstream reviewer move it would invalidate the setup geometry, scoring, price-distance checks, and potentially the user's expected order type.
Sizing remains outside the planner because risk belongs to each user's account, not to a global market opinion. One POI evaluation can fan out into many users with different balances, limits, leverage settings, and autonomy modes. The planner run is global; the final quantity is personal.
And the agent gets read-only tools only. There is no exchange mutation tool hidden behind prompt instructions saying "please don't use it." The capability does not exist in its tool registry.
One snapshot, two agents, one deterministic gate
Once an entry model confirms an exact entry level, LiquidMind first calculates the existing deterministic SL and TP. It then freezes a TradePlanningContext containing the setup, venue, side, timeframes, current market snapshot, price precision, tick size, minimum RR, and the full deterministic baseline.
That same immutable context becomes the reference point for everything that follows:
The Entry Agent owns setup quality. The Trade Planner reviews protection and targeting. Deterministic code resolves the answer before any user-specific execution begins.
There are two important sequencing rules here.
First, the baseline is calculated before the planner runs. The model cannot recreate it from prose or pretend a different value was the original. The exact baseline, its calculation steps, and whether the TP fallback was used are present in the context and later checked against the response.
Second, the existing per-user risk and venue guards run after the plan is resolved. Planner validation is an additional gate, not a replacement for position sizing, exposure limits, margin checks, duplicate-order protection, approval flow, or the final Bybit/OKX order validation.
Parallelism without shared-state guessing
The first version of this idea was sequential in my head: ask the Entry Agent whether the setup is valid, then ask the Trade Planner how to structure it.
That would be clean conceptually and slow operationally. Both agents perform read-heavy reasoning, and neither needs the other's conclusion to start. If Entry takes 18 seconds and planning takes 14, sequential execution turns that into roughly 32 seconds before the normal order pipeline can continue.
So they run concurrently.
Both blocking ReAct calls are moved into worker threads, each with its own timeout and error capture. Both receive the same correlation identity. They do not mutate shared market state, and the planner receives a frozen context rather than rebuilding its own version of the setup halfway through the run.
In simplified form:
entry_task = asyncio.create_task(run_entry_agent())
planner_task = asyncio.create_task(run_trade_planner())
entry_result, planner_result = await asyncio.gather(
entry_task,
planner_task,
return_exceptions=True,
)Concurrency changes latency, not authority.
If the Entry Agent rejects the setup, no planner result can resurrect it. The planner may have completed first and produced a perfectly valid SL/TP pair; it still does not matter, because the answer to "should we trade?" remains no.
The reasoning timeline reflects that semantic order rather than wall-clock order: ENTRY appears before PLAN. The planner can finish first, but it is still downstream in meaning.
Shadow mode needs slightly different supervision. Execution should not wait for a result that cannot be applied anyway, so the planner task may continue in the background while the deterministic entry path proceeds. It is held in a supervised task set, errors are consumed and recorded, and the late result can still be attached to the same POI evaluation. "Fire and forget" would have hidden failures and lost evidence; supervised background work gives us the latency benefit without making the task invisible.
A proposal is not a plan until it proves its provenance
An LLM returning "stop_loss": 64123.45 is not technical analysis. It is a number.
Every proposed SL and TP must identify:
- the exact selected price;
- the matching source level;
- the source type, such as swing, ChoCH origin, Order Block, FVG, liquidity pool, PDH/PDL, EQH/EQL, or session level;
- the timeframe;
- the read-only tool that returned it;
- its relation to the deterministic baseline;
- the technical reason;
- confidence.
The selected price must equal the cited source level within the allowed precision. The source tool must be in the planner's explicit read-only whitelist. A beautifully written explanation cannot rescue a price that has no machine-verifiable origin.
The output itself is a strict Pydantic contract with extra fields forbidden. It contains exactly one primary TP. Additional technical levels are stored only as management triggers with one possible action hint: REASSESS_POSITION.
That last constraint matters. A target at an internal liquidity pool can be useful information without becoming an instruction to close 25% of the position. A validated active target can wake Position Management when price reaches it, but the touch is context for a fresh assessment, not a pre-authorized trading action.
The deterministic validation wall
After schema parsing, the proposal still has to pass the TradePlanValidator. The checks happen in code, using normalized venue prices:
| Validation | What it prevents |
|---|---|
| Finite, positive prices | NaN, infinity, zero, or negative values reaching arithmetic or exchange code |
| Immutable entry | A downstream agent silently changing the trade geometry |
| Baseline provenance | The response misquoting the deterministic SL, TP, or baseline RR |
| Read-only technical provenance | Invented levels or evidence from a tool the planner was never allowed to use |
| Tick-size and precision normalization | A plan that works before rounding but breaks on the actual instrument |
| LONG/SHORT ordering | LONG with SL above entry, SHORT with TP above entry, and equivalent side errors |
| Non-degenerate levels | Stop or target collapsing onto the entry after rounding |
| Recalculated RR | A persuasive but mathematically false proposed_rr |
| Minimum RR | A technically plausible plan that no longer clears the system threshold |
| Snapshot freshness | Applying a plan built from market data that has aged beyond its contract |
| Trigger direction | LONG reassessment targets below entry or SHORT targets above it |
| Deterministic sizing re-check | An invalid quantity when a risk amount is available |
Direction is checked after rounding, not before. That catches an easy-to-miss case: a stop can be microscopically below a LONG entry in model output, then normalize to the exact same tick as the entry. It looked valid in floating-point space and is invalid on the venue.
RR is also recalculated from the final normalized levels:
risk = abs(entry - stop_loss)
reward = abs(take_profit - entry)
RR = reward / riskThe planner's stated RR is checked against this result. Then the result is checked against the Planner's recommendation minimum. The model does not get to certify its own arithmetic or inherit a lower quality bar from one user's settings.
If a risk amount is available at this stage, quantity is recalculated deterministically against the proposed stop distance. But the recalculated quantity is evidence for validation, not a quantity chosen by the planner. The normal user-specific Risk Manager remains authoritative later in the pipeline.
The 0.58R plan that exposed a missing boundary
The first production-shaped failure was more useful than a clean demo.
For one ETHUSDT LONG, the deterministic plan was:
Entry: 1919.07
SL: 1893.46548
TP: 1933.9200439453125
RR: 0.58The Planner saw the same numbers, explained why the stop and target were technically plausible, and returned KEEP_BASELINE. Its prose was coherent. Its recommendation was not.
The user-level minimum RR and the Planner's own recommendation standard had been treated as the same value. That is wrong for a multi-user system. A user may deliberately configure min_rr_ratio=0.5; that says the execution pipeline may consider such a trade for that account. It does not mean a system presented as a technical planner should recommend 0.58R as an attractive plan.
LiquidMind now has two separate boundaries:
| Boundary | Owner | Meaning |
|---|---|---|
User min_rr_ratio |
Individual trading profile | The lowest RR that user's later execution policy permits |
TRADE_PLANNER_MIN_RECOMMENDED_RR |
Global Planner policy | The lowest RR the Planner may recommend; defaults to 2.0 and cannot be configured below 2.0 |
The frozen context now tells the model the exact baseline RR, the independent Planner floor, whether the baseline clears it, the minimum TP required with the existing stop, and the maximum stop distance allowed with the existing target. For a baseline below the floor, KEEP_BASELINE is explicitly forbidden.
Code enforces the same contract after the response. It recalculates the normalized RR, rejects a sub-threshold KEEP_BASELINE, and requires ADJUST to change at least one leg using an exact whitelisted technical source. Prompt guidance makes the right answer easier; deterministic validation makes the wrong answer unusable.
This also changed the meaning of failure. In active mode, if the baseline is below the Planner floor and no valid adjustment exists, the result is not a successful deterministic fallback. It is source=rejected, execution_allowed=false. The POI is invalidated before multi-user fan-out and no order is created.
That is the important distinction: a fallback is only safe when the thing being restored is itself acceptable under the active contract.
Why fallback replaces the whole proposal
Imagine the planner proposes a wider, structurally better stop and a farther target. The stop validates, but the target cites a level that does not exist.
It would be tempting to keep the planner's stop and combine it with the deterministic target. I chose not to.
SL and TP form one risk/reward plan. Changing one changes the meaning of the other. A partial merge creates a combination neither the deterministic algorithm nor the planner actually evaluated. It also makes failures harder to explain: "the stop came from run X, the target came from fallback Y, and the displayed RR came from a third calculation."
Fallback is therefore atomic and conditional:
valid complete proposal -> planner SL + planner primary TP
safe complete baseline -> deterministic SL + deterministic TP
active baseline below Planner floor, no repair -> reject before user executionIn deterministic mode, the Planner is not part of the decision and the existing per-user rules remain authoritative. Shadow mode may observe and reject a proposal without changing execution. Active mode is stricter: the complete baseline is retained on a Planner failure only when that baseline already meets the independent Planner floor.
Subject to that safety condition, the complete baseline is retained when:
- the planner times out;
- the model or tool call raises an exception;
- JSON is malformed or violates the schema;
- the planner returns
NO_VALID_PLAN; - entry, side, RR, freshness, provenance, or normalized price checks fail;
- the planner infrastructure is unavailable;
- the database migration is missing;
- the run or its management triggers cannot be persisted safely.
Redis, Langfuse, Telegram, or planner availability must never corrupt the existing worker. Depending on mode and baseline quality, the deterministic outcome is either continued execution with the complete known plan or a clean pre-execution rejection — never a half-applied planner proposal.
Persistence failure is included deliberately. In active mode, applying a planner decision that cannot be audited would leave the exchange state ahead of the reasoning record. The safer answer is to keep an already acceptable baseline or reject a sub-threshold one.
There is one operational caveat worth stating plainly: a synchronous LLM request running in a worker thread cannot be forcibly killed at the network stack the instant asyncio times out. LiquidMind stops waiting, ignores any late answer, and the agent has read-only capabilities — but the underlying provider call may finish later. Bounded application latency and read-only tools make that acceptable; pretending the thread disappeared would not.
Three modes, because rollout is part of the feature
The planner has three runtime modes:
| Mode | Planner call | Can change SL/TP? | Purpose |
|---|---|---|---|
deterministic |
No | No | Production baseline and immediate rollback |
shadow |
Yes | No | Collect proposals, validation outcomes, latency, and fallback reasons |
active |
Yes | Only after every check passes | Apply a fully validated plan |
The default is deterministic. In this mode the planner is lazily left uninitialized: no hidden LLM request, no billing event, no extra latency.
Shadow is where this feature should earn trust. It records baseline, proposal, normalized result, validation failures, tool provenance, reasoning summary, confidence, and latency while execution still uses the original deterministic levels. That lets us answer the questions that matter before promotion:
- How often does the planner keep the baseline?
- When it adjusts, which technical sources does it use?
- Which validation rules reject the most proposals?
- Does a wider structural stop produce a target and RR that still make sense?
- How often does the planner finish before the Entry Agent?
- What is the latency and cost distribution by symbol and timeframe?
The mode can be changed through an authenticated runtime endpoint backed by Redis, so rollback to deterministic does not require a deployment. Invalid configuration also fails closed to deterministic.
Why one planner run can belong to many executions
LiquidMind's market analysis is global, but execution is multi-user.
One POI evaluation may produce one planner opinion for BTCUSDT and then fan out into several orders: different users, balances, risk limits, leverage settings, and autonomy modes. Storing a separate copy of the same planner reasoning on every order would duplicate data and blur the distinction between market analysis and personal execution.
The new persistence model reflects the actual relationship:
one POI evaluation
-> one trade_planner_run
-> zero or many management targets
-> zero or many user orders
-> zero or many user positionsThe migration is additive and idempotent. Orders and positions receive nullable planner references, so historical records remain valid and old reasoning timelines do not change. The core execution insert also does not depend on the new column being present; planner linkage happens as a best-effort attachment after the existing write. A migration problem cannot roll back an otherwise valid order record.
Each run shares a correlation identity with the entry evaluation, links to the Entry Agent decision when available, and receives any resulting executions — including late attachments needed by shadow-mode races.
From stored landmarks to executable reassessment
The first version persisted management targets but deliberately left them inert. That was useful for validating the contract, but it stopped halfway: the Planner could name the exact opposing level that mattered and the running position would never react when price arrived.
Those targets now have an execution lifecycle:
inactive -> active -> processing -> triggered
\-> error
active ------------------------> cancelledOnly targets from a validation-passed, applied active plan become active. Shadow, fallback, rejected, and historical targets stay inert. A Bybit or OKX worker polls venue prices every five seconds by default and considers a target only when an open managed position is linked to that exact trade_planner_run_id.
On touch, one worker atomically claims active -> processing. The event sent into Position Management follows the existing threat semantics:
opposite_poi_detected_trade_plan_<level>It carries the planned price, actual fired price, technical level type, timeframe, source tool, priority, rationale, expected reaction, venue, POI, and Planner run. Global Sentinel receives a fresh reason to reassess the market; Personal Guard maps that assessment only to positions belonging to the same Planner run and venue.
The trigger does not say "take profit now." It says "the opposing level identified in the original plan has been reached; reassess with this exact context." Any resulting hold, defense, risk reduction, or exit still passes through the existing Autonomous/Approval contract and deterministic exchange guards.
The database migration adds fired price, processing timestamps, result and error audit fields, plus the atomic lifecycle states. This makes retries and concurrent venue workers observable without allowing the same target to fire twice.
What becomes visible
The reasoning timeline now has a PLAN event after ENTRY.
It shows:
- deterministic baseline SL, TP, and RR;
- planner proposal and technical sources;
- normalized final levels;
- whether the plan was
VALIDATED,ADJUSTED,BASELINE KEPT,SHADOW,FALLBACK, orFAILED; - validation errors and warnings;
- management reassessment levels;
- public reasoning summary, tool trace, model, and latency;
- whether the final source was
planner,deterministic,deterministic_fallback, orrejected.
The planner also gets its own Langfuse trace and billing identity. Its tree now mirrors the Entry Agent: Data Preparation, a parent ReAct Loop, one API Call - Iteration N span per iteration, a nested LLM Call - Iteration N, individual tool-call events with arguments, results, duration and failure state, and a final Validation & Resolution span. Structured public reasoning is recorded, but provider-private hidden reasoning is removed before observability. Telegram can broadcast a compact Trade Plan notification — including required RR and ALLOWED or REJECTED execution state — and remains best effort.
This is not just debugging decoration. A planner that sometimes changes price protection must make three states distinguishable:
- what the deterministic system would have done;
- what the agent proposed;
- what the system actually applied.
Without all three, "AI changed my stop" is an anecdote. With all three, it is an auditable decision.
Why Bybit and OKX only
The Trade Planner is integrated into the shared autonomous worker used by Bybit and OKX.
Alpaca has a separate worker and is intentionally outside this implementation. Its capability check always resolves to the existing deterministic path, even if the global planner mode is set to shadow or active. There is no accidental "all exchanges" switch hidden in configuration.
That scope is architectural, not editorial. Venue support should be explicit because market data, precision, order behavior, and worker lifecycles differ. A feature being safe in the shared Bybit/OKX path does not prove it has been integrated safely into another venue.
The practical effects
The immediate effect is not that every trade gets a more creative stop.
In deterministic mode, nothing changes. In shadow mode, execution still does not change. Even in active mode, I expect KEEP_BASELINE to be a healthy and frequent result. The planner's value is not measured by how often it disagrees; it is measured by whether disagreement is technically sourced, mathematically valid, and useful.
What did change is the shape of the system:
The deterministic baseline became a first-class artifact. It is no longer just two local variables on the way to an order. Its source, calculation steps, RR, and fallback history can be compared against a proposal and final resolution.
Agent responsibilities became narrower. The Entry Agent judges setup quality. The Trade Planner reviews protection and targeting. Risk Manager sizes per user. Venue code validates and executes. Narrow roles make failures easier to contain.
Latency became a supervised concurrency problem. The new analysis does not simply add another serial wait. Parallel tasks have separate deadlines, shared correlation, and explicit late-result handling.
Fallback and rejection became observable. "Used deterministic values" is no longer ambiguous. We can distinguish deliberate deterministic mode, shadow non-application, safe fallback, validation failure, timeout, persistence failure, unavailable Planner, and a hard pre-execution rejection.
Position management gained executable structured landmarks. A validated active plan may arm meaningful reaction levels. Price touch wakes a fresh review with exact provenance, while the original Planner still cannot pre-authorize an exit or partial take-profit.
Recommendation quality stopped depending on the most permissive user. Personal min_rr_ratio still belongs to personal execution. The global Planner floor now prevents a low-RR baseline from being presented as an intelligent recommendation and makes active mode fail closed when no technically valid repair exists.
There is also a cost: more schema, more persistence, another trace, more tests, and another component to monitor. The implementation added contract tests for LONG and SHORT ordering, rounding, malformed output, NaN and infinity, RR, immutable entry, trigger direction, timeout, exception, mode behavior, concurrency, database failure, one-to-many linking, and historical timelines without plan data.
That cost is the feature. The model call is the smallest part.
What I learned
The first lesson is that a fallback is useful only if it was designed before the intelligent path. Because LiquidMind already had deterministic SL/TP logic, the planner could be introduced as an optional reviewer. If the AI layer had also been the only source of a plan, every provider problem would have become a trading-system problem.
The second is that provenance has to be data, not prose. "Based on nearby liquidity" sounds reasonable and proves nothing. A source tool, exact level, timeframe, type, and matching selected price can be validated.
The third is that parallelism needs semantic ordering. Two tasks can finish in either order while still having different authority. The Entry Agent's answer remains logically first because a rejected trade has no plan to execute.
The fourth is that safe AI integration is mostly about the code around the model: immutable inputs, denied capabilities, strict output contracts, deterministic arithmetic, conditional fallback, audit linkage, rollout modes, and a rollback switch.
The fifth is that "fallback" is not automatically synonymous with "safe." Restoring a 0.58R baseline after the Planner failed to improve it technically preserves availability while violating the active feature's purpose. A fallback needs to be validated against the contract that is active at the moment it is used.
The Planner is now wired for Bybit and OKX, with deterministic mode still the default. The next step is not to trust active mode because the code compiles. It is to measure how often the independent RR floor causes adjustment or rejection, inspect the full ReAct traces, and follow each armed management target through its lifecycle.
That is the line I want LiquidMind to keep: agents can add judgment, but they do not get to erase the contracts that make the system trustworthy.

Public pen name of LiquidMind's founder and builder. Writing first-hand engineering notes and transparent performance reviews from the system's internal ledger.
Related Posts
Stay Liquid
New posts on transparency, engineering, and the LiquidMind thesis — no noise.
Loading discussion…