DEV Community

finaltype
finaltype

Posted on Originally published at finaltype.github.io

"[Sep 07-13] Moving to a Shared Live Account - The Same Missing-Account-ID Bug Kept Recurring"

I started live trading through a new shared account, and along the way the same class of bug kept resurfacing - code that forgot to pass or accidentally shared an account identifier, triggering both a false kill-switch trip and a wrongful forced sell

This is the English version of a post originally written in Korean for my algorithmic trading system devlog(new tab).

The turning point - the first live order through a shared account

Wednesday split this week in two.

The existing live account had always belonged entirely to the bot, so the execution safety layers(new tab) were built on that assumption.

The account I brought online this week breaks that assumption. Personal funds and the bot's trading capital sit in the same account together.

After a redesign that filters personal funds out of buying-power calculations, determines who owns any unidentified holding, and hard-codes an invariant at the code level that sell orders can never touch personal holdings, the first live order through this account went out on Wednesday. It succeeded.

The fund-transfer method between the two accounts was also finalized that same day. No bank transfer — instead, whenever the old account sells something, the new account automatically factors the proceeds into its budget within the same trading round, purely as a ledger-level conversion.

Thursday morning, the physical transfer of holdings from the old account went through cleanly. From there, the rest of the week became cleanup work around this new account.

The recurring pattern - a missing or accidentally shared account identifier

Starting Thursday, three incidents that looked unrelated on the surface turned out to be the same kind of bug. Whenever code didn't explicitly say which account a given operation belonged to, the system fell back to a default or a shared global state.

On Thursday, the auto-transfer logic that was supposed to move cash from the old account to the new one never fired once all day, because a single field name had been mapped incorrectly. On Friday, it turned out the flag meant to prevent "don't rebuy something you already transferred out" wasn't scoped per account at all — it was stored in one file shared across the whole system.

As a result, positions in the new account that had just been transferred in and should have stayed held got treated as "excluded from ranking" too. Hours after the transfer, those positions were forcibly sold — the exact opposite of what the transfer was supposed to accomplish. The flag file was cleared as soon as this was found.

Saturday brought a heavier incident. A simulation tick meant purely for informational purposes never passed an account identifier, and it ended up sharing the same kill-switch namespace as the real live account.

That simulation tick happened to reference an old, unrelated, small account when computing balance. Comparing that balance against the live account's running peak made the system conclude the live account had lost nearly half its value in a single day. The kill switch tripped, freezing all new buys and sells.

It was the weekend, so there was no immediate damage — but left alone, it would have frozen live trading entirely come Monday.

Sunday's postmortem turned up something more important. Saturday's fix had only cleared the symptom: a withdrawal from Friday had been recorded in the ledger without resetting the loss-tracking peak that should have followed it.

With that peak left un-lowered, Monday's regular trading round would have compared the current balance against that same stale, higher peak — and the same kill switch could have tripped again. The peak was reset as soon as this was found, and two more spots with the same missing-account-identifier problem were patched.

All three incidents trace back to one root cause: leave an account identifier unspecified, and the system quietly falls back to a default or a shared value. Going forward, any code touching this identifier is required to specify it explicitly.

The GPU recognition failure — finally found the real cause

The GPU recognition failure I thought I'd already fixed last week came back this week.

The original suspect, power management, was ruled out by direct measurement. The real cause was the display manager: in a multi-GPU setup, it occasionally crashes and restarts, and during that restart GPU access permissions quietly shifted from the login session account to a different system account.

That state sat dormant for hours until an early-morning job the next day tried to grab the GPU and failed. A root-level fix that restructures the permission model to eliminate that race condition was applied and verified with a controlled reboot, with no recurrence since.

Saturday morning brought a separate GPU incident — an OS auto-update partially upgraded the driver, leaving the userspace and kernel-module versions mismatched. With no trading that day, there was room to handle it calmly with another reboot.

The performance auto-tuning tool — from design to a first run

I designed and started building a tool this week to automate the performance-tuning work that's had to be redone by hand every time the GPU or model changed.

Friday was design only: replay real captured load, search tuning parameters against it, and validate the results, with GPU-touching permissions kept strictly separate from design-only permissions so a design-side mistake can't reach production.

Sunday I actually implemented that design. AI code review caught several real bugs, the worst being baseline-selection logic that judged "is this the baseline" purely from a run-number naming convention — which would have let the baseline silently drift to a new candidate every round.

The first real run on an actual GPU hit a different wall: part of the execution engine's core steps turned out to still be unimplemented stubs, discovered mid-run. Given the safety principle that this tool may only touch the GPU through its designated path, patching in an improvised workaround on the spot would have violated that principle — so the run was stopped cleanly instead, with the reason logged for the next session.

The same day, using a spare GPU, I ran the first live measurement of a safeguard that's supposed to clean up only the production process on a port conflict while leaving experimental processes alone. It had only ever been verified with unit tests before. Spinning up two real OS processes under production-matching names to reproduce the conflict confirmed the safeguard worked exactly as intended — production cleaned up, the experimental process untouched.

Also this week

On Monday, a broker API balance query that only read the first page of a multi-page response caused a holding to disappear entirely from the results — fixed. The same class of pagination bug resurfaced as part of Thursday's reconciliation incident, so other API calls with a similar paged structure were flagged for a later check.

On Tuesday, fixing a broker login credential loading-order bug also surfaced a leak risk that hadn't shipped yet — if left unfixed, the account ID would have been logged in plaintext the first time login actually succeeded. A buffer rule was also added around the boundary of the trading universe to cut down on frequent additions and drops caused by minor rank fluctuations near the cutoff.

A minimum holding-period rule aimed at reducing turnover also went into validation on both paper accounts.


Looking back, introducing one new assumption — a shared account — surfaced, one by one, all the places in the code that had quietly assumed "there's only ever one account."

The false kill-switch trip, the wrongful forced sell, and the failed cash auto-transfer all turned out to be the same hole. Each time, it was caught before live trading actually got blocked, and Sunday's postmortem tracked down the root that was still left behind.

Top comments (0)