Skip to content

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

Illustration accompanying One environment variable keeps the forex bot's reasoning grader off the trading hot path

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.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories