Build1 publisher3 min readPublished
One environment variable keeps the forex bot's reasoning grader off the trading hot path
The bot has traded real money since May and lost a $10,000 prop account to max drawdown on September 9. The grader built to check whether its theses match its own trades has made nine calls, every one of them a probe.
The Engineer · Build desk

What happened
- Once an hour, Gemini gets a one-minute candlestick chart plus daily and weekly context images, returns UP or DOWN with a one-line thesis, and the bot trades EURUSD, GBPUSD or USDJPY through MT5 on funded prop accounts.
- The first $10,000 account blew through its max drawdown on September 9 and is gone; the fleet is now two $100,000 accounts run as separate instances with separate state.
- The rails around the loop are a 2% daily loss cap, a 0.6% daily profit cap, and an external watchdog that kills the process after four consecutive losses.
- A new file of about 430 lines, shadow.py, sends each hourly decision to TypeSafe's text-only JEV model as a single pass/fail question about whether the recorded thesis supports the recorded direction.
- The grader has not run in production because SHADOW_MODE isn't set in any launch config; the dashboard probe ledger shows nine calls, all made while the plugin was being built.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint The incoherence rate the harness exists to produce has no samples yet, so the claim that a binary grader can flag bad reasoning stays untested against live hourly cycles.
- exposure Two accounts holding $200,000 now ride the same hourly loop that lost the first one, twenty times the capital, with the reasoning check dark.
- contradiction The post credits the rails and reports the first account gone, but stays silent on which cap or the watchdog fired in the run-up, so the caps' effect on the equity curve is unevidenced.
- decision The first real exercise of the collector will happen on funded accounts with open positions. Flipping the variable is a live-money call.
The check is one question per hourly decision. shadow.py posts a state string plus a single question named `shadow_direction_consistency`, typed `noul`, with instructions and explicit criteria: true when the recorded direction is explicit and the thesis supports it, false when the direction is ambiguous, contradicts the thesis, or was never recorded [12][13]. A `noul` answer is pass/fail against criteria the operator wrote [14]. The verdicts stay binary and countable, so thousands can be lined up against the trades they judged and the incoherence rate tracked over time [14].
JEV is TypeSafe's System-One model, `jev-latest`, served at api.typesafe.ai/v1/systemone, and it is text-only: no vision, no market opinions [11]. It cannot say whether UP was the right call on EURUSD. It can say when UP arrived attached to a thesis that argued DOWN [16]. "So it's a proofreader, not a fortune teller," wrote the developer, who publishes as nodefiend on dev.to [15][28].
The stated design rule is that the trading path must not know the harness exists unless it is told [18]. With `SHADOW_MODE` unset, shadow.py is a complete no-op, the only hot-path cost is a single `os.getenv()` call, and the tests assert that zero network calls happen [19]. When it is on, the harness dumps the exact chart images sent to the vision model into `shadow_out/<instance>/<timestamp>/` and records a sha256 of the prompt text for later lookup [20]. The one JEV call is hard-timeboxed at 20 seconds on a daemon thread, and the state is capped at 30,000 characters to stay inside the model's context [21]. Twenty seconds is about 0.6 percent of an hourly cycle [3]. One JSON line per decision goes to `shadow_log.jsonl` with the verdict and its latency [22]. `attach_outcome()` later patches the most recent unfinalized line for that pair with EXECUTED, FAILED or ORDER_FAILED plus the ticket, and it only touches lines written in the last 15 minutes, so a slow hourly cycle cannot corrupt an older decision's record [23].
A 2 percent daily loss cap sitting next to a 0.6 percent daily profit cap means a permitted losing day costs about 3.3 times what a permitted winning day banks [1]. Those numbers bound a day and the watchdog bounds a streak of four, but it was max drawdown that ended the first account [5].
The logging was already detailed. signal_log.csv holds every decision, executed or skipped, with the model's reasoning attached [7]. trade_analysis.csv holds every close with MFE/MAE in pips and R-multiples [8]. Neither file checks whether the thesis argues for the direction the model picked [9]. "When the account died, I could read every trade and every excuse, and none of it told me whether the reasoning was any good," the developer wrote [10].
Each instance writes to its own log, so the two accounts never mix records [24]. The developer wants a few market-open cycles of confidence before turning the collector on [27].
What to watch
- Whether SHADOW_MODE=1 reaches a launch config, and what the first incoherence rate over live cycles looks like.
- Whether the logged JEV latencies stay inside the 20-second timebox once real hourly cycles are graded.
- Whether attach_outcome's 15-minute window is wide enough for hourly cycles that run long, or whether outcomes go unattached.