{
    "version": "https://jsonfeed.org/version/1",
    "title": "BT365 Articles",
    "home_page_url": "https://bt365.ai/articles/",
    "description": "Guides and analysis on onchain perps, prediction markets, and autonomous trading agents.",
    "items": [
        {
            "id": "https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/",
            "content_html": "\n<p>Suppose an agent has been asked to buy a token, and your service can do that. How does it find out?</p>\n<p>Not from your homepage — it cannot reliably read a JavaScript app, and marketing copy does not tell it which endpoint to call. Not from your docs site, which assumes a reader who already decided to integrate. If discovery depends on a human having read something and then written code, your service is not agent-accessible; it is human-accessible with an API attached.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"convention-beats-registration\">Convention beats registration<a href=\"https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/#convention-beats-registration\" class=\"hash-link\" aria-label=\"Direct link to Convention beats registration\" title=\"Direct link to Convention beats registration\" translate=\"no\">​</a></h2>\n<p>The mechanism that solves this is old and boring: put the metadata at a path everyone has already agreed on.</p>\n<p>That is what <code>/.well-known/</code> is for. Because the location is fixed by convention, a client can simply ask — no registry to join, no directory to be listed in, no negotiation. It is <code>robots.txt</code> generalised, and it is the reason discovery works at all without a central authority.</p>\n<p>BT365 publishes two documents there. <code>/.well-known/agent.json</code> is an ERC-8004 discovery document, the emerging convention for agent identity and capability. <code>/.well-known/ai-plugin.json</code> is the OpenAI plugin manifest, which predates it and is still widely fetched. Both describe the same service; they exist in parallel because you do not get to choose which convention the caller implements.</p>\n<p>There is also <code>llms.txt</code> at the site root — a plain-text summary of what the service does and how to integrate, aimed at a model reading the site directly rather than at a client parsing a manifest. And <code>robots.txt</code> explicitly allows <code>GPTBot</code>, <code>ClaudeBot</code> and <code>PerplexityBot</code>, because none of the above matters if the crawler was disallowed before it got there.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"write-the-models-description-separately\">Write the model's description separately<a href=\"https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/#write-the-models-description-separately\" class=\"hash-link\" aria-label=\"Direct link to Write the model's description separately\" title=\"Direct link to Write the model's description separately\" translate=\"no\">​</a></h2>\n<p>The single highest-leverage detail in any of these manifests is that the plugin format has two description fields, and they are not the same text.</p>\n<p><code>description_for_human</code> is positioning. It is what you would put on a pricing page.</p>\n<p><code>description_for_model</code> is operational, and it should read like a note to an engineer with no time. Ours names the concrete capabilities — swap execution across specific venues, token launch, agent creation, portfolio tracking — and then names the endpoints for the common path: search to find a token, quote for a price, swap to build the transaction.</p>\n<p>That last part is what earns its place. A model choosing between services is not deciding whether it likes your brand; it is deciding whether it can accomplish a task and, if so, in what order to call things. Telling it directly removes the step where it guesses, and guessing is where integrations fail. Pasting your marketing tagline into that field wastes the most valuable few hundred characters you have.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"discovery-has-to-be-free-which-is-not-the-same-as-unguarded\">Discovery has to be free, which is not the same as unguarded<a href=\"https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/#discovery-has-to-be-free-which-is-not-the-same-as-unguarded\" class=\"hash-link\" aria-label=\"Direct link to Discovery has to be free, which is not the same as unguarded\" title=\"Direct link to Discovery has to be free, which is not the same as unguarded\" translate=\"no\">​</a></h2>\n<p>Both manifests are public and require no authentication. That is not an oversight, and it is worth being explicit about why, because it looks inconsistent with charging for the API.</p>\n<p>You cannot gate the document whose job is to tell callers you exist. An agent that must authenticate before it can discover what you offer will never get to the part where it pays you. So discovery is open, and the payment boundary sits at the individual endpoints — where a caller either presents <a class=\"\" href=\"https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/\">payment for the call</a>, a pre-issued key, or falls into a sponsored allowance.</p>\n<p>Open does not mean unlimited, though. Both discovery routes are rate-limited to a hundred requests a minute. A manifest is cheap to serve and trivially scriptable, and there is no scenario where one caller legitimately needs more than that.</p>\n<p>One honesty note if you are reading our manifest: it declares <code>auth: none</code>. That describes the manifest and the free surface, not every endpoint behind it. A caller that reads it as \"everything here is free\" will meet a <code>402</code> on its first priced call — which is precisely what the <code>402</code> is for, since it comes back with the price attached rather than a refusal.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-manifest-is-not-the-contract\">The manifest is not the contract<a href=\"https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/#the-manifest-is-not-the-contract\" class=\"hash-link\" aria-label=\"Direct link to The manifest is not the contract\" title=\"Direct link to The manifest is not the contract\" translate=\"no\">​</a></h2>\n<p>A manifest tells an agent that you exist and roughly what you do. It is deliberately not a full description of your interface, and trying to make it one is a mistake.</p>\n<p>The actual contract is the OpenAPI document the manifest points at. That is generated from the running service rather than maintained by hand, which matters more for machine callers than human ones: a human reading stale docs gets confused and asks; an agent reading a stale spec constructs a call that fails, and has no idea the spec was the problem.</p>\n<p>Two layers, two jobs. The manifest is an advertisement with a fixed address. The spec is the machine-readable truth. Keep the first short and the second generated.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"then-there-is-mcp-which-is-a-different-question\">Then there is MCP, which is a different question<a href=\"https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/#then-there-is-mcp-which-is-a-different-question\" class=\"hash-link\" aria-label=\"Direct link to Then there is MCP, which is a different question\" title=\"Direct link to Then there is MCP, which is a different question\" translate=\"no\">​</a></h2>\n<p>Everything above is about an agent that speaks HTTP finding an HTTP API. The Model Context Protocol answers something adjacent: how a <em>model inside a client</em> gets tools at all.</p>\n<p>The distinction is worth keeping straight. Discovery manifests make your API findable by any program that fetches URLs. MCP makes your capabilities available as tools inside an assistant, without that assistant's author having integrated you specifically. One is about being findable; the other is about being <em>installed</em>. We expose the trading surface both ways, over stdio and HTTP transports, authenticated by key rather than per-call payment — because a tool inside a session is an ongoing relationship, not a one-shot request.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-this-is-worth-an-afternoon\">Why this is worth an afternoon<a href=\"https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/#why-this-is-worth-an-afternoon\" class=\"hash-link\" aria-label=\"Direct link to Why this is worth an afternoon\" title=\"Direct link to Why this is worth an afternoon\" translate=\"no\">​</a></h2>\n<p>The audience for all of this is small right now. It is also the cheapest distribution work available, and it does not decay.</p>\n<p>Two manifests, a text file, a crawler policy, and a generated spec is perhaps a day of work, most of it writing. What it buys is that software written by people who have never heard of you can find you, understand what you do, and call you — and, with a payment method it can complete unattended, become a customer in the same session.</p>\n<p>Compare that with the alternative channel, where a human searches, reads, evaluates, signs up, and integrates over some number of weeks. Both are worth having. Only one of them is currently uncontested.</p>\n<hr>\n<p><em>Part of our guide to <a class=\"\" href=\"https://bt365.ai/articles/agent-integration/\">making an API usable by AI agents</a>.</em></p>\n<p><em>BT365 exposes its trading surface to agents over x402, MCP and machine-readable discovery. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">API documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/how-an-ai-agent-discovers-your-api/",
            "title": "How does an AI agent discover what your API can do?",
            "summary": "An agent cannot read your landing page, and it will not guess your endpoints. Making a service discoverable by software means publishing a manifest at a location nobody has to be told about — and writing a description aimed at a machine rather than a buyer.",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "agents",
                "discovery",
                "api",
                "erc-8004",
                "mcp"
            ]
        },
        {
            "id": "https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/",
            "content_html": "\n<p>An AI agent trying to use a paid API hits a wall that has nothing to do with intelligence. It cannot sign up.</p>\n<p>Signup assumes a person: an email to confirm, a card with a billing address, sometimes a captcha or an SMS. An agent has none of that. What it can have is a wallet — and if a service is willing to be paid by wallet, the entire onboarding problem collapses into a single HTTP exchange.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-status-code-nobody-used\">The status code nobody used<a href=\"https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/#the-status-code-nobody-used\" class=\"hash-link\" aria-label=\"Direct link to The status code nobody used\" title=\"Direct link to The status code nobody used\" translate=\"no\">​</a></h2>\n<p>HTTP has reserved <code>402 Payment Required</code> since the early nineties and essentially never used it. There was no interoperable way to say <em>what</em> payment, so servers returned <code>401</code> or <code>403</code> and pointed humans at a pricing page.</p>\n<p>The x402 pattern fills in the missing half. An unpaid request gets <code>402</code> back, and the response body carries a machine-readable description of the payment: how much, in which asset, to which address, on which chain. That is enough for a client to act without reading documentation. It pays, then retries the same request with proof attached, and gets the resource.</p>\n<p>Two round trips, no account, no key issued ahead of time. On BT365 the <code>402</code> response carries that payment block alongside the standard error shape, so a client that does not understand x402 still sees a well-formed HTTP error rather than something malformed.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"two-ways-to-prove-you-paid\">Two ways to prove you paid<a href=\"https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/#two-ways-to-prove-you-paid\" class=\"hash-link\" aria-label=\"Direct link to Two ways to prove you paid\" title=\"Direct link to Two ways to prove you paid\" translate=\"no\">​</a></h2>\n<p>There are two flavours in practice, and the difference matters more than it looks.</p>\n<p><strong>Proof by transaction hash.</strong> The agent sends the payment on-chain itself, then presents the hash. The server verifies the transaction really happened, went to the right address, carried the right amount, and has not been presented before. This works and is easy to reason about, but it requires the agent to hold native gas on the settlement chain and to wait for confirmation before it can retry.</p>\n<p><strong>Proof by signed authorisation.</strong> The agent instead signs an off-chain message — under EIP-3009, a <code>transferWithAuthorization</code> — that authorises the service to pull a specific amount. The signature travels in a header. The server verifies it cryptographically, then submits the transfer itself and pays the gas.</p>\n<p>The second removes a whole category of failure. The agent needs the stablecoin and nothing else: no gas token on the settlement chain, no confirmation wait, no separate funding step for a chain it may never otherwise touch. In our implementation the platform's own wallet pays that gas, and it costs a fraction of a cent per call — cheap enough that eating it is simpler than making every caller solve gas provisioning.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-parts-that-stop-it-being-exploitable\">The parts that stop it being exploitable<a href=\"https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/#the-parts-that-stop-it-being-exploitable\" class=\"hash-link\" aria-label=\"Direct link to The parts that stop it being exploitable\" title=\"Direct link to The parts that stop it being exploitable\" translate=\"no\">​</a></h2>\n<p>A payment scheme is only as good as its replay protection, and this is where most of the real engineering sits.</p>\n<p><strong>A signed authorisation is a bearer instrument.</strong> Anyone who obtains it can submit it. So each carries a nonce, and the nonce is enforced by a <strong>unique database index</strong> rather than by a lookup-then-insert check. That distinction is the whole thing: a check-then-act can be raced by two concurrent requests presenting the same authorisation, and both will pass the check before either writes. A unique constraint cannot be raced — the second insert simply fails.</p>\n<p><strong>Pricing fails closed.</strong> Prices are stored per endpoint, and asking the price of an endpoint that has no price configured <strong>throws</strong> rather than returning zero or a default. The failure mode of the alternative is a new endpoint shipping without a price row and being silently free until someone notices. Throwing means an unpriced endpoint denies access instead of giving itself away.</p>\n<p><strong>Amounts are validated as integers, in the asset's smallest unit.</strong> No floats anywhere near a payment amount.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"not-the-only-door\">Not the only door<a href=\"https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/#not-the-only-door\" class=\"hash-link\" aria-label=\"Direct link to Not the only door\" title=\"Direct link to Not the only door\" translate=\"no\">​</a></h2>\n<p>Worth saying, because a payment-per-call scheme is not always the right fit: x402 is one of three ways into the same API surface here.</p>\n<p>An agent can pay per call as described. It can present a <strong>pre-issued MCP key</strong>, which suits a caller with an ongoing relationship who would rather not sign a payment every time. Or it can fall through to a <strong>sponsored allowance</strong>, where the platform covers a bounded amount of usage — useful for evaluation, where asking someone to fund a wallet before they can see whether the API is any good is a poor trade.</p>\n<p>They resolve in one guard, and the endpoint does not care which one you used.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-bother-supporting-this-at-all\">Why bother supporting this at all<a href=\"https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/#why-bother-supporting-this-at-all\" class=\"hash-link\" aria-label=\"Direct link to Why bother supporting this at all\" title=\"Direct link to Why bother supporting this at all\" translate=\"no\">​</a></h2>\n<p>The honest answer is that the audience is small today and structurally growing, and the cost of being early is low.</p>\n<p>An agent that can pay for a call can also <em>discover</em> what it is paying for. Publishing a machine-readable description of your service, at a well-known location, alongside a payment method an agent can complete unattended, makes you callable by software that was not written with you in mind. That is a different distribution channel from search — and it is one where almost nobody has bothered to show up.</p>\n<p>For a service whose value is execution rather than content, that asymmetry is the interesting part. A human reads an article and might sign up. An agent reads a manifest and can be a customer in the same request.</p>\n<hr>\n<p><em>Part of our guide to <a class=\"\" href=\"https://bt365.ai/articles/agent-integration/\">making an API usable by AI agents</a>.</em></p>\n<p><em>BT365 exposes its trading surface to agents over x402 and MCP, with machine-readable discovery. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">API documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/how-an-ai-agent-pays-for-an-api-call/",
            "title": "How does an AI agent pay for an API call?",
            "summary": "An agent with a wallet and no credit card cannot sign up for anything. The x402 pattern turns the long-dormant 402 status code into a payment handshake an agent can complete on its own, in one round trip.",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "agents",
                "x402",
                "api",
                "machine-payments"
            ]
        },
        {
            "id": "https://bt365.ai/articles/hyperliquid-vs-polymarket-prediction-markets/",
            "content_html": "\n<p>From the outside, a prediction market is the same thing everywhere. You pay something under a dollar for a claim on an outcome, and if the outcome happens you collect a dollar.</p>\n<p>The mechanics underneath are not the same, and the differences are not cosmetic — they determine what you actually own, what it costs to get in and out, and which specific things can go wrong. BT365 runs both Polymarket and Hyperliquid's HIP-4 outcome markets, so this is a comparison from having implemented against each rather than from reading their docs.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-you-hold\">What you hold<a href=\"https://bt365.ai/articles/hyperliquid-vs-polymarket-prediction-markets/#what-you-hold\" class=\"hash-link\" aria-label=\"Direct link to What you hold\" title=\"Direct link to What you hold\" translate=\"no\">​</a></h2>\n<p>This is the difference everything else follows from.</p>\n<p><strong>On Polymarket, you hold a token.</strong> Each outcome is a conditional token on Polygon, and buying a position means acquiring those tokens through an order book. When the market resolves, the tokens become redeemable on-chain, and redemption is a transaction — one that you (or the platform acting for you) submit. The winning outcome's tokens are worth a dollar each; the others are worth nothing.</p>\n<p><strong>On Hyperliquid, you hold a position.</strong> HIP-4 lists each outcome as a market on the exchange's own order book. Buying does not mint anything on-chain; it opens a position in your exchange account, priced strictly between zero and one, which the venue settles to zero or one at resolution.</p>\n<p>So the question \"what happens at resolution?\" has two different answers. On Polymarket, something has to be redeemed, by someone, on-chain. On Hyperliquid, the venue marks your position to its terminal value and the balance changes.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"which-means-the-failure-modes-are-different\">Which means the failure modes are different<a href=\"https://bt365.ai/articles/hyperliquid-vs-polymarket-prediction-markets/#which-means-the-failure-modes-are-different\" class=\"hash-link\" aria-label=\"Direct link to Which means the failure modes are different\" title=\"Direct link to Which means the failure modes are different\" translate=\"no\">​</a></h2>\n<p>Both are fine when everything works. They break differently.</p>\n<p><strong>Polymarket's failure mode is redemption.</strong> Redeeming is an on-chain transaction, and transactions need gas and can revert. A resolved position whose redemption fails is still yours and still worth a dollar a share — but the value has not been collected yet, and something has to retry until it is. Handling this properly means treating \"resolved\" and \"collected\" as separate states and never marking the second on the strength of the first.</p>\n<p><strong>Hyperliquid's failure mode is delisting.</strong> A market on an exchange can be removed. If it is removed <em>after</em> the outcome is known, settlement is unambiguous. If it is removed while the price is still mid-range — say it was trading at fourteen cents — then the position's terminal value is genuinely not determined by the last price it traded at, and something has to decide what the position was worth. That is a harder problem than it sounds, and guessing at it is how a platform silently pays the wrong amount.</p>\n<p>This is not hypothetical. When Hyperliquid moved HIP-4 outcome markets from USDH to USDC as the quote asset, the USDH-quoted markets were settled and delisted as a batch. A venue-side change can end a market on its own schedule, which is a category of event on-chain conditional tokens do not have.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"price-mechanics\">Price mechanics<a href=\"https://bt365.ai/articles/hyperliquid-vs-polymarket-prediction-markets/#price-mechanics\" class=\"hash-link\" aria-label=\"Direct link to Price mechanics\" title=\"Direct link to Price mechanics\" translate=\"no\">​</a></h2>\n<p>Both venues price outcomes between zero and one, but the rules differ in ways that matter if you are placing orders programmatically.</p>\n<p>HIP-4 enforces a strict range — a limit price must be greater than zero and less than one, never equal to either — with prices carried at six decimal places internally. Client orders are additionally held to at most five significant figures, matching Hyperliquid's general price rule, while fills and accounting snapshots may legitimately carry the sixth decimal. There is also a floor on order size: ten dollars of notional.</p>\n<p>The five-significant-figure rule is the one that catches people. <code>0.38211</code> is acceptable; a price with more precision than that is rejected outright rather than rounded, so an order builder that computes a price by dividing has to quantise before submitting.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"where-the-odds-do-not-add-up\">Where the odds do not add up<a href=\"https://bt365.ai/articles/hyperliquid-vs-polymarket-prediction-markets/#where-the-odds-do-not-add-up\" class=\"hash-link\" aria-label=\"Direct link to Where the odds do not add up\" title=\"Direct link to Where the odds do not add up\" translate=\"no\">​</a></h2>\n<p>One thing both venues share, and it is worth saying because it is the most common misreading of a prediction market.</p>\n<p>The price is not a probability. It is what the last trader was willing to pay. Across a set of mutually exclusive outcomes, the prices need not sum to one, because each outcome trades in its own book against its own buyers and sellers, and nothing mechanically forces the books to agree. Polymarket's grouping mechanism does enforce that exactly one outcome pays out — that is a settlement guarantee, not a pricing one.</p>\n<p>When the prices do not sum to one, the gap is real and, in principle, collectable. In practice, closing it means trading every leg at the displayed price, which is where order book depth and fees eat most of what looked like free money. We wrote about <a class=\"\" href=\"https://bt365.ai/articles/prediction-market-odds-dont-add-up/\">why prediction market odds don't add up</a> separately, and the arithmetic there applies to both venues.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"which-one-you-actually-want\">Which one you actually want<a href=\"https://bt365.ai/articles/hyperliquid-vs-polymarket-prediction-markets/#which-one-you-actually-want\" class=\"hash-link\" aria-label=\"Direct link to Which one you actually want\" title=\"Direct link to Which one you actually want\" translate=\"no\">​</a></h2>\n<p>There is no universal answer, but the trade-off is clean.</p>\n<p><strong>Polymarket has the deeper markets on political, cultural and event outcomes</strong>, and the largest set of them. If the question you want exposure to is a news event, that is usually where the liquidity is. You are also holding a real on-chain claim, which some people prefer on principle.</p>\n<p><strong>Hyperliquid puts the outcome next to everything else you trade.</strong> Because the position lives in an exchange account, it draws on the same collateral as your other positions there rather than sitting in a separate wallet on a separate chain, and getting in and out does not mean bridging. If you are already trading perps on Hyperliquid, an outcome position is a market away rather than a chain away.</p>\n<p>The corollary is worth stating explicitly, because it is the part people miss: sharing an account means sharing collateral. An outcome position on an exchange account is not walled off from the rest of it. That is convenient and it is also exposure — the same trade-off that applies to any <a class=\"\" href=\"https://bt365.ai/articles/what-unified-margin-actually-shares/\">unified margin account</a>.</p>\n<hr>\n<p><em>Part of our guide to <a class=\"\" href=\"https://bt365.ai/articles/prediction-markets/\">how prediction markets actually work</a>.</em></p>\n<p><em>BT365 runs prediction markets on both Polymarket and Hyperliquid, alongside perps and spot, from one self-custodial account. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/hyperliquid-vs-polymarket-prediction-markets/",
            "title": "How are prediction markets on Hyperliquid different from Polymarket?",
            "summary": "Both let you buy an outcome for less than a dollar and collect a dollar if you are right. Underneath, one gives you a token you redeem yourself and the other gives you a position the exchange settles — and that difference decides what can go wrong.",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "prediction-markets",
                "hyperliquid",
                "hip-4",
                "polymarket"
            ]
        },
        {
            "id": "https://bt365.ai/articles/order-sent-exchange-never-replied/",
            "content_html": "\n<p>Most order-handling code is written as if there are two outcomes: the order worked, or it didn't.</p>\n<p>There is a third, it is more common than people expect, and it is the one that actually costs money. You send an order. The connection stalls, or the venue returns a shape you don't recognise, or a gateway hands back a 502. You now do not know whether you have a position.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-this-is-not-just-an-error\">Why this is not just an error<a href=\"https://bt365.ai/articles/order-sent-exchange-never-replied/#why-this-is-not-just-an-error\" class=\"hash-link\" aria-label=\"Direct link to Why this is not just an error\" title=\"Direct link to Why this is not just an error\" translate=\"no\">​</a></h2>\n<p>The reason this state is special is that sending an order is not reversible.</p>\n<p>Once the request has physically left, the venue may have accepted it. It may be resting on the book. It may have filled already. None of that depends on whether your process survived long enough to hear about it. The information was lost; the order was not.</p>\n<p>So the usual error-handling instinct — catch it, retry, move on — is actively dangerous here. Retrying an order that already exists does not repair anything. It places a second one. On a leveraged product that means double the intended position, at a price you never agreed to, discovered later.</p>\n<p>This is why \"did the failure happen before or after the request was transmitted?\" is the only question that matters, and why it deserves to be tracked explicitly rather than inferred from the error text.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"naming-the-state\">Naming the state<a href=\"https://bt365.ai/articles/order-sent-exchange-never-replied/#naming-the-state\" class=\"hash-link\" aria-label=\"Direct link to Naming the state\" title=\"Direct link to Naming the state\" translate=\"no\">​</a></h2>\n<p>BT365 marks these orders <strong>ambiguous</strong>, as a first-class status distinct from rejected.</p>\n<p>The distinction is enforced at the point where a venue response is parsed. If the response has a shape the parser does not recognise — a missing status block, an unknown discriminator — it does not fall through to a default and it does not treat the order as failed. It raises an error explicitly flagged as <em>post-broadcast</em>, and that flag is what turns the order ambiguous instead of rejected. The same flag is set for <code>429</code> and <code>5xx</code> responses, because a rate-limit or gateway error arriving after transmission tells you nothing about the order's fate either.</p>\n<p>The alternative — the version this replaced — was to write the order down with a null exchange id and call it submitted. That looks harmless and is not: a row claiming to be a submitted order while carrying no identifier is unusable. You cannot cancel it, cannot look it up, and cannot tell it apart from a real order whose id you simply haven't stored yet. Refusing to persist that row is the whole point.</p>\n<p><strong>Rejected means the venue said no.</strong> <strong>Ambiguous means the venue didn't say.</strong> Collapsing the two is how a system convinces itself it has no position while holding one.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"getting-the-answer-later\">Getting the answer later<a href=\"https://bt365.ai/articles/order-sent-exchange-never-replied/#getting-the-answer-later\" class=\"hash-link\" aria-label=\"Direct link to Getting the answer later\" title=\"Direct link to Getting the answer later\" translate=\"no\">​</a></h2>\n<p>An ambiguous order is not left ambiguous. It is a question to be answered, and the answer comes from the venue.</p>\n<p>The mechanism that makes this possible is an identifier attached to the order <em>before</em> it is sent — a client-supplied order id. Because it is chosen in advance, it survives the lost reply. The exchange-assigned id may never have arrived, but the order can still be looked up by the id we gave it, and the venue's answer is authoritative: it filled, it is resting, it never existed.</p>\n<p>A reconciliation pass runs over orders in an unresolved state and does exactly that, asking the venue by exchange id where one is known and by client id where it is not, then persisting whatever the venue says. Two details make it trustworthy rather than merely present. Transport failures during reconciliation are swallowed and retried later rather than being read as answers — a failed question is not a \"no\". And an explicit \"no such order\" response <em>is</em> treated as terminal, because that is the venue affirmatively answering, which is categorically different from failing to reach it.</p>\n<p>There is one wrinkle worth knowing if you build against this kind of venue: fills do not necessarily carry the client id back. So you cannot always match a fill to your original request directly, and recovery works by first resolving the order to its exchange id, then linking fills through that. It is one indirection more than you would expect, and skipping it is how fills go unattributed.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"resting-orders-make-the-window-longer\">Resting orders make the window longer<a href=\"https://bt365.ai/articles/order-sent-exchange-never-replied/#resting-orders-make-the-window-longer\" class=\"hash-link\" aria-label=\"Direct link to Resting orders make the window longer\" title=\"Direct link to Resting orders make the window longer\" translate=\"no\">​</a></h2>\n<p>All of this gets worse the longer an order can live.</p>\n<p>An immediate order resolves in the same breath — filled or not, you find out now, and the ambiguous window is milliseconds. A good-til-cancel order that rests on the book can fill hours later, or partially, or in pieces. The gap between \"we sent this\" and \"we know what it became\" is no longer a network round trip; it is however long the order sits there.</p>\n<p>Which means reconciliation cannot be a one-shot cleanup after a failure. It has to be a routine that keeps running, because an order that was genuinely unfilled when you last asked can be filled the next time you do. A system that only reconciles on error will correctly recover from the timeout and then miss the fill that happens twenty minutes later.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-same-shape-everywhere\">The same shape everywhere<a href=\"https://bt365.ai/articles/order-sent-exchange-never-replied/#the-same-shape-everywhere\" class=\"hash-link\" aria-label=\"Direct link to The same shape everywhere\" title=\"Direct link to The same shape everywhere\" translate=\"no\">​</a></h2>\n<p>The reason to write this up is that it is not a quirk of one venue or one asset class. The identical pattern shows up in three unrelated parts of our own system, and it is the same discipline every time.</p>\n<p>An outcome-market order whose response was lost is ambiguous. A treasury payout whose transaction hash was recorded but whose confirmation wait timed out stays submitted, and explicitly must never be re-signed — a replacement would be a second payment. A funding transfer that cannot be confirmed either way is marked ambiguous for a human to look at, rather than synthetically reversed, because a synthetic reversal of a transfer that actually succeeded is just a second, wrong transfer.</p>\n<p>Three different money paths, one rule: <strong>when you have sent an instruction you cannot un-send and lost the reply, stop and ask. Never assume, and never retry.</strong></p>\n<p>The general form is worth keeping, because it applies to anything that moves value over a network you don't control. There is a difference between <em>this did not happen</em> and <em>I do not know whether this happened</em>, and a system that cannot represent the second one will eventually act as though it were the first.</p>\n<hr>\n<p><em>BT365 runs prediction markets, perps, and spot from one self-custodial account, with venue-truthful order reconciliation on every position. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/order-sent-exchange-never-replied/",
            "title": "What happens when your order is sent but the exchange never replies?",
            "summary": "There is a third outcome between filled and rejected, and it is the one that loses money. Sending an order you cannot un-send, then losing the reply, is the hardest state in trading infrastructure — and the wrong fix is retrying.",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "perps",
                "hyperliquid",
                "order-execution",
                "reliability"
            ]
        },
        {
            "id": "https://bt365.ai/articles/signing-trading-bot-api-requests/",
            "content_html": "\n<p>If you have integrated one exchange API you have integrated most of them. The signing scheme is near-universal: a key id, a secret, an HMAC over some canonical form of the request.</p>\n<p>What varies — and what almost no documentation explains — is exactly which bytes go into the signature, what else the server checks, and in what order. Those details are the security. The HMAC is the easy part.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-actually-gets-signed\">What actually gets signed<a href=\"https://bt365.ai/articles/signing-trading-bot-api-requests/#what-actually-gets-signed\" class=\"hash-link\" aria-label=\"Direct link to What actually gets signed\" title=\"Direct link to What actually gets signed\" translate=\"no\">​</a></h2>\n<p>BT365's trading API follows the Binance and Hyperliquid convention. A request carries the key id, a client timestamp, and a hex HMAC-SHA256 signature, with an optional field for how much clock skew you want tolerated.</p>\n<p>The signed material is the part worth being precise about:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#F8F8F2;--prism-background-color:#282A36\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#F8F8F2;background-color:#282A36\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#F8F8F2\"><span class=\"token plain\">${timestamp}\\n${METHOD}\\n${path}\\n${rawBody}</span><br></div></code></pre></div></div>\n<p>Four components, each there for a reason.</p>\n<p>The <strong>timestamp</strong> binds the request to a moment, which is what makes the replay window enforceable. The <strong>method</strong> stops a signature for a <code>GET</code> being replayed as a <code>DELETE</code> on the same path. The <strong>path</strong> stops a signature for one endpoint being reused on another. And the <strong>raw body</strong> — the exact bytes, not a re-serialised version of the parsed object — is what stops an attacker altering the payload while keeping the signature intact.</p>\n<p>That last one is the subtle bug. If a server parses JSON and then re-serialises it to verify, two different byte sequences can produce the same canonical form, and key ordering or whitespace differences can make a legitimate signature fail or an altered body pass. Sign and verify the bytes that arrived.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-order-of-the-checks-is-the-design\">The order of the checks is the design<a href=\"https://bt365.ai/articles/signing-trading-bot-api-requests/#the-order-of-the-checks-is-the-design\" class=\"hash-link\" aria-label=\"Direct link to The order of the checks is the design\" title=\"Direct link to The order of the checks is the design\" translate=\"no\">​</a></h2>\n<p>A signature check is not the first thing that should happen, and treating it as the only thing is where implementations go wrong. Ours runs in this order:</p>\n<ol>\n<li class=\"\"><strong>Resolve the key.</strong> Unknown key id, done.</li>\n<li class=\"\"><strong>IP allowlist</strong>, if the key has one. Cheap, and it makes a stolen key useless from anywhere else.</li>\n<li class=\"\"><strong>Per-key rate limit</strong>, in Redis. Before any cryptography.</li>\n<li class=\"\"><strong>Timestamp window.</strong> Default five seconds, configurable up to sixty.</li>\n<li class=\"\"><strong>Replay rejection</strong> — has this exact signature been seen before?</li>\n<li class=\"\"><strong>Recompute and compare</strong> the HMAC, in constant time.</li>\n<li class=\"\"><strong>Scope check</strong> — is this key allowed to do this?</li>\n</ol>\n<p>Two things about that ordering.</p>\n<p><strong>The cheap rejections come first, deliberately.</strong> An HMAC is inexpensive but not free, and neither is the database lookup behind a scope check. Putting the rate limit ahead of the cryptography means a flood of garbage requests costs a Redis increment each rather than a hash and a query each. Authentication is the most exposed surface you have; it should be the cheapest thing to fail.</p>\n<p><strong>The replay check and the timestamp window are not redundant.</strong> The window bounds <em>how long</em> a captured request stays usable. The replay check means it cannot be used even twice inside that window. Neither alone is sufficient: without the window you would have to remember every signature forever, and without the replay check a request captured and resent within the same few seconds is perfectly valid. Together they give you a bounded memory and no reuse.</p>\n<p>The comparison being <strong>constant-time</strong> matters for the same reason it does anywhere else — a byte-by-byte compare that returns early leaks, through timing, how much of a guessed signature was correct, which turns forgery into a series of cheap measurements instead of an impossible search.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"scopes-because-a-leaked-key-is-a-question-of-degree\">Scopes, because a leaked key is a question of degree<a href=\"https://bt365.ai/articles/signing-trading-bot-api-requests/#scopes-because-a-leaked-key-is-a-question-of-degree\" class=\"hash-link\" aria-label=\"Direct link to Scopes, because a leaked key is a question of degree\" title=\"Direct link to Scopes, because a leaked key is a question of degree\" translate=\"no\">​</a></h2>\n<p>Keys carry one of three scopes: read, trade, or withdraw.</p>\n<p>The reason to separate them is that the consequences are not comparable. A leaked read key exposes your positions — bad. A leaked trade key lets someone move your money around inside your account — worse. A leaked withdrawal key lets them take it — final. Issuing one key that can do all three, for a bot that only needs to place orders, converts a recoverable incident into an unrecoverable one for no benefit.</p>\n<p>The practical advice is unglamorous and worth following: give a strategy a trade-scoped key, never a withdrawal-scoped one, and pin it to the IP it runs from. If the strategy is on a fixed host, the allowlist costs you nothing and removes almost the entire value of stealing the key.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-secret-at-rest-and-the-one-time-reveal\">The secret at rest, and the one-time reveal<a href=\"https://bt365.ai/articles/signing-trading-bot-api-requests/#the-secret-at-rest-and-the-one-time-reveal\" class=\"hash-link\" aria-label=\"Direct link to The secret at rest, and the one-time reveal\" title=\"Direct link to The secret at rest, and the one-time reveal\" translate=\"no\">​</a></h2>\n<p>Two implementation details that are easy to get wrong in the other direction.</p>\n<p>The secret is <strong>encrypted at rest</strong> with AES-256-GCM under a dedicated key, not stored in plaintext and not merely hashed. This differs from a password on purpose: the server has to be able to <em>recompute</em> an HMAC with the original secret, so a one-way hash would not work. Encryption is the correct tool, and the encryption key belongs in the environment, never in the database beside the ciphertext.</p>\n<p>The secret is shown <strong>once</strong>, at creation. There is no endpoint that returns it later. That is not an inconvenience to work around — it is the property that means a compromised admin session cannot enumerate every customer's signing secrets.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"failure-messages-tell-you-nothing\">Failure messages tell you nothing<a href=\"https://bt365.ai/articles/signing-trading-bot-api-requests/#failure-messages-tell-you-nothing\" class=\"hash-link\" aria-label=\"Direct link to Failure messages tell you nothing\" title=\"Direct link to Failure messages tell you nothing\" translate=\"no\">​</a></h2>\n<p>Every rejection above returns a generic message. Not \"unknown key\", not \"signature mismatch\", not \"timestamp too old\".</p>\n<p>This is deliberate and it is occasionally annoying to integrate against. The reason is that specific auth errors are an oracle: \"unknown key\" versus \"bad signature\" tells an attacker which key ids are real, and \"timestamp outside window\" versus \"bad signature\" tells them their signing is correct and only their clock is off. Each distinction hands over a piece of the puzzle.</p>\n<p>When you are integrating and getting a generic rejection, work through the list above in order — key id, source IP, clock, then signed material. It is almost always the raw body or the clock.</p>\n<hr>\n<p><em>Part of our guide to <a class=\"\" href=\"https://bt365.ai/articles/agent-integration/\">making an API usable by AI agents</a>.</em></p>\n<p><em>BT365 exposes Hyperliquid perps to external bots over an HMAC-signed API with scoped keys. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">API documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/signing-trading-bot-api-requests/",
            "title": "How do you authenticate a trading bot safely with API keys?",
            "summary": "Every exchange API uses roughly the same HMAC signing scheme, and almost every explanation of it stops at \"sign the request\". What you sign, what you check, and the order you check it in are where the security actually lives.",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "api",
                "trading-bots",
                "hmac",
                "security"
            ]
        },
        {
            "id": "https://bt365.ai/articles/what-unified-margin-actually-shares/",
            "content_html": "\n<p>Most explanations of leverage stop at the point where you open one position. The interesting behaviour starts at the second one.</p>\n<p>If you are long one market and short another and the first goes badly, what happens to the second? The answer depends entirely on whether your positions share collateral — and on a unified margin account, they do. That is the whole design, and it is rarely spelled out before you use it.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"two-ways-to-hold-collateral\">Two ways to hold collateral<a href=\"https://bt365.ai/articles/what-unified-margin-actually-shares/#two-ways-to-hold-collateral\" class=\"hash-link\" aria-label=\"Direct link to Two ways to hold collateral\" title=\"Direct link to Two ways to hold collateral\" translate=\"no\">​</a></h2>\n<p>There are broadly two arrangements.</p>\n<p><strong>Isolated margin</strong> assigns collateral to a position. You commit some amount to a long, and that amount is the most that position can lose. If it liquidates, it liquidates alone; everything else in the account is untouched. Each position is its own sealed compartment.</p>\n<p><strong>Unified margin</strong> pools the collateral. Every position draws on one balance, and liquidation is assessed against the account as a whole rather than position by position.</p>\n<p>The pooled version is more capital-efficient, and the reason is easy to state: offsetting positions stop needing separately funded margin. If you are long one asset and short a correlated one, the account's net risk is smaller than the sum of the two positions, and a unified account can recognise that. Under isolated margin you fund both compartments in full regardless.</p>\n<p>Hyperliquid's HIP-3 markets — which is how assets outside the core perp set, including equities and commodities, are traded — use the unified arrangement. BT365 treats a HIP-3 account as exactly that: one balance across markets.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-part-that-is-a-risk-not-a-feature\">The part that is a risk, not a feature<a href=\"https://bt365.ai/articles/what-unified-margin-actually-shares/#the-part-that-is-a-risk-not-a-feature\" class=\"hash-link\" aria-label=\"Direct link to The part that is a risk, not a feature\" title=\"Direct link to The part that is a risk, not a feature\" translate=\"no\">​</a></h2>\n<p>Shared collateral cuts both ways, and the downside deserves stating plainly because it surprises people.</p>\n<p>On a unified account, a loss in one market can liquidate a position in another.</p>\n<p>The mechanism is not exotic. Liquidation triggers when account equity falls below the maintenance requirement for everything currently open. An unrealised loss anywhere reduces account equity. So a position moving hard against you in one market lowers the equity supporting all of them, and the engine closes what it needs to close — which may include positions that were, on their own, doing fine.</p>\n<p>This is the reason \"I was right about the trade and still got liquidated\" is a coherent complaint on a unified account and a confused one on isolated margin. Being right about a position is not sufficient. The account has to survive.</p>\n<p>The practical implication is that on a unified account, position sizing is an account-level exercise. Sizing each position against its own thesis, one at a time, systematically overstates how much risk you can carry — because each new position is quietly borrowing from the buffer protecting every existing one.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"turning-it-on-is-an-explicit-act\">Turning it on is an explicit act<a href=\"https://bt365.ai/articles/what-unified-margin-actually-shares/#turning-it-on-is-an-explicit-act\" class=\"hash-link\" aria-label=\"Direct link to Turning it on is an explicit act\" title=\"Direct link to Turning it on is an explicit act\" translate=\"no\">​</a></h2>\n<p>One detail that matters if you are building against this rather than just trading on it.</p>\n<p>A unified account is not the default state. Trading a HIP-3 market requires enabling unified accounting on the account first, as a distinct signed action, and an account that has not done so cannot trade those markets at all.</p>\n<p>BT365 issues that action from the user's own wallet — the same wallet that holds the funds, signing for itself — rather than delegating to a subordinate API key. The distinction is not cosmetic. Our agents' wallets <em>are</em> the main accounts, so the account-level variant is the correct one; the delegated variant would be signing on behalf of an account that does not exist. It is also called defensively, both after funding and again before placing an order, because a failure to enable is not fatal on its own and is much cheaper to retry than to diagnose later.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"where-the-accounting-gets-genuinely-tricky\">Where the accounting gets genuinely tricky<a href=\"https://bt365.ai/articles/what-unified-margin-actually-shares/#where-the-accounting-gets-genuinely-tricky\" class=\"hash-link\" aria-label=\"Direct link to Where the accounting gets genuinely tricky\" title=\"Direct link to Where the accounting gets genuinely tricky\" translate=\"no\">​</a></h2>\n<p>Two bugs are worth describing, because they are the kind that produce confidently wrong numbers rather than errors — and confidently wrong numbers in balance accounting are the expensive kind.</p>\n<p><strong>A market-scoped balance query returns zero.</strong> The instinct, when you want to know how much collateral is behind a position in some market, is to ask about that market. On a unified account this returns zero, because the collateral is not held there — it is held at the account level. The balance has to be read from the account-wide view. Asking the narrower question does not produce a smaller answer; it produces a wrong one, and it looks exactly like an empty account.</p>\n<p><strong>The same collateral gets counted twice.</strong> Hyperliquid uses spot USDC as margin internally for unified accounts, and the portion locked as margin already appears inside the account-level equity figure. Add the spot balance to that equity and the locked part is counted twice. The result is an account that appears to hold more than it does — which then surfaces as phantom drift alerts and, worse, as an automatic reconciliation that over-credits realised profit against a balance that was never there.</p>\n<p>The fix is to subtract the locked portion before adding the spot balance, which is a no-op on a non-unified account and correct on a unified one. It is a single line, and the reason it is worth an article is that both the wrong version and the right version run without error. Nothing fails. One of them is just untrue.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-to-actually-do-with-this\">What to actually do with this<a href=\"https://bt365.ai/articles/what-unified-margin-actually-shares/#what-to-actually-do-with-this\" class=\"hash-link\" aria-label=\"Direct link to What to actually do with this\" title=\"Direct link to What to actually do with this\" translate=\"no\">​</a></h2>\n<p>If you are trading on a unified account, three things follow.</p>\n<p><strong>Size against the account, not the position.</strong> The buffer is shared, so every position you add reduces the room every other one has to move against you.</p>\n<p><strong>Treat correlated positions as one exposure.</strong> Unified margin gives you capital efficiency on offsetting trades, which is genuinely useful — but the flip side is that positions correlated in the <em>same</em> direction concentrate risk faster than the position count suggests.</p>\n<p><strong>Know which mode you are in before you need to.</strong> The difference between \"this position can lose its own collateral\" and \"this position can take the account with it\" is not a detail to discover during a drawdown.</p>\n<p>Unified margin is the better arrangement for most people running more than one position. It is not the safer one, and the platforms that describe it purely as a capital-efficiency win are telling you half of it.</p>\n<hr>\n<p><em>BT365 runs prediction markets, perps, and spot from one self-custodial account, including Hyperliquid's HIP-3 markets. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/what-unified-margin-actually-shares/",
            "title": "If one leveraged position goes wrong, what happens to the others?",
            "summary": "On a unified margin account, your positions are not independent bets — they draw on one shared balance. That is the feature, and it is also the risk nobody explains before you open the second position.",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "perps",
                "hyperliquid",
                "hip-3",
                "risk"
            ]
        },
        {
            "id": "https://bt365.ai/articles/when-a-market-is-delisted-before-it-resolves/",
            "content_html": "\n<p>Here is a situation with no obviously right answer, which is why it is worth writing about.</p>\n<p>You hold a position in an outcome market. It has not resolved. The venue removes the market — the event was cancelled, or the listing was migrated, or the whole set was replaced. The last price it traded at was forty cents. What is your position worth?</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-the-last-price-is-the-wrong-answer\">Why the last price is the wrong answer<a href=\"https://bt365.ai/articles/when-a-market-is-delisted-before-it-resolves/#why-the-last-price-is-the-wrong-answer\" class=\"hash-link\" aria-label=\"Direct link to Why the last price is the wrong answer\" title=\"Direct link to Why the last price is the wrong answer\" translate=\"no\">​</a></h2>\n<p>The tempting answer is forty cents. It is the last thing the market said, it is a number, and using it makes the problem disappear.</p>\n<p>It is also wrong, and the reason is worth being precise about. A contract that pays either zero or one is never actually worth forty cents at settlement. Forty cents was the price at which the last buyer and the last seller disagreed about which of the two it would be. Settling every holder at that price pays out an amount that was never a possible outcome — everyone who was going to be wrong gets paid something, and everyone who was going to be right gets shortchanged.</p>\n<p>Worse, it is a <em>silent</em> error. The position closes, a number lands in the balance, nothing fails, and no one has any particular reason to look at it again. Compared with a position that visibly refuses to settle, a confidently wrong settlement is the more expensive outcome by a wide margin.</p>\n<p>So the design question is not \"what number do we use?\" It is \"under what circumstances do we actually know the answer, and what do we do when we do not?\"</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"two-kinds-of-evidence\">Two kinds of evidence<a href=\"https://bt365.ai/articles/when-a-market-is-delisted-before-it-resolves/#two-kinds-of-evidence\" class=\"hash-link\" aria-label=\"Direct link to Two kinds of evidence\" title=\"Direct link to Two kinds of evidence\" translate=\"no\">​</a></h2>\n<p>BT365's settlement path for delisted HIP-4 markets looks for two independent sources, strongest first.</p>\n<p><strong>The strongest is mechanical.</strong> Many outcome markets are not opinions about events — they are price questions with a defined rule, such as whether an asset finished above a strike or inside a bucket at expiry. For those, the result is a function of the underlying price at expiry and the market's own bounds. Given the market's descriptor and a price reading from near expiry, the terminal value is <em>known</em> — and, critically, known <strong>regardless of the mark it happened to delist at</strong>. A bucket that was trading at fourteen cents can be settled correctly to one if the underlying finished inside it. The market being mid-priced when it vanished tells you what traders believed, not what happened.</p>\n<p><strong>The weaker one is a near-terminal price.</strong> If the market delisted at or above 0.9, or at or below 0.1, the market had effectively already decided, and that price can stand in for the outcome. This covers markets with no mechanical rule to appeal to — event and sports outcomes, where there is no underlying price to check against a strike.</p>\n<p>The second test alone was the original implementation, and it works right up until a market delists in the middle, which is exactly when you need it most. Adding the mechanical path is what turned a class of stranded positions into correctly settled ones.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"when-the-two-disagree-believe-neither\">When the two disagree, believe neither<a href=\"https://bt365.ai/articles/when-a-market-is-delisted-before-it-resolves/#when-the-two-disagree-believe-neither\" class=\"hash-link\" aria-label=\"Direct link to When the two disagree, believe neither\" title=\"Direct link to When the two disagree, believe neither\" translate=\"no\">​</a></h2>\n<p>The rule that makes this trustworthy is the one governing conflict.</p>\n<p>When both sources are available, they have to agree. If the mechanical result says one and the delisting price was 0.05, something is wrong — the descriptor was parsed incorrectly, or the price reading is off, or the mark is from the wrong market. Any of those is a bug, and none of them is resolved by picking the more convincing of the two.</p>\n<p>So a conflict does not settle. It leaves the position open and raises it for review. This is a deliberate choice to prefer a visible unresolved position over an invisible wrong one, and it is the same choice as declining to settle at the mid price, applied one level up.</p>\n<p>The same applies when neither source is available: a market that delisted mid-range with no mechanical rule to appeal to — a genuine void or cancellation, or an outcome pinned at the money — is left open and surfaced for manual repair. There is no fallback that guesses. That is the point.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"waiting-is-part-of-it\">Waiting is part of it<a href=\"https://bt365.ai/articles/when-a-market-is-delisted-before-it-resolves/#waiting-is-part-of-it\" class=\"hash-link\" aria-label=\"Direct link to Waiting is part of it\" title=\"Direct link to Waiting is part of it\" translate=\"no\">​</a></h2>\n<p>One more guard, less conceptually interesting but responsible for most of the correctness in practice.</p>\n<p>A market that has stopped appearing in the venue's data has not necessarily been delisted. It might be a temporary gap, a partial response, or a read that raced a venue-side update. Treating the first absence as a delisting means settling live positions on the strength of a missing field.</p>\n<p>So absence has to persist for a required window before it counts as a delisting, and that window is longer for markets whose expiry could not be established — if we cannot tell whether the market <em>should</em> have ended, we are more willing to keep waiting than to act. Only after the absence has held long enough does the evidence-gathering above run at all.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-this-means-if-you-hold-one\">What this means if you hold one<a href=\"https://bt365.ai/articles/when-a-market-is-delisted-before-it-resolves/#what-this-means-if-you-hold-one\" class=\"hash-link\" aria-label=\"Direct link to What this means if you hold one\" title=\"Direct link to What this means if you hold one\" translate=\"no\">​</a></h2>\n<p>Three practical points.</p>\n<p><strong>A position that stays open after a market disappears is not necessarily stuck.</strong> It may be inside the waiting window, which is doing real work. Settling faster would mean settling on weaker evidence.</p>\n<p><strong>A position that stays open for a long time may be genuinely undecidable</strong>, and that is what the manual repair path exists for. The alternative was never \"settle it correctly, sooner\" — it was \"settle it wrongly, sooner\".</p>\n<p><strong>Delisting risk is specific to venue-listed outcome markets.</strong> Where the outcome is an on-chain token, resolution is recorded on-chain and there is no listing to withdraw. That venue also has a redemption step that can fail and need retrying, so neither arrangement is strictly safer — but the failure you are exposed to is different, and worth knowing which one you have.</p>\n<p>The general principle is the one worth taking away, and it is not specific to prediction markets: in a system that moves money, an unresolved state you can see beats a resolved state that is wrong. The first is an operational problem. The second is a loss nobody notices.</p>\n<hr>\n<p><em>Part of our guide to <a class=\"\" href=\"https://bt365.ai/articles/prediction-markets/\">how prediction markets actually work</a>.</em></p>\n<p><em>BT365 runs prediction markets on both Polymarket and Hyperliquid, alongside perps and spot, from one self-custodial account. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/when-a-market-is-delisted-before-it-resolves/",
            "title": "What happens to your position if a market is delisted before it resolves?",
            "summary": "A prediction market that disappears while it is still trading at forty cents leaves a position whose value nobody has established. The tempting fix — settle it at the last traded price — is wrong, and quietly so.",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "prediction-markets",
                "hyperliquid",
                "hip-4",
                "settlement"
            ]
        },
        {
            "id": "https://bt365.ai/articles/why-withdrawals-take-minutes/",
            "content_html": "\n<p>A token transfer on a modern chain confirms in a couple of seconds and costs a fraction of a cent. So when a platform tells you a withdrawal is \"processing\" for several minutes, the natural assumption is that something is stuck.</p>\n<p>Usually nothing is stuck. The transfer really does take seconds — it is the two questions on either side of it that take the time, and both of them are questions a platform has to get right or lose your money.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-balance-has-to-stop-moving-first\">The balance has to stop moving first<a href=\"https://bt365.ai/articles/why-withdrawals-take-minutes/#the-balance-has-to-stop-moving-first\" class=\"hash-link\" aria-label=\"Direct link to The balance has to stop moving first\" title=\"Direct link to The balance has to stop moving first\" translate=\"no\">​</a></h2>\n<p>You cannot send a balance while it is still arriving.</p>\n<p>On a prediction market, closing out is not a single event. Resolved positions redeem, resting orders fill or cancel, and the proceeds land in the account over some stretch of time rather than all at once. If a withdrawal snapshots the balance in the middle of that, it moves whatever happened to be there at that instant and leaves the rest behind — which the user experiences as a withdrawal that quietly shortchanged them.</p>\n<p>So the first stage does not move anything. It watches. BT365 reads the account balance every five seconds and waits for three consecutive readings that differ by less than fifty cents before treating the number as final. Three readings rather than two, because a single pair can be stable by coincidence between two payouts. Fifty cents rather than exact equality, because insisting on an unchanging number means waiting on rounding dust forever.</p>\n<p>That watch is capped at five minutes. If the balance is still visibly moving after that, the process stops waiting and works with what it has — an unbounded wait is not patience, it is a hang.</p>\n<p>Most of the \"processing\" time you see is this stage, and it is doing something: establishing that there is nothing left to wait for.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"below-five-dollars-moving-it-destroys-it\">Below five dollars, moving it destroys it<a href=\"https://bt365.ai/articles/why-withdrawals-take-minutes/#below-five-dollars-moving-it-destroys-it\" class=\"hash-link\" aria-label=\"Direct link to Below five dollars, moving it destroys it\" title=\"Direct link to Below five dollars, moving it destroys it\" translate=\"no\">​</a></h2>\n<p>Once the balance is settled, it gets checked against a floor. Under five dollars, the withdrawal is skipped.</p>\n<p>This is not a fee we are charging. It is an observation that a withdrawal touches at least one chain, that touching a chain costs gas, and that below some amount the gas is larger than the balance — so executing the withdrawal leaves the user with less than not executing it.</p>\n<p>The part that matters is what happens next. A skipped withdrawal is marked as a completed outcome, with the reason recorded, rather than left as a failure for a recovery process to retry forever against an economic constraint that will never change. The floor is checked twice, too — once when the balance settles, and again immediately before the transfer, because a balance can drain between the two.</p>\n<p>If you are testing a platform with small real amounts, this threshold is the one to know about. It is the difference between \"nothing happened and I do not know why\" and \"this was too small to be worth moving\".</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"doing-it-exactly-once\">Doing it exactly once<a href=\"https://bt365.ai/articles/why-withdrawals-take-minutes/#doing-it-exactly-once\" class=\"hash-link\" aria-label=\"Direct link to Doing it exactly once\" title=\"Direct link to Doing it exactly once\" translate=\"no\">​</a></h2>\n<p>Now the transfer, and the problem that makes withdrawals genuinely hard.</p>\n<p>Requesting a withdrawal and sending the funds are separate steps, and a process can die between them. The dangerous version is dying <em>after</em> submitting a transfer but <em>before</em> recording that it did — because the obvious recovery, retrying, sends the money twice.</p>\n<p>The defence is to write down the intention before acting on it. BT365 persists the fact that a transfer is about to be submitted, with a timestamp, before submitting it. On restart, a record that says \"a transfer was submitted but no transaction was recorded\" is an instruction to go and look: scan the chain around that timestamp for a transfer matching the expected source and destination, and if one is there, adopt it as the transfer that already happened rather than sending a second one.</p>\n<p>The same discipline covers the request that precedes it. The withdrawal is requested from the bridge once per record, carrying an idempotency key that is generated on the first attempt and deliberately reused on every retry, so a retried request is recognised as the same request rather than treated as a new one.</p>\n<p>None of this is visible when it works. It is entirely about the case where something fails halfway.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-counts-as-it-arrived\">What counts as \"it arrived\"<a href=\"https://bt365.ai/articles/why-withdrawals-take-minutes/#what-counts-as-it-arrived\" class=\"hash-link\" aria-label=\"Direct link to What counts as &quot;it arrived&quot;\" title=\"Direct link to What counts as &quot;it arrived&quot;\" translate=\"no\">​</a></h2>\n<p>The last stage is the one most likely to be done badly, because there is an easy answer that is almost right.</p>\n<p>The easy answer is to watch the destination balance and call it delivered when it goes up by about the right amount. That works until the destination is a wallet that does other things — receives funds from another position, a manual transfer, an unrelated settlement — during the same window. Then the balance rises by roughly the expected amount for a reason that has nothing to do with this withdrawal, and the platform marks it delivered while the actual funds are still in flight.</p>\n<p>So delivery is established two ways. The bridge's own status is asked first, and when it reports the withdrawal delivered, the transaction it names is verified against the chain rather than taken on trust. Only if that is unavailable does the balance check apply — and even then a matching delta is not enough on its own. The delta has to fall inside a band of ninety-five to a hundred and ten percent of the expected amount, and the specific transfer that delivered it has to be found on-chain. If the balance moved by the right amount but no matching transfer can be identified, the withdrawal is flagged as unverified rather than completed. An unexplained credit is not proof.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-your-balance-still-shows-the-old-number\">Why your balance still shows the old number<a href=\"https://bt365.ai/articles/why-withdrawals-take-minutes/#why-your-balance-still-shows-the-old-number\" class=\"hash-link\" aria-label=\"Direct link to Why your balance still shows the old number\" title=\"Direct link to Why your balance still shows the old number\" translate=\"no\">​</a></h2>\n<p>One last piece, and it is a deliberate asymmetry worth explaining because it looks like a bug.</p>\n<p>Throughout all of this, the balance shown on the account does not change. It is zeroed at one moment only: after delivery is confirmed. Not when the withdrawal is requested, not when the transfer is submitted — after the funds are established as arrived.</p>\n<p>The alternative is to zero the balance up front, which produces a cleaner interface: the money leaves the account the moment you ask for it and there is no phantom balance sitting there during the transfer. BT365 does exactly that for perps withdrawals, where the path is mature and well understood.</p>\n<p>For prediction markets it does the opposite, and the reason is the failure case. If the balance is zeroed first and the withdrawal then fails, the platform's record says the user has nothing while the funds are still sitting on the other chain — a phantom <em>debit</em>, which is far worse than a phantom credit. Zeroing late means that on failure the balance is still there, still correct, and the recovery process has something to work with.</p>\n<p>Seeing your old balance during a withdrawal is not the system lagging. It is the system declining to write down a payout it has not yet confirmed.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-short-version\">The short version<a href=\"https://bt365.ai/articles/why-withdrawals-take-minutes/#the-short-version\" class=\"hash-link\" aria-label=\"Direct link to The short version\" title=\"Direct link to The short version\" translate=\"no\">​</a></h2>\n<p>A withdrawal that takes minutes is usually spending them on two things: waiting for a balance that is still moving, and refusing to declare victory on a delivery it has not verified. Both are indistinguishable from being slow, right up until the moment one of them saves you from a withdrawal that silently moved the wrong amount or was marked complete without arriving.</p>\n<p>The platforms that return your money in seconds every time are generally the ones that have not thought about the failure cases yet.</p>\n<hr>\n<p><em>Part of our guide to <a class=\"\" href=\"https://bt365.ai/articles/prediction-markets/\">how prediction markets actually work</a>.</em></p>\n<p><em>BT365 runs prediction markets, perps, and spot from one self-custodial account. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/why-withdrawals-take-minutes/",
            "title": "Why does withdrawing from a prediction market take minutes, not seconds?",
            "summary": "A blockchain transfer settles in seconds, so a withdrawal that takes minutes looks broken. Most of that time is spent on two questions that have nothing to do with block times — is the balance finished moving, and did the money actually arrive?",
            "date_modified": "2026-08-17T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "prediction-markets",
                "polymarket",
                "withdrawals"
            ]
        },
        {
            "id": "https://bt365.ai/articles/when-a-trading-agent-does-nothing/",
            "content_html": "\n<p>Most automated trading systems keep an excellent record of what they did and no record at all of what they decided against.</p>\n<p>That sounds like a minor omission until you try to answer a simple question about a bot that has not traded in four days: is it being patient, or is it broken? A log containing only trades gives you an empty file in both cases.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"silence-is-ambiguous\">Silence is ambiguous<a href=\"https://bt365.ai/articles/when-a-trading-agent-does-nothing/#silence-is-ambiguous\" class=\"hash-link\" aria-label=\"Direct link to Silence is ambiguous\" title=\"Direct link to Silence is ambiguous\" translate=\"no\">​</a></h2>\n<p>An automated trader that has placed no trades since Tuesday could be in any of these states:</p>\n<ul>\n<li class=\"\">Running on schedule, evaluating the market, and correctly finding nothing worth acting on.</li>\n<li class=\"\">Running, but erroring out before it reaches a decision — and swallowing the error.</li>\n<li class=\"\">Not running at all, because the scheduler stopped dispatching it.</li>\n<li class=\"\">Running and deciding to trade, but failing at execution and discarding the attempt.</li>\n</ul>\n<p>From the outside these are indistinguishable. All four produce the same thing: nothing. And the failure modes are far more urgent than the healthy case, which is precisely why \"no news is good news\" is the wrong default for a system trading your money.</p>\n<p>The fix is not more alerting. It is recording the decision itself, so that \"nothing happened\" becomes a positive statement rather than an absence.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"writing-down-the-non-event\">Writing down the non-event<a href=\"https://bt365.ai/articles/when-a-trading-agent-does-nothing/#writing-down-the-non-event\" class=\"hash-link\" aria-label=\"Direct link to Writing down the non-event\" title=\"Direct link to Writing down the non-event\" translate=\"no\">​</a></h2>\n<p>On BT365, every agent evaluation resolves to one of three outcomes, and all three are written to the agent's execution diary:</p>\n<ul>\n<li class=\"\"><strong>It traded.</strong> The action, its parameters, and the resulting order are recorded.</li>\n<li class=\"\"><strong>It held.</strong> A no-action entry is recorded, with the reason the strategy gave.</li>\n<li class=\"\"><strong>It errored.</strong> The failure is recorded as an error, not as silence.</li>\n</ul>\n<p>The middle case is the one that usually goes missing elsewhere, and it is the one that makes the other two interpretable. A diary showing two hundred hold entries and no trades describes a strategy that is running and choosing to wait. A diary showing nothing at all describes a strategy you have lost track of. Same trade count; completely different situations.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"not-every-quiet-run-is-a-decision\">Not every quiet run is a decision<a href=\"https://bt365.ai/articles/when-a-trading-agent-does-nothing/#not-every-quiet-run-is-a-decision\" class=\"hash-link\" aria-label=\"Direct link to Not every quiet run is a decision\" title=\"Direct link to Not every quiet run is a decision\" translate=\"no\">​</a></h2>\n<p>There is a distinction worth drawing carefully, because getting it wrong replaces a useful signal with noise.</p>\n<p>An agent configured to act on a four-hour timeframe still gets dispatched more often than that. Most of those runs exit immediately: it is not yet due to act. That is not a judgement about the market — the strategy never looked. Recording it as a decision would bury the real ones under entries that mean nothing more than \"the clock has not moved yet\".</p>\n<p>So evaluations that stop at the interval check are deliberately not recorded as decisions. What gets written down is the case where the strategy actually ran, looked at the market, and concluded it should do nothing. The distinction is between <em>evaluated and chose to wait</em> and <em>was not asked to evaluate</em>.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"holding-is-proof-of-life\">Holding is proof of life<a href=\"https://bt365.ai/articles/when-a-trading-agent-does-nothing/#holding-is-proof-of-life\" class=\"hash-link\" aria-label=\"Direct link to Holding is proof of life\" title=\"Direct link to Holding is proof of life\" translate=\"no\">​</a></h2>\n<p>Recording holds has a second use that only becomes obvious once you rely on it.</p>\n<p>Health monitoring on an agent fleet generally works by watching how long it has been since each agent last acted. Miss enough expected cycles and the agent is presumed broken, flagged, and eventually suspended automatically.</p>\n<p>That heuristic quietly punishes patience. An agent gated by a trading-session window, or sitting in a cooldown after a loss, is behaving exactly as configured — and to a monitor that only counts trades, it looks progressively more dead the longer it correctly does nothing. The failure mode is a system that automatically shuts off its most disciplined strategies.</p>\n<p>Because holds are recorded, they count as evidence the agent is alive. A recent hold proves the scheduler reached the strategy and the strategy reached a conclusion. The agent is gated, not dead — and the distinction is visible instead of inferred.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-the-record-is-worth-keeping-for\">What the record is worth keeping for<a href=\"https://bt365.ai/articles/when-a-trading-agent-does-nothing/#what-the-record-is-worth-keeping-for\" class=\"hash-link\" aria-label=\"Direct link to What the record is worth keeping for\" title=\"Direct link to What the record is worth keeping for\" translate=\"no\">​</a></h2>\n<p>Holds are by far the highest-volume thing an agent fleet produces and the least valuable per row, which makes retention a real design question rather than an afterthought. BT365 keeps three tiers:</p>\n<ul>\n<li class=\"\"><strong>Holds: two weeks.</strong> Enough to answer \"what has this agent been doing lately\", which is the only question they are useful for.</li>\n<li class=\"\"><strong>Trades, errors, and status changes: ninety days.</strong> Long enough to investigate a pattern across a quarter.</li>\n<li class=\"\"><strong>Deposits and withdrawals: indefinitely.</strong> These are a financial audit trail, and an audit trail with a delete policy is not one.</li>\n</ul>\n<p>The diary is owner-only. An agent's reasoning is visible to the person who deployed it and to nobody else — including on a public agent profile, which shows performance without exposing the decision log behind it.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-this-matters-when-you-are-evaluating-an-agent\">Why this matters when you are evaluating an agent<a href=\"https://bt365.ai/articles/when-a-trading-agent-does-nothing/#why-this-matters-when-you-are-evaluating-an-agent\" class=\"hash-link\" aria-label=\"Direct link to Why this matters when you are evaluating an agent\" title=\"Direct link to Why this matters when you are evaluating an agent\" translate=\"no\">​</a></h2>\n<p>The practical consequence is about how you read an agent's activity.</p>\n<p>A strategy that holds through the overwhelming majority of its evaluations is not underperforming; that is what having an entry condition means. If a strategy trades on every evaluation, its entry condition is not doing any work, and you are paying fees for the privilege. The number worth looking at is not how often an agent trades but whether the trades it does make were preceded by a stated reason for waiting — and whether the reasons look like a strategy rather than a coin flip.</p>\n<p>You can only ask that question of a system that wrote the waiting down.</p>\n<hr>\n<p><em>BT365 runs prediction markets, perps, and spot from one self-custodial account, with a full execution diary on every agent you deploy. <a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the <a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">agent documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/when-a-trading-agent-does-nothing/",
            "title": "What does a trading bot do when it decides not to trade?",
            "summary": "An idle automated trader and a broken one look identical from the outside. The difference is whether \"I considered it and declined\" is written down — and most systems never write it.",
            "date_modified": "2026-08-16T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "ai-agents",
                "automation",
                "monitoring"
            ]
        },
        {
            "id": "https://bt365.ai/articles/prediction-market-odds-dont-add-up/",
            "content_html": "\n<p>Open any prediction market with several candidates and add up the YES prices. In a\nmarket where exactly one outcome can win, those prices should sum to $1 — each YES\nshare pays out $1 if its outcome resolves true, and precisely one of them will.</p>\n<p>They frequently sum to $1.03. Sometimes $0.96.</p>\n<p>That gap is not a display bug, and it is not free money either. It is worth\nunderstanding what it actually is.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"each-outcome-is-its-own-order-book\">Each outcome is its own order book<a href=\"https://bt365.ai/articles/prediction-market-odds-dont-add-up/#each-outcome-is-its-own-order-book\" class=\"hash-link\" aria-label=\"Direct link to Each outcome is its own order book\" title=\"Direct link to Each outcome is its own order book\" translate=\"no\">​</a></h2>\n<p>The intuition that the prices \"should\" sum to $1 quietly assumes something that isn't\ntrue: that the market prices the outcomes together. It doesn't.</p>\n<p>Each outcome trades in a separate order book, against whoever happens to be buying and\nselling that particular outcome. Someone with a strong view on one candidate pushes\nthat book without touching any other. A large fill eats one book's depth and leaves the\nrest untouched. Nothing in the exchange mechanism forces the books into agreement.</p>\n<p>Polymarket groups these mutually exclusive outcomes under what it calls <strong>NegRisk</strong> —\n\"exactly one wins\" — and settles them through a dedicated exchange contract. The\ngrouping defines the payout relationship. It does not continuously enforce the pricing\nrelationship. The only thing that does is a trader taking the other side.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-the-gap-means-in-each-direction\">What the gap means in each direction<a href=\"https://bt365.ai/articles/prediction-market-odds-dont-add-up/#what-the-gap-means-in-each-direction\" class=\"hash-link\" aria-label=\"Direct link to What the gap means in each direction\" title=\"Direct link to What the gap means in each direction\" translate=\"no\">​</a></h2>\n<p><strong>Prices sum to more than $1.</strong> The market is collectively pricing the outcomes as more\nthan certain. Sell one share of every outcome and you collect, say, $1.03 today against\nthe $1 that will be paid out at resolution. Exactly one outcome pays; the other legs\nexpire worthless. The $0.03 is yours regardless of which one wins.</p>\n<p><strong>Prices sum to less than $1.</strong> The mirror image. Buy one share of every outcome for\n$0.96 and hold a basket guaranteed to be worth exactly $1 at resolution, whoever wins.</p>\n<p>Note what is absent from both descriptions: any view about who wins. The position is\nconstructed so the outcome does not matter. That is what makes it arbitrage rather\nthan a bet.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-it-persists\">Why it persists<a href=\"https://bt365.ai/articles/prediction-market-odds-dont-add-up/#why-it-persists\" class=\"hash-link\" aria-label=\"Direct link to Why it persists\" title=\"Direct link to Why it persists\" translate=\"no\">​</a></h2>\n<p>If this is visible on a public order book, why hasn't someone already taken it?</p>\n<p>Usually someone has, and what remains is the part that isn't worth taking. Several\nthings eat the gap:</p>\n<ul>\n<li class=\"\"><strong>Fees and slippage.</strong> The quoted price is for the top of the book. A basket large\nenough to be worth the effort walks down each book, and the average fill is worse\nthan the quote on every leg simultaneously.</li>\n<li class=\"\"><strong>Capital lockup.</strong> The $0.03 is realised at resolution. If that is four months out,\nthe capital is committed for four months — and the annualised return is what matters,\nnot the headline gap.</li>\n<li class=\"\"><strong>Leg risk.</strong> The arbitrage exists only if <em>every</em> leg fills. Fill four of five and\nyou no longer hold a hedged basket; you hold a directional position on the outcomes\nyou did fill, which is precisely the exposure you were trying not to have.</li>\n<li class=\"\"><strong>Resolution risk.</strong> The payoff assumes the market resolves the way its rules\ndescribe. Ambiguously worded markets are where that assumption gets tested.</li>\n</ul>\n<p>The gaps that survive are generally the ones where these costs exceed the spread. The\ninteresting question is never \"is there a gap\" — it is whether the gap clears the\ncosts of closing it.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"why-this-is-fiddly-to-trade-by-hand\">Why this is fiddly to trade by hand<a href=\"https://bt365.ai/articles/prediction-market-odds-dont-add-up/#why-this-is-fiddly-to-trade-by-hand\" class=\"hash-link\" aria-label=\"Direct link to Why this is fiddly to trade by hand\" title=\"Direct link to Why this is fiddly to trade by hand\" translate=\"no\">​</a></h2>\n<p>The mechanical problem is that the legs have to be filled close to simultaneously. Work\nthrough five outcomes manually and the prices you started from have moved by the time\nyou reach the last one. The gap you were trading may be gone, or inverted, and you are\nleft holding a partial basket.</p>\n<p>This is a reasonable thing to automate, and it is roughly the shape of BT365's\n<code>POLYMARKET_ARB_V1</code> strategy: identify NegRisk groups whose best YES asks sum\nsufficiently below $1 to clear the configured edge and slippage buffers, size each\nbasket against the agent's available balance and spend caps, and track the legs as one\nbasket so a partial fill is recognised as an open risk rather than a completed trade.</p>\n<p>None of that removes the constraints above. Capital is still locked until resolution,\nresolution risk is still real, and an automated system fills partial baskets faster\nthan a human does. It removes the coordination problem, which is a genuine but narrow\npart of the difficulty.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-useful-takeaway\">The useful takeaway<a href=\"https://bt365.ai/articles/prediction-market-odds-dont-add-up/#the-useful-takeaway\" class=\"hash-link\" aria-label=\"Direct link to The useful takeaway\" title=\"Direct link to The useful takeaway\" translate=\"no\">​</a></h2>\n<p>Odds summing to more than 100% is not a mispricing in the sense of someone being wrong\nabout the world. It is a structural artifact of mutually exclusive outcomes trading in\nindependent books, and its size is a rough measure of how much attention that market is\ngetting.</p>\n<p>A market group sitting at $1.08 is telling you something about its liquidity — that\nnobody is actively keeping its books aligned. Whether that is an opportunity or a\nwarning depends entirely on why the market is being ignored.</p>\n<hr>\n<p><em>Part of our guide to <a class=\"\" href=\"https://bt365.ai/articles/prediction-markets/\">how prediction markets actually work</a>.</em></p>\n<p><em>BT365 runs prediction markets, perps, and spot from one self-custodial account.\n<a href=\"https://bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Explore the platform</a> or read the\n<a href=\"https://docs.bt365.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">agent documentation</a>.</em></p>",
            "url": "https://bt365.ai/articles/prediction-market-odds-dont-add-up/",
            "title": "Why don't prediction market odds add up to 100%?",
            "summary": "In a market where exactly one outcome can win, the YES prices should sum to $1. They often don't — here is what the gap is, why it persists, and what it costs to close.",
            "date_modified": "2026-08-15T00:00:00.000Z",
            "author": {
                "name": "BT365 Team",
                "url": "https://bt365.ai"
            },
            "tags": [
                "prediction-markets",
                "polymarket",
                "arbitrage"
            ]
        }
    ]
}