10. The Code Is Done. The Market Hasn't Answered Yet.
The Code Is Done. The Market Hasn't Answered Yet.
Production Is a Different World
When the backtest hit +6,214% returns, I thought I was basically done. Model complete, risk management complete, Walk-Forward validation passed, Dry-Run completed. All that's left is to put it live, right?
Wrong. Running a system in production is an entirely different beast.
In backtests, all data is clean, APIs always respond, and the network never drops. But in production, the Binance server goes silent at 3 AM, orders take 2 seconds to fill, and database connections die.
Operating is 100 times harder than building.
That's not an exaggeration. I experienced firsthand the classic software engineering axiom: "Development is 20% of total cost, maintenance is 80%."
War with the Binance API
Timestamp Synchronization Issues
The very first error I hit when going live:
BinanceAPIException: Timestamp for this request was 1000ms ahead of the server's time.
The Binance API rejects requests whose timestamps differ from the server time by more than 1 second. If my server clock is 1 second ahead of Binance's, every single order fails.
This is a miniature version of the Clock Synchronization problem in distributed systems. Leslie Lamport's paper "Time, Clocks, and the Ordering of Events in a Distributed System" (1978) is the classic in this field. The core insight is that in distributed systems, "exact time" doesn't exist. You can only guarantee causal ordering between events. The final solution was checking the server time before every API call and applying an offset.
This is the kind of problem you will never discover in a backtest.
Order Execution Delay and Slippage
In backtests, "enter at this price" means you get exactly that price. But when you place a MARKET order in production, there's network latency, server processing, order book matching, and fill confirmation, price moves during this entire process. This is slippage.
$$S = \frac{P_{executed} - P_{expected}}{P_{expected}}$$
Slippage is a systematic cost. During volatile moments, getting filled 0.05-0.1% worse than intended is routine. With a 0.04% commission plus 0.05% slippage, that's 0.09% in invisible costs per trade. Accumulated across 740 trades, it's a non-trivial number.
Dashboard: Seeing the Numbers
Monitoring the system through console logs alone has its limits. Especially for tracking performance over multiple days or spotting patterns, visualization was essential. So I built a separate dashboard project.
It Started Simple
The first dashboard was bare-bones. Total trades, win rate, daily PnL chart, trade history list. Minimal features, but a hundred times better than digging through console logs.
But once I started running it live, needs kept emerging. The dashboard evolved steadily.
The PnL Calculation Battle
The first thing I discovered after building the dashboard was PnL calculation errors. There were subtle differences in how leverage and fees were reflected, causing the dashboard's displayed profit to differ from the actual Binance account balance. The difference was under 0.1%, but accumulated across hundreds of trades, it became a significant discrepancy.
It was also a classic floating-point arithmetic problem. Decimal precision between Binance's precision and my calculations was drifting apart. This was the same issue that led me to use a 0.001 tolerance in the trade sync script.
Mobile Access Is a Must
On the subway to work, during lunch, before bed, when a position is open, you want to check constantly. A desktop-only dashboard wasn't enough. I added mobile responsiveness and converted the menu to a hamburger button.
The next addition was a daily profit calendar. A monthly calendar view showing each day's profit/loss with color coding. Green-heavy months feel great; when red starts clustering, you tense up. This calendar had the biggest emotional impact of any feature. Putting data into the intuitive frame of "dates" made it hit harder than abstract cumulative returns.
Close Reason Analysis
The most pressing question from trade records is "why did this position close?" Was it TP (take-profit), SL (stop-loss), trailing stop, or time expiry? Once I visualized this, patterns emerged.
SL clustering during certain hours, trailing stop rates being higher on certain days of the week. These patterns are invisible in raw numbers. They only reveal themselves in charts. This is why I added trade_mode filters and close reason analysis during dashboard upgrades.
Model Signal Reversal Experiment
The most interesting feature was the MODEL_REVERSAL toggle. An option that flips the model's prediction signal to the exact opposite. When a LONG signal fires, it executes SHORT, and vice versa.
Why build such a thing? The hypothesis was: "If the model is wrong in a consistent pattern, wouldn't the reverse be profitable?" Of course, a simple reversal doesn't guarantee profits. There's the NO_TRADE decision to consider. But if there are specific market regimes where the model's directional predictions are systematically wrong, reversal could be viable.
I added this toggle to the dashboard so I could compare reversal mode performance side-by-side with normal mode. The bottom line: the windows where reversal was meaningful were fewer than expected. But the experiment itself brought a valuable shift in perspective: "analyzing the model's failure patterns."
Per-Model Profit Analysis
The last feature I added was a per-model profit analysis page. Comparing cumulative and daily PnL charts by model version, with performance tables for at-a-glance comparison.
Placing v2.5 and v2.7 side by side for the same period, the shape of the curves told a story that numbers alone couldn't. v2.5's equity curve zigzagged up and down while gradually declining; v2.7 smoothly trended upward. Same market, same period, but the character of the curves was completely different. This was the most intuitive display of the "qualitative difference" between models.
The Battle for Data Consistency
sync_trades: Hunting Ghost Positions
As the automated trading system runs, trade records accumulate. The problem is that the Binance trade history and my database records can fall out of sync.
The most common scenario: a TP or SL order fills automatically on Binance, but if my system doesn't happen to poll the API at that moment, the DB still shows "OPEN." This is the eventual consistency problem in distributed systems.
To solve this, I built a trade synchronization script. It queries the DB for "OPEN" trades, fetches the last 24 hours of trade history from the Binance API, matches by quantity, and updates trades that have actually been closed. I included a 0.001 tolerance in quantity matching because of floating-point precision differences, without it, unmatched trades become "ghost positions" that haunt the DB forever.
The first feature I added was dry-run mode. It lets you verify "will this trade match?" before touching the actual DB. Making a wrong DB update in production is hard to undo.
Token Expiry and Forced Redirects
When the dashboard auth token expired, the initial version just silently showed a blank screen. "Why isn't the data loading?" I spent a good while debugging before realizing it was a token issue. I added forced redirect to the login page on expiry. A small thing, but details like these are what separate "a tool you can actually use" from "a tool you just built."
The Routine of Operations
Auto-Restart and Availability
Programs crash. Memory leaks, unexpected exceptions, network timeouts. What matters is that they come back automatically when they die.
System availability is defined as:
$$A = \frac{MTBF}{MTBF + MTTR}$$
Here, $MTBF$ (Mean Time Between Failures) is the average time between crashes, and $MTTR$ (Mean Time To Repair) is the average recovery time. Without automatic restarts, $MTTR$ depends on human reaction time (potentially hours if it's the middle of the night). Adding automatic restarts reduces $MTTR$ to tens of seconds, dramatically improving $A$.
Think of a 24/7 convenience store. MTBF is how long the clerk lasts before collapsing, and MTTR is how long until the replacement arrives. If the owner has to drive over, it takes 2 hours, but if there's a backup worker next door, they swap in within 30 seconds.
I set up periodic health checks to verify process survival and restart if dead. Simple, but without it, a system crash at 3 AM means you don't find out until morning.
Telegram Notifications
Important events are sent to Telegram: position entry/exit, circuit breaker triggers, system errors, daily profit summary.
I can check notifications on my phone while sleeping. When a "BTCUSDT LONG +12.5% take-profit" notification comes in, I go back to sleep with a smile. When a "Circuit breaker triggered: 3 consecutive losses" notification arrives... my eyes snap open, but since the system stopped itself, I toss and turn for a bit and go back to sleep.
The Gap Between Backtest and Live
Throughout this series, I've repeatedly emphasized "be skeptical of your backtest results." Here's a summary of the main factors that create the gap:
FactorBacktestLiveImpactSlippage$S = 0$$S \approx 0.05\%$Reduced profit per tradeCommissionsFixed 0.04%0.04% + variableSame to slightly higherAPI failures0%~0.1%Delayed entries/exitsLiquidityInfiniteFiniteUnfavorable fills on large ordersData qualityPerfectOccasional gaps/delaysPotential false signals
Taking all these factors together, it's realistic to expect about 60-80% of backtest returns to be achievable in live trading. But you should always keep this "discount factor" in mind.
So, What About the Returns?
This has been a four-month journey. Starting from v1.0's 55.58%, I learned about log returns, fell for the illusion of Transformer's 32,687%, experienced the poison of SMOTE, got trapped by the 68% win rate mirage, redefined the question with Triple-Barrier, designed 67 features, built risk management that prevents bankruptcy, validated with Walk-Forward and Dry-Run, and built a dashboard to see the numbers with my own eyes.
The code is done. The model is trained. The system is running.
So what happened to the returns after all that?
Nobody knows.
The +6,214% backtest is a simulation on historical data. I confirmed consistency with Walk-Forward and verified live friction with Dry-Run. But tomorrow's market is different from yesterday's. How Bitcoin will move tomorrow, what the Fed will do, how market microstructure will shift. That's the domain of the world, not the model.
The one thing I can be sure of is this: this system is the best I could build. I've done every mathematically possible validation. I've done everything code can do. The rest is up to the market.
Perhaps this entire series converges to a single lesson:
Asking the right question matters more than finding the right answer.
When I asked "will the price go up or down?" in v1.0, the answer couldn't break 60%. When I changed the question to "which barrier gets hit first?", the same model hit 88%. The moment I shifted from "build a better model" to "ask a better question," the wall disappeared.
I don't think this applies only to trading. In development, in life, when the answer won't come, what you often need isn't a more sophisticated solution. It's a better question.
The system keeps running. Telegram notifications keep coming. The dashboard's equity curve adds a new point every day.
Where that curve is headed, truly, nobody knows.