A 16-second clock drift falsely tripped my trading bot's…
    Neura Market
    Neura Market
    /Perplexity
    Marketplace
    Directories
    Resources
    Perplexity
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeekCoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityTrending
    PerplexityBlogA 16-second clock drift falsely tripped my trading bot's kill switch. A postmortem.
    Back to Blog
    A 16-second clock drift falsely tripped my trading bot's kill switch. A postmortem.
    python

    A 16-second clock drift falsely tripped my trading bot's kill switch. A postmortem.

    Wataru Suda September 20, 2026
    0 views

    My PC clock ran 16 seconds ahead of the exchange. Signature validation failed, a "helpful" fallback computed equity from a paper balance, and the bot declared a 58% drawdown. No orders were sent. Here is the chain of events and the two fixes.

    I run a small, deliberately boring trading bot: daily bars, spot only, no leverage, about ¥24,000 of my own money on a Japanese exchange (GMO Coin). It wakes up once a day at 06:05, looks at yesterday's candle, and usually does nothing.

    On day six it halted itself and reported a 58% drawdown. My balance had not moved by a single yen.

    (The free lite backtester for this system, public API only, no key: GMO Coin Trend Lab, source on GitHub.)

    Nothing was lost. No order was sent. But the bug is a good one, because every link in the chain looked reasonable on its own.

    The timeline

    • The PC had been powered off for three days. When it came back, Windows had not yet resynced the clock. It was running about 16 seconds fast.
    • The scheduled task fired. The bot signed its first private API call with the local timestamp.
    • The exchange rejected it: ERR-5009 Timestamp for this request is too fast.
    • My startup code treated "key validation failed" as "no usable key" and fell back to dry-run mode. I had written that fallback on day one so that pasting the wrong kind of key would not crash anything.
    • Dry-run mode has its own paper balance, ¥10,000, and it shared the same state file as live mode.
    • The bot computed equity as ¥10,000, compared it with the stored high-water mark of ¥23,900, got a 58% drawdown, and did exactly what it is supposed to do at 30%: flatten and halt.

    Because it was in dry-run, "flatten" sent nothing. The halt flag, however, was written to the real state file. The bot would have sat there, halted, until a human noticed.

    Why each decision looked fine in isolation

    "Fall back to dry-run if the key does not validate." Friendly on day one, when the likely failure is a user pasting the FX key into the crypto slot. Dangerous on day six, when the key is fine and the failure is transient.

    "One state file." Simple. Until two modes with different balances write the same high-water mark.

    "Halt at 30% drawdown." Correct, and it worked. The kill switch is not the bug. The bug is that a number from one world was compared against a number from another.

    Fix 1: sign with the server's clock, not mine

    The exchange checks that your timestamp is close to its own. So ask it what time it is. Every HTTP response carries a Date header, and that is accurate enough for a tolerance measured in seconds.

    import email.utils, time, requests
    
    PUBLIC = "https://api.coin.z.com/public"
    
    class GmoClient:
        def __init__(self):
            self.s = requests.Session()
            self._offset = None   # local clock minus server clock, in seconds
    
        def clock_offset(self, refresh=False):
            if self._offset is None or refresh:
                try:
                    t0 = time.time()
                    r = self.s.get(PUBLIC + "/v1/status", timeout=15)
                    t1 = time.time()
                    srv = email.utils.parsedate_to_datetime(r.headers["Date"]).timestamp()
                    self._offset = (t0 + t1) / 2 - srv
                except Exception:
                    self._offset = 0.0
            return self._offset
    
        def _timestamp_ms(self):
            # the Date header has one-second resolution, so lean half a second early:
            # "slightly slow" is tolerated, "too fast" is rejected
            return str(int((time.time() - self.clock_offset() - 0.5) * 1000))
    

    Two details matter. Take the midpoint of the request so latency does not bias the estimate. And lean slightly early, because this API rejects timestamps from the future more strictly than ones from the recent past.

    After this change the bot does not care whether the OS clock is right.

    Fix 2: a failed validation must not touch live state

    c = GmoClient()
    if not c.dry_run:                      # keys are present, so we intend to be live
        try:
            c.assets()
        except GmoError as e:
            log(f"key validation failed, aborting this run without touching state "
                f"(clock offset {c.clock_offset():+.1f}s): {e}")
            return                          # try again tomorrow
    
    if c.dry_run:
        STATE = ROOT / "state_dry.json"    # paper trading lives in its own file
    

    The rule I took away: if the system intended to be live and cannot prove it is live, it should do nothing, loudly. Not "degrade gracefully" into a different mode that shares storage with the real one.

    What I check now when writing a fallback

    1. What does the fallback write, and where? If it shares storage with the normal path, it is not a fallback, it is a second writer.
    2. Is the failure I am catching permanent (wrong key) or transient (clock, network, maintenance window)? Transient failures should abort and retry later, not change mode.
    3. Would I notice? The halt was silent until I went looking. The bot now writes a dashboard every ten minutes with a red banner when it is halted.
    4. Can the safety mechanism be fed garbage? A kill switch is only as good as the equity number going into it.

    The boring numbers, since people ask

    The strategy itself is a 20-day breakout with a 50-day filter and a 2×ATR stop, risking 1% per trade. My backtest on the exchange's own daily data from 2018 to 2026 gives about the same return as volatility-targeted buy-and-hold with roughly half the drawdown, and close to zero in ranging years. Walk-forward out-of-sample Sharpe is 0.85. Its first real trade happened on day eight: 0.0076 ETH, with about ¥240 at risk.

    Operational holes scare me more than strategy losses. This one cost nothing, which is the best price to learn at.

    If you want to poke at the backtest, the lite version is a free download (public API only, no key needed): https://wataflow1.gumroad.com/l/trend-lab-free

    Not investment advice.

    Tags

    pythondebuggingapipostmortem

    Comments

    More Blog

    View all
    Claude on Google Cloud workshop | NYC | 9/30eventsinyourcity

    Claude on Google Cloud workshop | NYC | 9/30

    On Wednesday, September 30, Anthropic and Google Cloud are co-hosting a hands-on developer workshop...

    J
    Jen Harvey
    Building File4Base: The Modern, Open-Source Alternative to old file bases (Powered by Antigravity)opensource

    Building File4Base: The Modern, Open-Source Alternative to old file bases (Powered by Antigravity)

    For decades, platforms like Claris FileMaker, Microsoft Access, and 4D enabled businesses to build...

    M
    Mario Ezquerro
    This video is about how this video was madeai

    This video is about how this video was made

    This video (the script, the voice timings, the source code, the storyboard, the briefs the subagents...

    P
    Peter Kim Frank
    Congrats to the Summer Bug Smash Winners!devchallenge

    Congrats to the Summer Bug Smash Winners!

    We are thrilled to announce the winners of DEV's Big Summer Bug Smash powered by Sentry! This...

    J
    Jem
    Running a Jev-Style Decision Model on One TPU v6e: What Fits, What It Costs, and What Changes From a GPUgemma

    Running a Jev-Style Decision Model on One TPU v6e: What Fits, What It Costs, and What Changes From a GPU

    Gemma 4 E2B, E4B, 12B and a 26B-A4B fp8 build read by their label probabilities with vLLM on one TPU v6e chip, checked against the same read on an NVIDIA L4 and against Jev 1.13.0's published results. What fits one chip, how to read labels when vLLM on TPU returns only the top 32 log-probabilities, speed, cost, and why no 31B loads today.

    X
    xbill
    Equip your agent with Google Cloud best practices using google-cloud-developer pluginai

    Equip your agent with Google Cloud best practices using google-cloud-developer plugin

    Coding agents in your terminal can scaffold microservices in seconds. But what happens when your...

    R
    Remigiusz Samborski

    Stay up to date

    Get the latest Perplexity prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Perplexity and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Games
    • Blog
    • Videos
    • Guides
    • Courses
    • Community

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this Perplexity resource

    • Automate SEO-Optimized Blog Creation with GPT-4, Perplexity AI & Multi-Language Supportn8n · $24.99 · Related topic
    • Automate SEO Blog Content Creation with GPT-4, Perplexity AI, and WordPressn8n · $24.99 · Related topic
    • Automate SEO Blog Creation + Social Media with GPT-4, Perplexity, and WordPressn8n · $24.99 · Related topic
    • Auto-Generate SEO Blog Posts with Perplexity, GPT, Leonardo & WordPressn8n · $14.99 · Related topic
    Browse all workflows