<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dream</title>
    <description>The latest articles on DEV Community by Dream (@quant001).</description>
    <link>https://dev.to/quant001</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3732259%2Fd089f60c-f2a4-4ba4-a84d-6c79ada78de3.png</url>
      <title>DEV Community: Dream</title>
      <link>https://dev.to/quant001</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/quant001"/>
    <language>en</language>
    <item>
      <title>No Explanations, Just Decisions: Plugging Jev into a Perpetual Strategy — Run It First, Talk Later</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 23 Sep 2026 02:46:37 +0000</pubDate>
      <link>https://dev.to/quant001/no-explanations-just-decisions-plugging-jev-into-a-perpetual-strategy-run-it-first-talk-later-2gep</link>
      <guid>https://dev.to/quant001/no-explanations-just-decisions-plugging-jev-into-a-perpetual-strategy-run-it-first-talk-later-2gep</guid>
      <description>&lt;p&gt;Last year, when Alpha Arena was making the rounds, several AI models were put into live trading environments, and every gain or loss sparked another round of discussion. Quant traders paid especially close attention: after spending so much time researching models and strategies, it was fascinating to see someone simply give the models capital and let them trade.&lt;/p&gt;

&lt;p&gt;This year, Jev arrived. I recently came across an open-source project that connects Jev to Kuru's order book on Monad and makes a trading decision roughly once every 300 milliseconds, in step with Monad's block cadence. Both the code and front end are open source. One widely circulated screenshot was especially eye-catching: on one side, people were impressed by the technology; on the other, it said something along the lines of "ran for 14 hours and lost more than 300%." But the screenshot was marked as a dry run, meaning the fills were simulated, and that percentage also depends on the P&amp;amp;L calculation method. It should not be interpreted as a real account literally losing more than three times its initial capital.&lt;/p&gt;

&lt;p&gt;Alpha Arena coverage · On-chain trading project source code&lt;/p&gt;

&lt;p&gt;After watching all that, I naturally wanted to try it myself. Last year AI trading went viral; this year we have a model designed specifically to make structured judgments. As quant traders, it is hard not to plug something like that into our own strategy and see what happens.&lt;/p&gt;

&lt;p&gt;That led to this Binance USDT-margined perpetual strategy running on FMZ. It is currently being tested live on ONE_USDT. Recently I have mainly used it to observe the kind of high-volatility, fast-moving market conditions traders often call "monster-coin" behavior. In these markets, changes in the order book and short-term price action are easier to see, and it is also easier to spot where the strategy and execution logic fail to cooperate.&lt;/p&gt;

&lt;p&gt;Below, I will walk through how Jev was integrated, why the strategy was modified along the way, and what became visible only after it started running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let Jev Watch the Order Book, but Keep the Division of Labor Clear
&lt;/h2&gt;

&lt;p&gt;Jev's role can be summarized in one sentence: give it the current state, ask several clearly defined questions, and let it return choices, probabilities, or scores that the program can use downstream. For this experiment, that interface is enough.&lt;/p&gt;

&lt;p&gt;Short-term traders are familiar with situations like these: the bid side looks thick, yet the price refuses to rise; offers keep getting lifted, but new sell orders keep appearing; the market is quiet, then several aggressive buys hit in succession and the price starts stepping higher. Each of these observations can be turned into an indicator on its own. The difficult part is deciding what they mean when several appear at the same time.&lt;/p&gt;

&lt;p&gt;What I wanted to test was whether these clues could be organized into a clean market state and handed to Jev as several specific judgments.&lt;/p&gt;

&lt;p&gt;Official quick start&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffdoob0gaccifin3ovj8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffdoob0gaccifin3ovj8j.png" alt=" " width="800" height="463"&gt;&lt;/a&gt;&lt;br&gt;
The input sent to Jev mainly includes the order book, recent trades, short-window price changes, and volatility. Missing data is explicitly marked as missing; "we did not receive the data" must not be silently converted into "buying and selling pressure is balanced."&lt;/p&gt;

&lt;p&gt;Account positions, order sizes, and trading restrictions are kept local. That way, when a trade does not happen, we can still determine whether the model failed to produce a strong enough signal or whether the order was blocked by fees, position limits, stale market data, or another execution rule.&lt;/p&gt;

&lt;p&gt;For execution, the strategy uses passive Maker orders in the hope of getting a better fill price. But anyone who has placed Maker orders knows the trade-off: being right does not guarantee a fill, and when you finally do get filled, it may be because the market has already started moving against you. Most of the later changes revolved around this problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Most Useful Change Was Splitting "Should I Trade?" into Separate Questions
&lt;/h2&gt;

&lt;p&gt;The easiest first attempt is to put market data, fees, and positions into one request and ask, "Should I buy, sell, or wait?" You get an answer, but if the model keeps saying "wait," it is hard to know why.&lt;/p&gt;

&lt;p&gt;Does it expect the price to fall? Does it think the upside is too small to cover fees? Is it worried about chasing the move? Is the current position affecting the answer?&lt;/p&gt;

&lt;p&gt;After reading How to Use Jev: Three Question Primitives and Layered Thresholds and comparing that approach with the official documentation, the division of labor became much clearer. The most useful idea was to split a large trading decision into several smaller judgments and then combine the answers in code.&lt;/p&gt;

&lt;p&gt;The snippets below are taken from the core strategy logic. Some field descriptions, logs, and surrounding checks are omitted for readability.&lt;/p&gt;

&lt;p&gt;Official design patterns&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tkkoplp0wmwuq9apskk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tkkoplp0wmwuq9apskk.png" alt=" " width="800" height="492"&gt;&lt;/a&gt;&lt;br&gt;
First, ask about direction and magnitude with a Choice question. For example: 30 seconds from now, is the return more likely to fall into a large decline, small decline, narrow-range move, small rise, or large rise?&lt;/p&gt;

&lt;p&gt;The program defines these ranges in advance, and the model assigns a probability to each one.&lt;/p&gt;

&lt;p&gt;Why ask about magnitude as well as direction? Because we have all seen trades where the direction was correct but there was almost nothing left after costs. Knowing only up or down is not enough.&lt;/p&gt;

&lt;p&gt;The strategy asks the same type of question over several horizons. The main trading horizon is 30 seconds; the other horizons are kept mainly for comparison.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;forecastHorizons().forEach(function (h) {
    questions["market_" + h] = {
        type: "choice",
        instructions: {
            question: "At " + h / 1000 +
                " seconds after state.timestamp, which interval will " +
                "R = 10000*(future mid price/state.mid - 1) fall into?"
        },
        criteria: criteria  // Five predefined return intervals
    };
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, ask whether getting filled is likely to hurt. This uses a Score question.&lt;/p&gt;

&lt;p&gt;A common Maker-order problem on the buy side is that the order refuses to fill, and then the moment it finally fills, the price drops. On the sell side, the mirror image happens: you finally sell, and the price keeps rallying.&lt;/p&gt;

&lt;p&gt;So I evaluate the two sides separately and ask how severe the adverse-selection risk would be over the next few seconds after a passive fill.&lt;/p&gt;

&lt;p&gt;The score levels should not be vague labels such as "low," "medium," and "high." Each level needs a concrete description: do aggressive trades and price movement point in the same adverse direction? Is there only one warning sign, or are several independent clues pointing the same way?&lt;/p&gt;

&lt;p&gt;In the snippet below, book is the best quote on that side. adverse means a decline for a buy order and a rise for a sell order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;questions["toxicity_" + side] = {
    type: "score",
    instructions: {
        question: "If an order at " + book + " is filled, how severe is the risk " +
            "that price moves " + adverse + " by more than " +
            bounds.neutral + " bps within 5 seconds?"
    },
    criteria: [
        "Microprice, aggressive trades, and the realized price path do not point " + adverse,
        "Evidence is conflicting, or there are too few trades / too little depth to judge",
        "Microprice shifts toward " + adverse + " and aggressive flow keeps hitting that side",
        "Microprice, aggressive trades, and the realized price path all point " + adverse
    ]
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to ask whether the order is likely to fill at all. For that I use Noul.&lt;/p&gt;

&lt;p&gt;Noul returns a number between 0 and 1 representing the model's estimated probability that a yes/no statement is true. A value close to 0.5 means the model has no strong preference between yes and no.&lt;/p&gt;

&lt;p&gt;Here, that estimate is used to filter entry candidates that appear unlikely to fill and also helps determine the quote placement.&lt;/p&gt;

&lt;p&gt;However, the model does not know our true queue position, and a single order-book snapshot cannot reconstruct the full sequence of cancellations and fills. So the number has to be checked against actual order records later rather than treated as ground truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;questions["fill_" + side] = {
    type: "noul",
    instructions: {
        question: "If we place at " + book + ", will the order be filled within the next " +
            Math.round(CFG.signalTtlMs / 1000) + " seconds?"
    },
    criteria: {
        "true": "The passive order will be hit by the opposing side",
        "false": "The passive order will remain unfilled"
    }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting these three question types into one request gets much closer to the way a trader actually thinks while watching the book:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What do direction and magnitude look like?&lt;/li&gt;
&lt;li&gt;If I place the order, is it likely to fill?&lt;/li&gt;
&lt;li&gt;If it fills, am I likely to regret the fill immediately?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the answers come back, the program uses them in different places: whether the directional signal is strong enough to open a position, whether fill probability is too low, and whether higher adverse-selection risk should move the quote to a more favorable price.&lt;/p&gt;

&lt;p&gt;Splitting the decision this way also gives us something concrete to debug. If quoting is too aggressive, inspect the risk score and quote-placement logic. If no orders ever appear, inspect which condition keeps failing.&lt;/p&gt;

&lt;p&gt;The three question primitives&lt;/p&gt;

&lt;h2&gt;
  
  
  When the Market Gets Wild, a Fixed Ruler Stops Working
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd6js1f5nfsnoxw1vty7u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd6js1f5nfsnoxw1vty7u.png" alt=" " width="800" height="510"&gt;&lt;/a&gt;&lt;br&gt;
While running the strategy on ONE, one thing became obvious: short-term market features are easier to see. Changes in buying and selling pressure, bursts of trades, sharp rallies, and pullbacks all become visible over relatively short windows.&lt;/p&gt;

&lt;p&gt;But large moves also magnify the weaknesses in passive quoting and exits.&lt;/p&gt;

&lt;p&gt;The first thing that needed to change was the ruler used to define price-move magnitude.&lt;/p&gt;

&lt;p&gt;A move that counts as a meaningful rise in a quiet market may be nothing more than ordinary noise in a token that is moving violently. If every instrument uses the same fixed boundaries, the model may still answer "large rise," "small rise," or "sideways," but those labels correspond to very different market conditions.&lt;/p&gt;

&lt;p&gt;The strategy previously spent long periods doing nothing. After checking the logic, the reason was fairly straightforward: on one hand, we were asking the model to predict a fairly large move over a very short horizon; on the other, we also required the model to be quite confident about it. When those conditions do not match the current volatility regime, waiting for a long time without a trade is perfectly normal.&lt;/p&gt;

&lt;p&gt;The return-interval boundaries were therefore changed to adapt to recent volatility, while preserving a floor based on fees and safety buffers.&lt;/p&gt;

&lt;p&gt;The lines below determine the initial boundary for a "large move." Upper and lower constraints are applied afterward. The volatility scaling is only an approximation, especially in markets with jumps and abrupt reversals; it should not be interpreted as a precise forecast of future move size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VOL.sigmaHorizonBps = VOL.sigma1mBps *
    Math.sqrt(CFG.signalHorizonMs / 60000);

var z = inverseNormal(1 - CFG.tailTargetRate);

var outer = Math.max(
    z * VOL.sigmaHorizonBps,
    minimumEdgeBps()
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cancel-and-requote logic has the same problem. If the strategy cancels every time the price moves slightly, it may look responsive while constantly throwing away queue position.&lt;/p&gt;

&lt;p&gt;So the requote distance also needs to account for volatility and the instrument's minimum tick size.&lt;/p&gt;

&lt;p&gt;I will not go through every parameter one by one. Price precision, quantity precision, and minimum-order requirements should be read from the exchange whenever possible. Distances that can be estimated from live market data should adapt automatically. The human mainly decides what to trade, how much to trade each time, and the maximum allowed position.&lt;/p&gt;

&lt;p&gt;That reduces manual configuration mistakes when switching instruments, although the actual behavior still needs to be evaluated instrument by instrument.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bearish Model Does Not Automatically Mean "Open a Short Now"
&lt;/h2&gt;

&lt;p&gt;Another useful idea from the reference material is that different actions should have different thresholds.&lt;/p&gt;

&lt;p&gt;Suppose we already hold a long position and the model starts leaning bearish. That evidence may not yet be strong enough to justify opening a new short position, but it may already be strong enough to reduce part of the long.&lt;/p&gt;

&lt;p&gt;The code therefore separates the directional probability threshold for opening a new position from the threshold for reducing an existing position.&lt;/p&gt;

&lt;p&gt;Opening new exposure requires a stronger judgment. The strategy also checks whether the probability distribution is too diffuse and whether the passive fill probability is too low.&lt;/p&gt;

&lt;p&gt;Reducing exposure uses a lower directional threshold.&lt;/p&gt;

&lt;p&gt;The lines below show the core threshold-selection logic. Market-data validity, signal freshness, and the other safeguards still run around this block.&lt;/p&gt;

&lt;p&gt;Official confidence documentation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var required = plan.reducing
    ? CFG.reduceThreshold
    : CFG.openThreshold;

decision.requiredProbability = required;

if (tail &amp;lt; required) {
    decision.reasonCode = "probability";
    return decision;  // Keep the default action: wait
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard follows the same division of labor.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Signals &amp;amp; Decisions&lt;/strong&gt; panel puts the directional probability, entry and reduction thresholds, fill estimates for both sides, and adverse-selection scores in one place. The final column states why the current decision was made.&lt;/p&gt;

&lt;p&gt;With that table, we can trace a local execution decision back to the model outputs instead of guessing why the system keeps waiting.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Prediction Probabilities by Horizon&lt;/strong&gt; section expands the different time scales so we can compare the model's view over short and slightly longer horizons.&lt;/p&gt;

&lt;p&gt;One distinction matters here: the directional probabilities correspond to specific market events, while confidence measures how concentrated the answer distribution is. Confidence should not be interpreted directly as a trading win rate.&lt;/p&gt;

&lt;p&gt;Likewise, a bullish or bearish prediction still has to pass the local execution checks before it can become an order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fafnbls93b4gw2h406suu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fafnbls93b4gw2h406suu.png" alt=" " width="800" height="174"&gt;&lt;/a&gt;&lt;br&gt;
Figure 1: Signal section captured from the live strategy dashboard. The upper section checks trading conditions; the lower section compares market judgments across multiple horizons. Values update while the strategy is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Once It Is Running, You Need to See What It Is Actually Doing
&lt;/h2&gt;

&lt;p&gt;The strategy is already running live, and the dashboard is mainly there to help identify problems.&lt;/p&gt;

&lt;p&gt;For example, when the model makes a prediction, once the forecast horizon has elapsed the strategy records which interval the realized price actually landed in. This appears in the &lt;strong&gt;Prediction Validation&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;For each horizon, it shows the sample count and five-class accuracy. It also includes a simple baseline that always predicts the narrow-range class.&lt;/p&gt;

&lt;p&gt;A Brier score is displayed alongside the accuracy figures to evaluate the error of the full probability distribution. Non-overlapping samples are also tracked to reduce the problem of many consecutive forecasts covering nearly the same market interval.&lt;/p&gt;

&lt;p&gt;Each metric tells us something different. No single number should be used to judge the entire strategy. Looking at them together makes it easier to identify which horizons and which market regimes deserve deeper investigation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm29th53b7ohgr536zct0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm29th53b7ohgr536zct0.png" alt=" " width="799" height="266"&gt;&lt;/a&gt;&lt;br&gt;
Figure 2: Prediction-validation panel. It uses the same return-interval definitions to compare the model's judgments with realized price action, while retaining a simple baseline and non-overlapping samples for later review.&lt;/p&gt;

&lt;p&gt;Passive fill quality needs to be evaluated separately, so the dashboard also contains a Post-Fill Price Performance section.&lt;/p&gt;

&lt;p&gt;It records the price move at several timestamps after a fill is detected. After a buy, a price rise is considered favorable; after a sell, a decline is favorable. The opposite direction is adverse.&lt;/p&gt;

&lt;p&gt;If the market frequently moves against the order immediately after a fill, we can go back and check whether the quote was too aggressive or whether the adverse-selection score actually helped.&lt;/p&gt;

&lt;p&gt;This measurement starts from the time the local program detects the fill. It does not subtract fees. Its purpose is to observe whether passive Maker orders are systematically exposed to adverse selection, not to report the net P&amp;amp;L of each trade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxkpuvjfds7vuklr66zrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxkpuvjfds7vuklr66zrg.png" alt=" " width="799" height="266"&gt;&lt;/a&gt;&lt;br&gt;
Figure 3: Post-fill price-performance panel. It keeps the short-term price path after each fill so it can be compared with the earlier risk score and quote-selection logic.&lt;/p&gt;

&lt;p&gt;When debugging, I also compare the &lt;strong&gt;Order Book &amp;amp; Order Flow, Data &amp;amp; Request Health&lt;/strong&gt;, and order-history sections.&lt;/p&gt;

&lt;p&gt;The first tells us what the model was seeing at the time. The health checks tell us whether the data was fresh and whether requests were failing. Position and order records tell us how far the execution actually progressed.&lt;/p&gt;

&lt;p&gt;If there was a signal but no order, first inspect the decision reason and the data state. If an order was placed but never filled, then inspect the quote and the fill-probability estimate.&lt;/p&gt;

&lt;p&gt;Read this way, each dashboard panel maps back to a specific piece of strategy logic described earlier.&lt;/p&gt;

&lt;p&gt;There are still important things missing from the current implementation.&lt;/p&gt;

&lt;p&gt;After a position exceeds its time budget, the strategy attempts to place a Reduce Only Maker order, but placing the order does not mean the position will be closed immediately. A complete hard stop-loss, daily-loss circuit breaker, and liquidation-distance check have not yet been added.&lt;/p&gt;

&lt;p&gt;As I continue running the strategy, what I most want to understand is where Jev provides useful information about order-book states, and how much of that value survives the transition from a model judgment to a real passive order.&lt;/p&gt;

&lt;p&gt;By keeping the input, judgment, execution, and validation records, there is something concrete to review afterward—and a much clearer answer to what should be changed next.&lt;/p&gt;

</description>
      <category>quanttrading</category>
      <category>cryptotrading</category>
      <category>perpetualfutures</category>
      <category>trading</category>
    </item>
    <item>
      <title>FMZ Web3 in Practice: Lessons from New-Pool Pitfalls — Building an Automated V3/V4 LP Strategy</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 23 Sep 2026 01:54:53 +0000</pubDate>
      <link>https://dev.to/quant001/fmz-web3-in-practice-lessons-from-new-pool-pitfalls-building-an-automated-v3v4-lp-strategy-14l</link>
      <guid>https://dev.to/quant001/fmz-web3-in-practice-lessons-from-new-pool-pitfalls-building-an-automated-v3v4-lp-strategy-14l</guid>
      <description>&lt;p&gt;In the previous article, we connected FMZ to Robinhood Chain and built a Uniswap V4 new-pool radar. Once a new pool appeared, the program could quickly capture information such as the trading pair, fee tier, and liquidity. That part went smoothly. With the pools now discoverable, the next question was obvious: could we provide liquidity when trading activity is high and earn some fees?&lt;/p&gt;

&lt;p&gt;So I continued modifying the code, adding simulated entries, position valuation, and exit logic. The first results looked quite good—the simulated equity curve climbed almost all the way up. That was tempting enough that I went on to add approvals, token purchases, liquidity provision, liquidity removal, and token selling, completing the live-trading workflow.&lt;/p&gt;

&lt;p&gt;After launching the bot, I was excited to watch the returns keep rising. Instead, I quickly learned that there are bad actors out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Three Tokens, Two Could Not Be Sold
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqcmn02tnr0bjoxc2a01v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqcmn02tnr0bjoxc2a01v.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Of the first three tokens I tried, two became impossible to sell after the purchase. I had barely used ETH to buy the target token when the liquidity on the other side was removed. It almost felt as if someone had been waiting specifically for my order. I had entered the pool hoping to earn trading fees, but before earning anything, I was already left holding tokens in the wallet.&lt;/p&gt;

&lt;p&gt;My first reaction was that I had run into a honeypot token. But after checking the on-chain transactions, the causes still needed to be separated clearly. A token contract that restricts selling and a pool whose liquidity has been drained can both make a token effectively unsellable, but they are not the same problem.&lt;/p&gt;

&lt;p&gt;The perp case was especially obvious. Roughly two seconds after the purchase, the original LP removed its liquidity. Our own LP position had not even been established yet. I later tried reducing the sell amount, but the quote still reported insufficient liquidity. Increasing slippage and repeating approvals did not help—the counterparty liquidity required for the swap was simply no longer there.&lt;/p&gt;

&lt;p&gt;Looking back at the simulation curve at that point made it clear that I had celebrated too early. A simulation can value assets using the pool price, but whether that price is actually executable and whether sufficient liquidity will still exist at exit must be validated separately. Appreciation in the target token also cannot simply be counted as LP fee income. There is a full execution process between seeing a profit on paper and actually getting the capital back.&lt;/p&gt;

&lt;p&gt;So I first changed the accounting model. Estimated income, position valuation, realized LP fees, and final cash flows are now recorded separately. If the token has not yet been sold or the transaction has not been confirmed, the round cannot be considered successfully completed and profitable.&lt;/p&gt;

&lt;p&gt;For incomplete transactions, the program also preserves execution progress. After a restart, it first checks previously submitted transactions so that a temporarily missing receipt is not misclassified as a failure and the same purchase is not sent again.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Stop Racing into Brand-New Pools and Look for Pools That Can Keep Trading
&lt;/h2&gt;

&lt;p&gt;After taking that loss, I changed direction. A newly created pool has very little history, and its liquidity can disappear at any moment. No matter how carefully we check before buying, there is no guarantee that the state will not change one second later.&lt;/p&gt;

&lt;p&gt;Since the goal is to earn trading fees continuously, it makes more sense to start with pools that have already been operating for some time and still have ongoing trading activity. At least then, the main challenge is no longer fighting for the first few seconds after a pool is created.&lt;/p&gt;

&lt;p&gt;The first version of the filter, however, was too strict: the pool had to be at least seven days old, TVL had to be at least $500,000, the fee tier could not exceed 0.3%, and additional volume and price conditions were layered on top. Each rule made sense individually, but after combining them, very few V4 pools survived the filter. The bot was certainly cautious—it just rarely opened a position.&lt;/p&gt;

&lt;p&gt;After breaking the problem down, I found that some pools were excluded because the thresholds were too strict, some were never discovered by the program, and some belonged to protocols that were not yet supported.&lt;/p&gt;

&lt;p&gt;So while loosening the pool-age, liquidity, and short-term volume thresholds, I also added V3 support. In practice, V3 contained more pools worth further review. Focusing only on V4 unnecessarily narrowed the candidate universe.&lt;/p&gt;

&lt;p&gt;Adding V3 did not simply mean inserting pool addresses into a list. The V3 workflow also required identity verification, quoting, approval, entry, position creation, and exit logic.&lt;/p&gt;

&lt;p&gt;Here, a “stable pool” means a pool whose trading activity and liquidity are relatively stable. It does not mean that we only trade stablecoin pairs. What matters is whether continuing volume can plausibly support fee income and whether capital can enter and exit under reasonable conditions.&lt;/p&gt;

&lt;p&gt;The current thresholds—such as a minimum pool age of 12 hours and TVL of $20,000—are only research settings for this stage and still need to be calibrated against live operating data.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. The Pool Is Clearly Visible on the Website, but the Program Cannot Find It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqmhq6phpl2ag3qz8787.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqmhq6phpl2ag3qz8787.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
After expanding the search scope, another frustrating problem appeared: some pools were clearly visible when opened manually in the browser, yet the program never placed them into the candidate list. MOO was one example.&lt;/p&gt;

&lt;p&gt;Early on, when we queried related pools through the ETH, WETH, and USDG token addresses, each API call returned only a limited number of results, and MOO was not among them. When we queried using the MOO token address itself, however, the pool appeared immediately.&lt;/p&gt;

&lt;p&gt;The problem was simple: it had never entered our pool directory in the first place. No matter how sophisticated the downstream filtering logic was, the pool could never reach that stage.&lt;/p&gt;

&lt;p&gt;We later added a hot-pool list and also tried reconstructing the historical directory from V3 and V4 pool-creation events. Technically, this works. But the node plan we were using allowed each eth_getLogs request to cover only ten blocks, making a historical backfill expensive in both request count and waiting time.&lt;/p&gt;

&lt;p&gt;For a new-pool radar, following the latest events forward is a good fit. But once the goal becomes finding already-active pools, rebuilding the full directory from historical blocks turns into a separate infrastructure project.&lt;/p&gt;

&lt;p&gt;After going around in circles, we eventually adopted the &lt;strong&gt;KyberSwap Earn pool-directory API&lt;/strong&gt;. Without specifying MOO by name or address, the pool appeared in ordinary pagination.&lt;/p&gt;

&lt;p&gt;From that point on, we separated pool discovery, market statistics, and on-chain verification:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flualy8fpi9jpth3y8g25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flualy8fpi9jpth3y8g25.png" alt=" " width="695" height="148"&gt;&lt;/a&gt;&lt;br&gt;
Each directory page retrieves 100 pools. After completing one full pass, the program waits 30 minutes and then paginates again, merging and refreshing the results.&lt;/p&gt;

&lt;p&gt;Market statistics come directly from the available API fields, while on-chain requests are concentrated on candidate verification and position management. This way, the bot does not need to spend its first hour accumulating its own market history after startup, nor does it need to repeatedly scan historical blocks just to filter existing pools.&lt;/p&gt;

&lt;p&gt;Pagination itself also caused problems. The API sorts pools by real-time TVL, so the order can change while the program is moving through pages. As a result, the number of unique pools after deduplication may not exactly match the total reported by the API.&lt;/p&gt;

&lt;p&gt;If the program rigidly requires those numbers to match, it may keep restarting from page one forever. We therefore retained discrepancy logging, retry handling, and periodic refreshes, while separating the idea of “this pagination pass has finished” from “the entire chain has been covered completely.”&lt;/p&gt;

&lt;p&gt;The API provides a much broader candidate universe, but indexing delays and omissions can still occur, so continued rechecking is necessary.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Testing with Only 3U — So How Did Gas Reach 2.33U?
&lt;/h2&gt;

&lt;p&gt;Once the pools could be discovered reliably, the next step was to measure costs seriously.&lt;/p&gt;

&lt;p&gt;During simulation, gas was mostly treated as an estimate. In live execution, however, one LP cycle can involve wrapping ETH, approvals, swaps, adding liquidity, removing liquidity and collecting fees, selling back, and unwrapping. V3 and V4 also use different approval paths, so it is not accurate to estimate both with a rough fixed number of transactions.&lt;/p&gt;

&lt;p&gt;We therefore wrote a standalone fee-measurement script. Using a principal budget of roughly 3U (~$3), it performed short-duration entry and exit cycles in pools pairing USDG with ETH/WETH.&lt;/p&gt;

&lt;p&gt;The tests were designed to answer two questions first: can the entire workflow complete successfully, and how much does each individual step actually cost?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7zwh7pejk5r0fnsx9f2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7zwh7pejk5r0fnsx9f2.png" alt=" " width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gas is calculated from the actual usage and gas price recorded in the transaction receipt rather than from the configured estimate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Actual gas expenditure, in wei
var gasWei =
    BigInt(receipt.gasUsed) *
    BigInt(receipt.effectiveGasPrice);

totalGasWei += gasWei;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0onyjhibg0xrr2unuiet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0onyjhibg0xrr2unuiet.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
In the tests, the first complete V3 cycle consumed about 0.326U in gas, while a later V3 cycle reached approximately 2.33U. That result was surprising: with just a little over three dollars in principal, how had gas suddenly climbed above two dollars?&lt;/p&gt;

&lt;p&gt;Comparing the transactions showed that the later round required more approval steps and also ran during a period of significantly higher gas prices.&lt;/p&gt;

&lt;p&gt;In both rounds, wrapping ETH used exactly 57,647 gas, yet the actual cost was approximately 0.0139U in one round and 0.1545U in the other.&lt;/p&gt;

&lt;p&gt;Even the same operation can cost very different amounts. We cannot simply reuse the previous result as the next transaction's expected cost.&lt;/p&gt;

&lt;p&gt;A subsequent standalone V4 test consumed about 0.226U for the full cycle. Again, that number is only a cost sample from that particular execution.&lt;/p&gt;

&lt;p&gt;With these measurements available, the production code was changed to reserve gas separately for each operation—wrapping, approval, swapping, LP creation, and liquidity removal—while also considering the current gas price and a safety buffer.&lt;/p&gt;

&lt;p&gt;For token operations that have not yet been measured in live conditions, the program still keeps a relatively conservative fallback budget. What we are calibrating here is the budgeting model. Actual expenditure is still calculated from receipts, rather than hard-coding one historical test result into the strategy.&lt;/p&gt;

&lt;p&gt;The accounting scope also needs to be clear. In the standalone fee-measurement script, the roughly 3U principal was tracked separately from gas. In the production strategy, the current 3U budget per position already includes entry gas and execution reserves.&lt;/p&gt;

&lt;p&gt;The net change over a full cycle also mixes swap loss, price movement, and LP income. The amount remaining after subtracting gas cannot simply be labeled “swap fees.”&lt;/p&gt;

&lt;p&gt;These rounds successfully validated the V3 and V4 capital-in/capital-out workflow and fee accounting, but they were not long-term profitability tests.&lt;/p&gt;

&lt;p&gt;With such a small position, fixed execution costs represent a very large percentage of capital. Increasing the principal may dilute those fixed costs, but it will also change price impact, the strategy's share of liquidity, and the amount of capital at risk. Those effects still need to be evaluated separately.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Estimate What This Position Can Earn Before Deciding Whether to Open It
&lt;/h2&gt;

&lt;p&gt;A pool can have a lot of trading activity without necessarily generating much fee income for our position.&lt;/p&gt;

&lt;p&gt;For a concentrated-liquidity position, revenue also depends on the selected price range, current active liquidity, and the positions of other LPs. The current model therefore estimates our share of active liquidity before estimating fee income instead of directly using the APR displayed on a webpage.&lt;/p&gt;

&lt;p&gt;For volume, the model uses the smaller of the one-hour trading volume and the six-hour average hourly trading volume. This avoids making an overly optimistic decision based only on a recent one-hour spike.&lt;/p&gt;

&lt;p&gt;After combining the fee tier, the share of fees distributable to LPs, and the position's expected liquidity share, the estimated fee income is compared with the full round-trip cost. The core logic can be simplified as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Revenue and costs are normalized to ETH
var hourlyVolumeUSD = Math.min(volume1hUSD, volume6hUSD / 6);
var share = myLiquidity / (poolLiquidity + myLiquidity);

var expectedFeesETH = hourlyVolumeUSD / ethUSD
    * feeRate * lpFeeShare * share * 0.5;

var totalCostETH =
    quoteLossETH + slippageReserveETH + gasBudgetETH;

var requiredFeesETH =
    totalCostETH * 1.5 + principalETH * 0.01;

var incomeConditionMet = expectedFeesETH &amp;gt;= requiredFeesETH;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The revenue side applies a &lt;strong&gt;50% discount factor&lt;/strong&gt;. On the cost side, expected fees must cover &lt;strong&gt;1.5× the total cost&lt;/strong&gt;, with an additional &lt;strong&gt;1% of principal&lt;/strong&gt; reserved for inventory risk.&lt;/p&gt;

&lt;p&gt;Even after that condition is met, the position must still pass capital and execution checks.&lt;/p&gt;

&lt;p&gt;These coefficients are still model assumptions and are not supported by a sufficiently large sample of profitable trades. The discount factor and buffers are used to leave room between historical trading activity and future realized income—not to guarantee that a position will cover its costs after entry.&lt;/p&gt;

&lt;p&gt;After a position is opened, we also need to check whether the original assumptions are actually being realized. Price deviation, net loss, and declining liquidity can all trigger an exit. Once a position has been held for ten minutes, the strategy begins checking whether actual fee accumulation is materially lagging behind expectations.&lt;/p&gt;

&lt;p&gt;At entry, we rely on estimates. Once the position is live, actual data is available, so that data should be used to continue validating the thesis.&lt;/p&gt;

&lt;p&gt;The earlier design of forcing an exit after a fixed 30 minutes was removed. If pool conditions have not changed, repeatedly entering and exiting simply because a timer expired adds unnecessary costs. If conditions have already deteriorated, the strategy should not wait for the timer either.&lt;/p&gt;

&lt;p&gt;One hour is still used as the revenue-evaluation window, but it is no longer treated as a mandatory holding period.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. What to Optimize Next After Completing This Version
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifytd7reizo3oaifl34d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifytd7reizo3oaifl34d.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
At this point, the complete strategy workflow is in place: the API supplies candidates, V3 and V4 use separate execution paths, expected revenue and costs are evaluated before entry, positions are continuously checked while open, and exits are settled using actual cash flows.&lt;/p&gt;

&lt;p&gt;Unconfirmed transactions and unresolved assets remain in the ledger. After a restart, the strategy first resumes existing workflows so that an interruption does not leave behind an untracked position.&lt;/p&gt;

&lt;p&gt;In addition to small-scale full-cycle live tests, code checks now cover directory-pagination anomalies, pool-identity mismatches, quote failures, insufficient budgets, delayed transaction receipts, and restart recovery.&lt;/p&gt;

&lt;p&gt;The priority is to make sure the program stops opening new positions when conditions are not satisfied and, when transaction state is unclear, verifies already-submitted transactions before sending anything again.&lt;/p&gt;

&lt;p&gt;Only after the execution process is sufficiently robust does it make sense to evaluate the real effectiveness of pool selection and the revenue model.&lt;/p&gt;

&lt;p&gt;Future adjustments will therefore revolve around actual operating data. In particular, we need to avoid a common trap: lowering thresholds simply because the strategy is not opening positions, then immediately tightening them after a loss, until the system is just oscillating between parameter sets.&lt;/p&gt;

&lt;p&gt;Every change should correspond to a specific problem and have a measurable comparison metric:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wgs60ip1nifvdh4fx7l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wgs60ip1nifvdh4fx7l.png" alt=" " width="800" height="177"&gt;&lt;/a&gt;&lt;br&gt;
Looking back, the main value of this experiment was that it forced us to fill in the parts that simulations easily gloss over: where the pool comes from, how capital actually enters, how costs are calculated, and whether the exit is truly completed.&lt;/p&gt;

&lt;p&gt;With those pieces now represented explicitly in the strategy, later optimization can build on a stable foundation instead of redesigning the entire system every time a new problem appears.&lt;/p&gt;

</description>
      <category>web3trading</category>
      <category>strategy</category>
      <category>cryptobot</category>
      <category>fmz</category>
    </item>
    <item>
      <title>Put Factors Under Continuous Evaluation: Implementing the APFF Multi-Asset Perpetual Strategy on FMZ</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:15:16 +0000</pubDate>
      <link>https://dev.to/quant001/put-factors-under-continuous-evaluation-implementing-the-apff-multi-asset-perpetual-strategy-on-fmz-2hnn</link>
      <guid>https://dev.to/quant001/put-factors-under-continuous-evaluation-implementing-the-apff-multi-asset-perpetual-strategy-on-fmz-2hnn</guid>
      <description>&lt;p&gt;Writing a momentum factor is not difficult. Writing a mean-reversion factor is not difficult either. The real hesitation begins when two factors give opposite opinions today: which one should the capital listen to? If a factor performs poorly for a while, should its weight be reduced, should it be temporarily disabled, or should its direction be reversed? And why should a newly discovered formula be allowed to control real positions at all?&lt;/p&gt;

&lt;p&gt;These questions are difficult to solve simply by adding more indicators. The more factors a system has, the more it needs a unified set of rules for evaluating and using them.&lt;/p&gt;

&lt;p&gt;APFF stands for Adaptive Perpetual Factor Factory. It is a multi-asset adaptive factor-factory strategy for perpetual futures. The system compares relative strength across a group of perpetual contracts, combines a small number of interpretable factors into a long-short portfolio, continuously records each factor's forward performance, gradually adjusts factor weights, and requires new candidates to pass through observation and small-weight trials before they can receive meaningful capital.&lt;/p&gt;

&lt;p&gt;This article is based on the repaired and runnable FMZ JavaScript implementation. It walks through the full process from data and factors to actual orders. The corresponding version is v0.1.0, build 20260905-05, research-definition version 2. Current live-running and simulation tests have validated the main engineering workflow, while long-term return performance still requires further out-of-sample evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. First Turn the Trading Problem Into “Who Is Relatively Stronger?”
&lt;/h2&gt;

&lt;p&gt;At every decision point, APFF works with a cross-section: a group of different contracts observed at the same time.&lt;/p&gt;

&lt;p&gt;Suppose two baskets each start with 3,000 USDT of notional exposure. One basket is long and the other is short. Ignoring fees, funding, and rebalancing for the moment, if the long basket rises 4% while the short basket rises 1%, the price PnL is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Long-basket profit = 3,000 × 4% = 120 USDT
Short-basket loss  = 3,000 × 1% =  30 USDT
Total PnL          = 120 - 30   =  90 USDT

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the long basket falls 2% while the short basket falls 5%, the price PnL can still be positive when both sides have the same notional size. The point is that a long-short portfolio cares about the return spread between two groups of assets.&lt;/p&gt;

&lt;p&gt;But similar long and short notionals do not eliminate market risk. If the long side is concentrated in high-volatility assets while the short side is concentrated in low-volatility assets, a sudden market decline can still produce a severe imbalance between the two sides. For that reason, APFF adds volatility scaling, per-symbol caps, and a limited BTC beta hedge on top of relative-strength ranking.&lt;/p&gt;

&lt;p&gt;The system operates on several different time scales:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqszqd40ceue6gnjnv0t9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqszqd40ceue6gnjnv0t9.png" alt=" " width="800" height="202"&gt;&lt;/a&gt;&lt;br&gt;
Market data needs to be timely, but factor weights should move more slowly. Recomputing weights every time the order book ticks would turn a medium/low-frequency portfolio into a noise-chasing rebalancing system.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The Trading Universe Determines What the Factors Are Comparing
&lt;/h2&gt;

&lt;p&gt;The current version supports only &lt;strong&gt;Binance USDⓈ-M USDT linear perpetual futures, with a default universe size of 30 symbols&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The program reads contract specifications through GetMarkets, while GetTickers and live market data are used to maintain volume, price, and spread information. The base filters include contract trading status, valid quotes, spread limits, and a 90-day listing-age requirement when listing time is available. Eligible contracts are then ranked by current 24-hour trading volume.&lt;/p&gt;

&lt;p&gt;A ranking buffer is used to reduce unnecessary membership turnover. By default, an existing member is preferentially retained as long as it remains within the top 40 eligible contracts. The remaining slots are filled with the highest-ranked alternatives. This prevents two contracts with similar volume from repeatedly entering and leaving the portfolio merely because they swap daily ranking positions.&lt;/p&gt;

&lt;p&gt;BTC is treated separately as the market benchmark and hedging instrument. Even if it is included in the trading universe, it is excluded from the ordinary alpha ranking. Actual portfolio decisions also require at least 15 non-BTC symbols that pass the data checks. The configured universe size and the number of symbols that are actually usable must therefore be treated as separate quantities.&lt;/p&gt;

&lt;p&gt;This implementation has clear boundaries. The current version uses 24-hour trading volume and basic filters. The reference design's 30-day average volume, historical point-in-time universes, continuous-data completeness filters, and order-book-depth filters have not yet been fully implemented. If listing time is missing, the code also cannot independently prove that a contract has been listed for at least 90 days. These differences should be kept in mind when evaluating strategy results.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Five Seed Factors, Each Representing a Testable Hypothesis
&lt;/h2&gt;

&lt;p&gt;The first set of seed factors comes from five families: momentum, reversal, funding, premium, and open interest.&lt;/p&gt;

&lt;p&gt;In the table below, RET denotes &lt;strong&gt;log price return&lt;/strong&gt;; RV denotes the sample standard deviation of 4-hour log returns within the corresponding window, not annualized volatility; and Z_TS denotes a time-series z-score computed separately for each symbol.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk2mcwoyl02dshegsygt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk2mcwoyl02dshegsygt8.png" alt=" " width="798" height="190"&gt;&lt;/a&gt;&lt;br&gt;
The price-direction term in the OI factor is +1 when the 24-hour return is non-negative and -1 when it is negative. These interpretations are research hypotheses; whether they hold must be answered by mature samples. For example, “high funding” does not mean price must fall immediately. Crowded trades can persist for a long time.&lt;/p&gt;

&lt;p&gt;The core of the momentum factor can be seen directly in the source code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var full = returnBars(prices, p.longBars)
var recent = returnBars(prices, p.skipBars)
var rv = realizedVol(prices, p.longBars)
return finite(full) &amp;amp;&amp;amp; finite(recent) &amp;amp;&amp;amp; rv &amp;gt; 0
    ? direction * (full - recent) / rv
    : NaN

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The seed parameters use 42 four-hour candles to represent seven days and skip the most recent candle. Invalid data returns NaN and is then excluded from the valid-value set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Funding Must First Be Put on a Common Time Scale&lt;/strong&gt;&lt;br&gt;
Funding settlement intervals can differ across records. The current implementation uses the observed settlement interval to normalize funding rates to a common 8-hour scale before computing time-series z-scores. The seed factor uses roughly 30 days of history and also checks whether historical coverage is sufficient and whether the most recent record is fresh enough.&lt;/p&gt;

&lt;p&gt;The current-period funding rate and historical settled funding rates must also be kept separate. FMZ's GetFundings provides funding-rate data for the current period. APFF stores it as current-period information, while research returns and settlement accounting use separate historical records. See the FMZ GetFundings documentation.&lt;/p&gt;

&lt;p&gt;Binance's /fapi/v1/fundingRate history endpoint provides fundingTime, fundingRate, and the markPrice associated with each funding settlement. This is the basis used by the current implementation to calculate historical funding cash flows. See the Binance USDⓈ-M Futures market-data documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Premium and OI Require Careful Attention to Data Sources&lt;/strong&gt;&lt;br&gt;
In production, the Premium factor uses historical Premium Index Klines from the exchange. In simulation, the system gradually accumulates a proxy snapshot series based on Mark / Index - 1. The two series are not defined identically, so factor performance observed in the simulated environment cannot be directly treated as evidence for production performance.&lt;/p&gt;

&lt;p&gt;Production OI history only accepts the quote-currency notional-value definition. Simulated snapshots are accumulated from available open-interest quantity and price data, then stored in 4-hour time buckets. This avoids accidentally treating six minute-level snapshots as six independent 4-hour observations.&lt;/p&gt;

&lt;p&gt;If auxiliary history is insufficient, the corresponding factor can remain unavailable temporarily. For a system designed to run for a long time, explicitly knowing that “this factor does not yet have enough data” is more meaningful than forcing the program to produce a number.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Put Every Factor on a Common Ranking Scale So They Can Vote Together
&lt;/h2&gt;

&lt;p&gt;The five raw factors have different units and cannot be added directly. APFF processes each factor cross-sectionally: invalid values are filtered out, the remaining values are winsorized at the 5th and 95th percentiles, and ranks are then mapped to [-1, +1].&lt;/p&gt;

&lt;p&gt;If ranks start at 0, the mapping is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normalized score = 2 × rank / (number of valid symbols - 1) - 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The highest score represents the symbol most favored by the factor, while the lowest score represents the least favored one. Equal raw values receive the average rank. If the entire cross-section has no meaningful variation, the factor is treated as invalid so that array ordering cannot manufacture a signal out of nothing.&lt;/p&gt;

&lt;p&gt;A single factor must cover at least 80% of the symbols in the current round. When the combined alpha score is formed, each individual symbol must also have coverage from at least 80% of the currently valid factor weight. These two checks separately control whether a factor has enough cross-sectional information and whether a particular symbol has enough combined information.&lt;/p&gt;

&lt;p&gt;For a symbol that passes the checks, the combined score is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alpha_i = Σ(available factor weight_j × normalized score_ij)
          / Σ(available factor weight_j for symbol i)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This score determines relative ranking. How much capital the portfolio is allowed to use depends on another quantity: the amount of factor budget that is actually available.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. If Two Factors Are Missing, Why Should Total Exposure Shrink Too?
&lt;/h2&gt;

&lt;p&gt;The five initial seed factors each receive a 20% budget. Suppose momentum, reversal, and funding are available, while Premium and OI have not yet accumulated enough history. The effective factor budget is then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20% + 20% + 20% = 60%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the remaining three factors are rescaled to 33.33% each, their weights sum back to 100%, but the same portfolio is now supported by less information and each factor is implicitly carrying more risk.&lt;/p&gt;

&lt;p&gt;APFF preserves the original budget meaning of each factor and leaves the unallocated portion idle. The initial gross notional budget is calculated as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross notional budget = account equity
                      × configured gross-exposure ratio
                      × selector confidence factor
                      × sum of effective factor budgets
                      × drawdown scaling factor

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider an example used only to explain the arithmetic. Account equity is 10,000 USDT, configured gross exposure is 60%, the sum of effective factor budgets is 60%, and both the confidence factor and drawdown factor are 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross notional budget = 10,000 × 60% × 1 × 60% × 1
                      = 3,600 USDT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, roughly 1,800 USDT is allocated to each side. Per-symbol constraints, hedging constraints, and turnover constraints are applied afterward. This number does not mean that the final orders are guaranteed to fill to the full amount, nor does it represent margin utilization.&lt;/p&gt;

&lt;p&gt;Current effective weights are also subject to these caps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sd8rz87ras2bfwk0b99.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sd8rz87ras2bfwk0b99.png" alt=" " width="259" height="184"&gt;&lt;/a&gt;&lt;br&gt;
These percentages constrain &lt;strong&gt;factor budgets&lt;/strong&gt;. They further affect portfolio notional exposure and should not be interpreted directly as the percentage of account equity allocated to a particular coin.&lt;/p&gt;

&lt;p&gt;The corresponding code is split into two steps. effectiveWeights extracts ACTIVE and TRIAL factors that have valid scores in the current round, then calls the budget-constraint function. The following excerpts show the connection between the two pieces; the second excerpt sits inside the factor loop of normalizeWeightTargets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function effectiveWeights(matrix) {
    var raw = {}
    factorIdsByStatus(["ACTIVE", "TRIAL"]).forEach(function(id) {
        if (Object.keys(matrix[id] || {}).length)
            raw[id] = Math.max(0, num(G.registry[id].weight, 0))
    })
    return normalizeWeightTargets(raw, G.registry, true)
}

// Per-factor cap inside normalizeWeightTargets
var cap = factor.status === "TRIAL"
    ? FIXED.trialFactorCap : FIXED.activeFactorCap
result[id] = Math.min(
    cap,
    Math.max(0, num(raw[id], 0))
        / (preserveBudget ? Math.max(1, total) : total)
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is the final true, which corresponds to preserveBudget. Suppose the sum of valid weights, total, is only 0.6. The denominator becomes Math.max(1, 0.6), so each 0.2 stays 0.2. If the code divided directly by 0.6, each factor would be inflated to approximately 0.3333. If total budget exceeds 1, the same denominator mechanism compresses it back into the total budget range. Later checks also enforce family caps and total trial-factor caps. Any budget clipped by these constraints is not redistributed to the other factors.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Save Today's Decision First, Then Wait for Tomorrow's Answer
&lt;/h2&gt;

&lt;p&gt;One of the easiest mistakes in an adaptive system is to change the question while evaluating the answer.&lt;/p&gt;

&lt;p&gt;Whenever APFF generates a new decision round, it stores the universe version, entry reference prices, factor scores, and coverage observed at that time, then creates a research sample that matures eight hours later. Factor performance is updated only after that sample matures.&lt;/p&gt;

&lt;p&gt;After the first startup, seeing “valid samples: 0” in the status panel can be completely normal. Having enough historical candles to calculate momentum and having accumulated complete forward evaluation samples are two different conditions. The five seed factors being marked ACTIVE describes their initial operating status; it does not mean they have already passed a statistical test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Freeze Sample Membership and Maturity Time&lt;/strong&gt;&lt;br&gt;
At entry, each factor's research portfolio uses the scores available at that moment to select approximately the top 20% of symbols for the long side and the bottom 20% for the short side. Half of the notional budget is allocated to each side, with equal weights within each side. Tied symbols on a quantile boundary are included together and side-level weights are then redistributed.&lt;/p&gt;

&lt;p&gt;At maturity, a symbol cannot simply be removed because its terminal price is missing, followed by reranking and replacement with another symbol. Otherwise, a losing position that happens to have missing data could disappear from the statistics.&lt;/p&gt;

&lt;p&gt;The current implementation allows at most a 60-second terminal-price collection window. Once a terminal price has been fixed, it is not replaced by a later price even if funding history arrives late. Funding history may be awaited until five minutes after the original maturity time; if the required data is still incomplete, the entire sample is invalidated.&lt;/p&gt;

&lt;p&gt;This puts an explicit bound on timing error, but the label is still a mid-price research approximation. It cannot be treated as the realized return of a passive limit order in a real exchange queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Price Features and Trading Returns Use Different Return Definitions&lt;/strong&gt;&lt;br&gt;
Log returns are suitable for momentum and volatility features. For a fixed-quantity linear perpetual position, however, PnL should be calculated using simple price returns plus funding cash flows.&lt;/p&gt;

&lt;p&gt;Let the entry reference price be P0, the terminal price be P1, the k-th funding settlement rate be f_k, and its settlement mark price be M_k. The long-side research return per unit of initial notional is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r_long = P1 / P0 - 1 - Σ(f_k × M_k / P0)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When historical mark prices are available, the code uses this definition. If mark price is unavailable, the funding impact is approximated against the initial notional. Trading fees are not deducted at this stage.&lt;/p&gt;

&lt;p&gt;The factor's long-short research portfolio then aggregates returns using signed weights and subtracts estimated turnover costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turnover   = Σ |new weight_i - old weight_i|
Net return = Σ(new weight_i × r_long_i) - Turnover × one-way cost

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, if a symbol moves from a +5% long weight to a -5% short weight, the absolute weight change is already 10%, which includes both closing the long and opening the short. Multiplying by 2 again would double-count the cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Make New Factors Pass Through Observation, Trial, and Exit
&lt;/h2&gt;

&lt;p&gt;The current “factor factory” generates candidates from a finite set of templates. The templates cover momentum, reversal, Funding, Premium, OI, price-volume relationships, low-volatility ideas, and multiple lookback horizons.&lt;/p&gt;

&lt;p&gt;Each batch can add at most 12 candidates that have not already been registered. Formula type, parameters, and direction together define a fixed identity used for deduplication. Once the template space is exhausted, later batches may generate no new candidates. The current version does not call a large language model online to generate trading code, nor does it perform open-ended formula search.&lt;/p&gt;

&lt;p&gt;The main candidate lifecycle is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tyu80v2fnnomkh91ifq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tyu80v2fnnomkh91ifq.png" alt=" " width="798" height="206"&gt;&lt;/a&gt;&lt;br&gt;
The observation stage requires at least 180 valid 8-hour samples, which is about 60 days under ideal continuous conditions. After entering TRIAL, the candidate must independently accumulate at least another 90 valid samples, or roughly 30 days. Missing samples and weekly screening cadence can make the actual calendar time longer.&lt;/p&gt;

&lt;p&gt;Trial-stage performance is recorded separately. Good performance accumulated during OBSERVING cannot be carried over to satisfy the TRIAL threshold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Does the Evaluation Look At?&lt;/strong&gt;&lt;br&gt;
The current evaluation mainly examines &lt;strong&gt;Rank IC, cost-adjusted long-short returns, subperiod stability, bucket monotonicity, turnover, and data coverage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rank IC measures whether the ranking of entry scores agrees with the ranking of subsequent returns. To reduce small-sample optimism, the mean IC is shrunk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shrunk mean IC = n / (n + 60) × mean IC

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stability divides the available return series into four time segments and measures how many segments have positive average net returns. This is a four-segment check; it should not be interpreted as having already survived four real calendar quarters or four complete market regimes.&lt;/p&gt;

&lt;p&gt;Candidates must not only have positive shrunk IC but also pass a stricter cost test. The base net return has already deducted one estimated cost charge; promotion testing subtracts the same cost once more to check whether the factor remains positive under doubled costs.&lt;/p&gt;

&lt;p&gt;The system also uses block bootstrap resampling to examine result stability. Separately, it estimates a tail probability under a zero-mean null and then applies a dependence-adjusted multiple-testing threshold. These two statistics must not be confused. The bootstrap positive-return frequency shown in the status panel describes the resampling results; it is not a “probability of future profitability.”&lt;/p&gt;

&lt;p&gt;These rules are intended to reduce the influence of small samples, repeated screening, and accidental winners. Fixed block length, a finite number of resamples, and continuous online screening still introduce approximations and selection bias. The system therefore cannot claim that all false discoveries have been rigorously controlled.&lt;/p&gt;

&lt;p&gt;In addition, if the absolute correlation between a candidate's contemporaneous research returns and those of existing portfolio factors exceeds 0.85, it is treated as highly redundant. At most two candidates from each batch can enter TRIAL, and at most one from the same family can do so. No more than two factors can be in TRIAL simultaneously, and no more than six factors can be ACTIVE.&lt;/p&gt;

&lt;p&gt;The basic admission function used during screening is shown below. The original logic is preserved; only line breaks and comments are made clearer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function factorPasses(factor, minimumSamples) {
    var s = summarizeFactor(factor)
    return s.samples &amp;gt;= minimumSamples
        &amp;amp;&amp;amp; s.shrunkIc &amp;gt; 0
        // meanNet already includes the base cost deduction.
        // Subtract the same cost once more to test doubled costs.
        &amp;amp;&amp;amp; s.meanNet - num(s.turnover, 0)
            * G.cfg.oneWayCostBps / 10000 &amp;gt; 0
        &amp;amp;&amp;amp; s.stability &amp;gt;= 0.75
        &amp;amp;&amp;amp; s.pPositive &amp;gt;= 0.90
        &amp;amp;&amp;amp; s.coverage &amp;gt;= 0.98
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All conditions must be satisfied simultaneously. stability &amp;gt;= 0.75 requires at least three of the four time segments to have positive average net returns. coverage &amp;gt;= 0.98 checks the average factor coverage across evaluation samples, which is stricter than the 80% threshold used for participation in a single round. During the observation stage, the function is called with a 180-sample threshold. During the trial stage, it uses independent trialMetrics and a 90-sample threshold. Even after this function returns true, the caller still checks the relevant multiple-testing criteria, correlation limits, and slot limits. A single true does not mean the factor has automatically been promoted.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Adaptive Reweighting Should Respond to Evidence With Restraint
&lt;/h2&gt;

&lt;p&gt;When there are not enough samples, the system uses equal budgets for the seed factors. Only after the adaptive-sample requirements are met are target weights derived from each factor's evaluation results.&lt;/p&gt;

&lt;p&gt;The implementation's evidence score combines scaled IC, net-return score, stability, and monotonicity, then subtracts turnover and missing-data penalties. It is an internal composite score, &lt;strong&gt;not a calibrated probability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Target weights also take the volatility of factor research returns into account. The old and new target weights are then smoothed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Smoothed weight = 70% × old weight + 30% × new target weight

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During ordinary weekly reweighting, the change in any single factor is limited to 5 percentage points, after which all budget caps are applied again. Factor dormancy and hard constraints can reduce a weight faster, so this smoothing rule should not be interpreted as saying that exposure can only decrease slowly under all circumstances.&lt;/p&gt;

&lt;p&gt;Once an ACTIVE factor has accumulated at least 180 valid samples, it enters DORMANT status and its weight is set to zero if both the mean IC and mean net return over the most recent 90 samples are negative. The current logic reduces or disables a factor; it does not automatically reverse the trading direction merely because recent performance has been poor.&lt;/p&gt;

&lt;p&gt;Reversing direction is itself a new hypothesis and should be evaluated from scratch. Otherwise, the system can easily end up chasing noise back and forth across the positive and negative sides of a signal that has simply stopped working.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. From Combined Scores to Executable Target Positions
&lt;/h2&gt;

&lt;p&gt;The live portfolio differs from the equal-weight research baskets used earlier to evaluate individual factors.&lt;/p&gt;

&lt;p&gt;The actual portfolio selects assets from both tails of the combined-score ranking. New targets are chosen from roughly the top and bottom 20%, with at most six symbols on each side. Existing targets are preferentially retained as long as they remain within roughly the corresponding 35% region of the ranking, reducing repeated turnover around the selection boundary.&lt;/p&gt;

&lt;p&gt;The raw weight within each side is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw weight_i ∝ max(0.05, |Alpha_i|) / RV_7D_i

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stronger relative scores receive larger raw weights, while higher historical volatility reduces allocation. A per-symbol cap is then applied, with the default absolute target notional limited to 8% of account equity.&lt;/p&gt;

&lt;p&gt;The BTC hedge estimates beta from aligned 4-hour returns. If the absolute estimated beta exposure of the portfolio exceeds 2% of equity, the program attempts to offset it with BTC. The hedge itself is capped and remains subject to per-symbol and total-portfolio exposure limits. Some residual beta can remain after clipping, so this is intentionally a &lt;strong&gt;limited hedge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Turnover is handled last. Under normal target rebalancing, the notional amount changed in one decision is softly capped at 20% of account equity. If the desired adjustment exceeds that level, the portfolio moves proportionally toward the new target. However, if the current target already violates a hard risk limit, required deleveraging takes priority; excessive risk is not preserved merely to satisfy the turnover limit.&lt;/p&gt;

&lt;p&gt;After all hedge and turnover adjustments, the program performs another check of per-symbol and total exposure. A target that passed earlier checks can become invalid again after subsequent transformations, so this final validation must occur before the executable target is finalized.&lt;/p&gt;

&lt;p&gt;Because the actual portfolio uses volatility weighting, ranking buffers, BTC hedging, and turnover controls, individual factor research returns cannot simply be added together and treated as strategy-level account returns. The research layer evaluates ranking information; the execution layer must additionally determine whether that information can be converted into positions at a reasonable cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. After an Order Fills, the Position Still Has to Give the Same Answer
&lt;/h2&gt;

&lt;p&gt;Once a multi-asset strategy runs asynchronously, market data, order responses, fills, and position snapshots can arrive in different orders.&lt;/p&gt;

&lt;p&gt;FMZ's exchange.Go can call trading interfaces asynchronously and retrieve results through the returned object's wait method. APFF uses this mechanism to stagger data and account queries. The current maximum number of concurrent tasks is 8. See the FMZ exchange.Go documentation.&lt;/p&gt;

&lt;p&gt;Asynchronous execution creates another question that must be answered explicitly: &lt;strong&gt;did this request actually take effect on the exchange?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save the Intent Before Sending the Order&lt;/strong&gt;&lt;br&gt;
Before every order is sent, the program first generates a client order ID and records the symbol, side, quantity, current position, and corresponding target, then persists that intent. If persistence fails, the order is not sent.&lt;/p&gt;

&lt;p&gt;At most one active intent is allowed per symbol, and at most four can exist globally at the same time. When reversing direction, the program closes the original side before considering a new position on the opposite side. Normal execution uses &lt;strong&gt;GTX passive limit orders&lt;/strong&gt;. New targets have a default execution window of 15 minutes. After that window expires, the strategy stops adding new risk, while required risk reduction and reconciliation continue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If a Request Times Out, Verify the Result First&lt;/strong&gt;&lt;br&gt;
A definite rejection and an unknown result must be treated differently. Insufficient balance, precision errors, and similar responses can be handled as explicit rejections. A timeout, broken connection, or unknown response may occur &lt;strong&gt;after&lt;/strong&gt; the exchange has already accepted the order.&lt;/p&gt;

&lt;p&gt;The current implementation continuously reconciles by order ID or client order ID against order details, open orders, and historical orders. It does not release an unknown intent and submit the order again merely because “the open-order list is empty” or “tens of seconds have passed.”&lt;/p&gt;

&lt;p&gt;After the Order Reaches a Terminal State, Confirm the Position Too&lt;br&gt;
Suppose the original position is 100 contracts and a closing sell order accumulates 30 contracts of fills. The next position snapshot should confirm that roughly 70 contracts remain. This query must be initiated after the order reaches its terminal state; a late response from an older request cannot be used as confirmation. If the snapshot still reports 100 contracts, the program shows WAIT_POSITION, continues querying, and keeps the symbol locked.&lt;/p&gt;

&lt;p&gt;The expected position is calculated as “initial position + signed cumulative filled quantity” and stored as barrier.expected. After a position response arrives, applyPositions checks whether the reconciliation lock can be released. The following excerpt shows the core order path in the current version, omitting diagnostic logs and legacy state-migration branches. positions is the already-parsed position map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var snapshotAt = num(requestedAt, nowMs())
Object.keys(G.execution.positionBarriers || {}).forEach(function(symbol) {
    var barrier = G.execution.positionBarriers[symbol]
    // The query must have been initiated after the terminal state.
    // A symbol with simultaneous long and short positions cannot be unlocked.
    if (snapshotAt &amp;lt;= barrier.at || (G.actual.hedgedSymbols || {})[symbol])
        return

    var spec = G.universe.markets[symbol]
    var tolerance = Math.max(1e-12, num(spec &amp;amp;&amp;amp; spec.step, 0) / 2)
    if (finite(barrier.expected)
        &amp;amp;&amp;amp; Math.abs(num(positions[symbol], 0) - barrier.expected) &amp;gt; tolerance)
        return

    delete G.execution.positionBarriers[symbol]
    clearIssue("POSITION_RECONCILE_" + symbol)
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;snapshotAt uses the request-initiation time to determine ordering. Quantity comparison allows an error of half the order-size step and also applies a tiny numerical-error floor. If any check fails, the function returns early, the symbol lock remains in place, and the execution layer continues blocking new orders for that symbol. Only after all checks pass is positionBarriers[symbol] deleted. This avoids the classic duplicate-trading failure mode of “the order has filled, the position endpoint has not updated yet, so the strategy sends the same trade again.”&lt;/p&gt;

&lt;p&gt;One Binance simulation test provides a concrete example. A TAC position-reduction order accumulated &lt;strong&gt;17,990 contracts&lt;/strong&gt; of fills, reducing the position from &lt;strong&gt;37,062&lt;/strong&gt; to &lt;strong&gt;19,072&lt;/strong&gt; contracts. During the process, a cancel request raced with the fill and produced one -2011 response. Subsequent order queries confirmed the remaining execution within about &lt;strong&gt;2.17 seconds&lt;/strong&gt;, completing reconciliation.&lt;/p&gt;

&lt;p&gt;This test record shows how that particular race condition was recovered. The error message itself cannot prove that the order filled, nor can it prove that cancellation succeeded. Final confirmation still requires the terminal order state and the position quantity to agree with each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Risk Controls Must Work While the Program Is Still Running
&lt;/h2&gt;

&lt;p&gt;APFF scales target budget according to drawdown from the historical equity peak:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqgjj517oxxk4x0tf8hf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqgjj517oxxk4x0tf8hf.png" alt=" " width="464" height="181"&gt;&lt;/a&gt;&lt;br&gt;
The 4% and 7% tiers take effect on the current target immediately; the system does not wait for the next 8-hour decision. The zero-target state continues to prevent new exposure, while the execution layer reduces positions after it obtains the required market and account state. These controls constrain the program's targets and execution behavior; they do not guarantee that maximum loss in an extreme market will stop exactly at 10%.&lt;/p&gt;

&lt;p&gt;The interactive controls distinguish three actions: PAUSE_NEW pauses new risk, RESUME resumes portfolio execution, and REDUCE_TO_ZERO cancels this strategy's orders, completes reconciliation, and then closes the positions managed by this strategy.&lt;/p&gt;

&lt;p&gt;The strategy uses its own position-ownership markers to determine what it manages. If a target symbol already has a position that is not attributed to this strategy, or if simultaneous long and short positions are detected in the same symbol, automatic trading for that symbol is blocked.&lt;/p&gt;

&lt;p&gt;Stopping the process does not automatically liquidate positions. If the intention is to flatten the portfolio, the flattening workflow should be allowed to complete and its result should be verified. Simply stopping the robot leaves positions in place. For a multi-asset portfolio, this distinction directly determines how much risk remains in the account after shutdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. How to Interpret Run Modes and the Status Panel on FMZ
&lt;/h2&gt;

&lt;p&gt;The current version provides three run modes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2l70zjvcp0o2l9ujmm1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2l70zjvcp0o2l9ujmm1v.png" alt=" " width="800" height="121"&gt;&lt;/a&gt;&lt;br&gt;
LIVE uses the trading interface. If the exchange object is configured for the Binance simulated environment, orders are matched in the exchange's simulation environment. Only when the strategy is connected to a real-money account does LIVE correspond to real-money trading. Both the run-mode name and the account environment must be checked.&lt;/p&gt;

&lt;p&gt;The current program depends on live WebSocket data and blocks entry into FMZ's historical backtest environment during initialization. Therefore, PAPER must accumulate forward results through real-time running. It is not a mode where one can simply select a historical date range and backtest it.&lt;/p&gt;

&lt;p&gt;After importing the complete strategy file and adding a corresponding Binance futures exchange object, the main parameters are:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1j2kyfurdzzb1hd8cg5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1j2kyfurdzzb1hd8cg5x.png" alt=" " width="798" height="254"&gt;&lt;/a&gt;&lt;br&gt;
These values are program defaults and have &lt;strong&gt;not&lt;/strong&gt; been certified as return-optimal parameters. Cost parameters in particular must be interpreted according to the run mode. The research layer deducts estimated cost from reference-price returns. In PAPER, buys use the Ask and sells use the Bid, so bid-ask spread is already reflected; the configured cost parameter is then charged additionally as a fee estimate. Cost calibration should avoid counting the same cost component twice.&lt;/p&gt;

&lt;p&gt;When observing the strategy, the status panel can be read in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Market data and data gates&lt;/strong&gt;: Are both market-data connections updating continuously, and are enough tradable symbols available?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research samples and maturity&lt;/strong&gt;: Is the system merely warmed up on data, or has it already accumulated valid forward samples?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configured weights and effective target weights&lt;/strong&gt;: Which factors are actually participating in this round?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Targets and positions&lt;/strong&gt;: Are deviations caused by the execution window, minimum order notional, exchange limits, or fills that are still awaiting confirmation?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order intents and position-confirmation locks&lt;/strong&gt;: Are UNKNOWN or WAIT_POSITION states persisting, and are the related reconciliation queries still progressing?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The PAPER ledger separates realized PnL, unrealized PnL, funding, and estimated fees. Funding is posted against the historical position that existed at settlement time; late-arriving funding records are not simply applied to the current position.&lt;/p&gt;

&lt;p&gt;LIVE currently displays performance mainly as the difference between account equity and baseline account equity. It does not yet fully exclude deposits, withdrawals, or the effect of other strategies, and it does not fully separate actual realized PnL, unrealized PnL, and funding. Items that are not separately accounted for are shown as -; they must not be interpreted as zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. What Has Been Validated So Far?
&lt;/h2&gt;

&lt;p&gt;As of the build used in this article, the project has completed base logic checks, 23 order-regression checks, and 34 dedicated audit tests. The dedicated tests include 36 accelerated decision cycles and 500 randomized weight-constraint checks.&lt;/p&gt;

&lt;p&gt;It is important to state what these numbers do and do not mean. The 36 cycles use synthetic data and a controlled clock to validate sampling, maturity, accounting, and recovery workflows; they are not a 12-day historical return backtest. The randomized constraint checks validate budget caps; they do not validate the predictive power of the factors.&lt;/p&gt;

&lt;p&gt;Runtime retesting in the Binance simulated environment covers actual simulated matching, restart recovery, cancel/fill races, explicit order rejection, and position reconciliation. These results are enough to justify continuing to accumulate forward evidence, but they are not sufficient to report conclusions about annualized return, Sharpe ratio, or long-term win rate.&lt;/p&gt;

&lt;p&gt;The highest-priority next steps are to add point-in-time historical data and independent rolling out-of-sample validation, calibrate execution costs by symbol and market regime, add correlation-cluster position limits, and introduce an entry threshold requiring expected return to cover trading costs. These directions already exist in the research design, but they have not all been fully implemented in the current code.&lt;/p&gt;

&lt;p&gt;APFF has already encoded the lifecycle of a factor—from creation and performance recording, to receiving budget, participating in orders, and eventually leaving the portfolio—into a running program. The most valuable data from here will be the decision recorded at each point in time and the answer obtained eight hours later under the same rules: which signals remain effective, which work only in certain market regimes, and how much of their apparent edge survives actual execution.&lt;/p&gt;

</description>
      <category>apff</category>
      <category>crypto</category>
      <category>momentumtrading</category>
      <category>fmzquant</category>
    </item>
    <item>
      <title>FMZ Web3 in Practice — Riding the Robinhood Chain Wave: Build a Uniswap V4 New Pool Radar Step by Step</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 09 Sep 2026 08:42:42 +0000</pubDate>
      <link>https://dev.to/quant001/fmz-web3-in-practice-riding-the-robinhood-chain-wave-build-a-uniswap-v4-new-pool-radar-step-by-5io</link>
      <guid>https://dev.to/quant001/fmz-web3-in-practice-riding-the-robinhood-chain-wave-build-a-uniswap-v4-new-pool-radar-step-by-5io</guid>
      <description>&lt;p&gt;Robinhood Chain has recently seen a noticeable surge in on-chain activity. By late August 2026, transaction activity on the network had accelerated significantly. On August 30, Robinhood Chain processed approximately &lt;strong&gt;5.52 million transactions&lt;/strong&gt; in a single day, while daily DEX trading volume reached roughly &lt;strong&gt;$875 million&lt;/strong&gt;. At the same time, token issuance also began to accelerate rapidly, with the Pons launch platform alone creating around &lt;strong&gt;22,600 tokens&lt;/strong&gt; that day.&lt;/p&gt;

&lt;p&gt;For ordinary users, the most visible takeaway may simply be that Robinhood Chain has recently become very active and that a large number of new tokens are appearing. For quantitative developers, however, a more interesting question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we let a program monitor Robinhood Chain directly and detect a new Uniswap pool the moment it appears?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is yes.&lt;/p&gt;

&lt;p&gt;We do not need to keep refreshing web pages, nor do we need to rely on third-party new-token alert services. By reading on-chain events directly through Robinhood Chain's JSON-RPC interface, we can capture new pools at the data source itself.&lt;/p&gt;

&lt;p&gt;In this article, we will use &lt;strong&gt;FMZ Quant&lt;/strong&gt; to connect to Robinhood Chain mainnet through its JSON-RPC interface and monitor the Uniswap V4 PoolManager contract. Starting from scratch, we will build a Robinhood Chain new-pool radar.&lt;/p&gt;

&lt;p&gt;Once running, the program continuously reads the latest blocks and uses eth_getLogs to monitor Uniswap V4 Initialize events. Whenever a new pool is created, it automatically decodes the trading pair, token information, fee, tick spacing, hook, and pool ID, with special attention given to pools pairing a new token with ETH or WETH.&lt;/p&gt;

&lt;p&gt;For now, we will solve the most fundamental problem in the entire system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can we discover a newly created Uniswap V4 pool as early as possible?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Later, this framework can be extended with liquidity monitoring, swap monitoring, wallet analysis, risk detection, new-token scoring, automated trading, and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxspr0revys7wavxuw2s5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxspr0revys7wavxuw2s5.jpg" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why Is Robinhood Chain Worth Watching?
&lt;/h2&gt;

&lt;p&gt;Robinhood Chain is an Ethereum Layer 2 network built on the Arbitrum technology stack. For developers building on-chain strategies with FMZ, one of its most important characteristics is that it is EVM-compatible.&lt;/p&gt;

&lt;p&gt;That means standard JSON-RPC methods we already use on Ethereum and other EVM networks—such as eth_blockNumber, eth_getLogs, eth_call, eth_getBalance, and eth_getTransactionReceipt—can also be used on Robinhood Chain.&lt;/p&gt;

&lt;p&gt;The current core parameters for Robinhood Chain mainnet are:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6l604adwy4lh2seg878.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx6l604adwy4lh2seg878.png" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
Robinhood provides a free public RPC endpoint, so during development you can start testing without purchasing access from a third-party node provider. However, Robinhood also states that the public RPC is rate-limited. If the strategy later needs to run continuously, perform large numbers of queries, or require higher reliability, a professional RPC provider such as Alchemy can be considered.&lt;/p&gt;

&lt;p&gt;Reference: Robinhood Chain — Connecting to Robinhood Chain&lt;/p&gt;

&lt;p&gt;From FMZ's perspective, connecting to Robinhood Chain is fundamentally no different from connecting to other EVM networks. We really need to solve only three problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connect to the correct RPC;&lt;/li&gt;
&lt;li&gt;find the target smart contract;&lt;/li&gt;
&lt;li&gt;monitor the correct on-chain event.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2. Why Choose New Uniswap V4 Pools as the Monitoring Target?
&lt;/h2&gt;

&lt;p&gt;Our goal is not simply to know that Robinhood Chain has produced another block.&lt;/p&gt;

&lt;p&gt;The information that is actually useful is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Has a new token trading pool appeared?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, during real operation we have already captured V4 pools such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ETH / GREAT&lt;/li&gt;
&lt;li&gt;ETH / MERRY&lt;/li&gt;
&lt;li&gt;ETH / ANTIDOTE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we can discover these pools immediately, we can then continue investigating what the token is, how much ETH liquidity has been added, what the initial price is, whether users have started trading, who the first buyers are, what the buy/sell ratio looks like, and whether the token itself shows any obvious risks.&lt;/p&gt;

&lt;p&gt;New-pool discovery therefore acts as the entrance to a broader on-chain new-token monitoring system.&lt;/p&gt;

&lt;p&gt;Uniswap is already deployed on Robinhood Chain, with V2, V3, V4, and UniswapX all part of the ecosystem. In August 2026, Uniswap Labs also launched Pools.trade for Robinhood Chain, providing infrastructure for new-token launches and liquidity creation.&lt;/p&gt;

&lt;p&gt;Reference: Uniswap — Robinhood Chain is Live&lt;/p&gt;

&lt;p&gt;For us, then, building a Uniswap V4 new-pool radar is a natural place to start.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Understanding Uniswap V4's PoolManager
&lt;/h2&gt;

&lt;p&gt;Before writing code, we first need to understand one important difference between Uniswap V4 and the earlier V2/V3 architecture.&lt;/p&gt;

&lt;p&gt;In Uniswap V2 and V3, we often think of different liquidity pools as separate pool contracts. Uniswap V4 introduces a Singleton Architecture, where the core state of many pools is managed by one important contract: PoolManager.&lt;/p&gt;

&lt;p&gt;As a result, in V4 we do not need to search everywhere for newly deployed pool contracts. For new-pool monitoring, what matters more is monitoring the events emitted by PoolManager directly.&lt;/p&gt;

&lt;p&gt;That makes the monitoring logic much simpler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe4n1zlednlivvgqzs3ch.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe4n1zlednlivvgqzs3ch.jpg" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
The Uniswap V4 PoolManager address on Robinhood Chain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x8366a39cc670b4001a1121b8f6a443a643e40951
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Robinhood Chain WETH address is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program later in this article will use both addresses directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. How Is a New Pool Discovered?
&lt;/h2&gt;

&lt;p&gt;When a new Uniswap V4 pool is initialized, PoolManager emits a very important event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initialize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its structure can be represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event Initialize(
    PoolId indexed id,
    Currency indexed currency0,
    Currency indexed currency1,
    uint24 fee,
    int24 tickSpacing,
    IHooks hooks,
    uint160 sqrtPriceX96,
    int24 tick
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This event already contains a large amount of information useful for monitoring a new pool, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pool ID;&lt;/li&gt;
&lt;li&gt;the two trading assets;&lt;/li&gt;
&lt;li&gt;fee;&lt;/li&gt;
&lt;li&gt;tick spacing;&lt;/li&gt;
&lt;li&gt;hook;&lt;/li&gt;
&lt;li&gt;initial price information;&lt;/li&gt;
&lt;li&gt;initial tick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The monitoring principle itself is not complicated.&lt;/p&gt;

&lt;p&gt;When a user calls PoolManager.initialize() to initialize a new V4 pool, PoolManager emits an Initialize event. That event is then recorded in the block logs.&lt;/p&gt;

&lt;p&gt;FMZ can query these logs through the standard JSON-RPC method eth_getLogs. As long as we specify both the PoolManager contract address and the Initialize event topic, the RPC node can filter out the new-pool events we need directly. There is no need to download every transaction in the block and analyze them one by one.&lt;/p&gt;

&lt;p&gt;This is the core mechanism behind the new-pool radar in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Adding Robinhood Chain to FMZ
&lt;/h2&gt;

&lt;p&gt;Now we can start the actual setup.&lt;/p&gt;

&lt;p&gt;First, add a Web3 exchange object on FMZ Quant.&lt;/p&gt;

&lt;p&gt;Because Robinhood Chain is an EVM network, set ChainType to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ETH

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that selecting ETH does not mean we are connecting to Ethereum Mainnet. It tells FMZ to use the Ethereum/EVM-style Web3 interface.&lt;/p&gt;

&lt;p&gt;For testing, set the RPC address to the official Robinhood Chain mainnet RPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://rpc.mainnet.chain.robinhood.com

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final configuration is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2facdp9loc726v3xy7u7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2facdp9loc726v3xy7u7.png" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
Although the new-pool radar in this article only reads public on-chain data and does not send transactions, the wallet private key will become involved in transaction signing if functions such as Approve or Swap are added later.&lt;/p&gt;

&lt;p&gt;For development, it is therefore better to create a dedicated test wallet rather than using a main wallet that holds substantial assets. Never provide your private key or seed phrase to anyone.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Testing the FMZ–Robinhood Chain Connection
&lt;/h2&gt;

&lt;p&gt;When developing Web3 strategies, I recommend starting with the smallest possible test. Do not begin by writing hundreds of lines of code. First verify that the most basic data path works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FMZ → RPC → Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a minimal FMZ JavaScript strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;chainId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth_chainId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;blockNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth_blockNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chain ID:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;chainId&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Block Number:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;blockNumber&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running it, we obtained:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnfjm5onndtg25ty60zkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnfjm5onndtg25ty60zkg.png" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
0x1237 converted to decimal is 4663, which matches the Robinhood Chain Mainnet Chain ID exactly.&lt;/p&gt;

&lt;p&gt;This means FMZ has successfully connected to Robinhood Chain mainnet through JSON-RPC.&lt;/p&gt;

&lt;p&gt;The test is simple, but it is important. If new-pool monitoring later encounters a problem, we already know that the RPC connection itself is working.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Calculating the Uniswap V4 Initialize Event Topic
&lt;/h2&gt;

&lt;p&gt;In an Ethereum event log, topics[0] is the Keccak256 hash of the event signature.&lt;/p&gt;

&lt;p&gt;We can therefore calculate the topic for Initialize directly in FMZ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getInitializeTopic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Initialize(bytes32,address,address,uint24,int24,address,uint160,int24)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="nc"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keccak256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;signature&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff86gkytmiwrbggp4z2b6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff86gkytmiwrbggp4z2b6.png" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
Later, when calling eth_getLogs, we only need to place this value in topics[0]. The RPC node can then filter Initialize events for us.&lt;/p&gt;
&lt;h2&gt;
  
  
  8. Querying New Pools with eth_getLogs
&lt;/h2&gt;

&lt;p&gt;FMZ Web3 can call Ethereum JSON-RPC directly through exchange.IO().&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth_getLogs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;params&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query parameters mainly specify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the starting block;&lt;/li&gt;
&lt;li&gt;the ending block;&lt;/li&gt;
&lt;li&gt;the PoolManager address;&lt;/li&gt;
&lt;li&gt;the Initialize topic.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="na"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;numberToHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;numberToHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x8366a39cc670b4001a1121b8f6a443a643e40951&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;getInitializeTopic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The RPC node will now return not every transaction in the block, but specifically the Initialize events emitted by Uniswap V4 PoolManager within the requested block range.&lt;/p&gt;

&lt;p&gt;For a new-pool radar intended to run continuously, this is far more efficient than downloading every block's transactions and analyzing them individually.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Decoding the Initialize Event
&lt;/h2&gt;

&lt;p&gt;Once we receive an event log, we still need to convert the raw hexadecimal data into human-readable information.&lt;/p&gt;

&lt;p&gt;The first three parameters in Initialize are indexed parameters, so they appear in topics[1], topics[2], and topics[3]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;poolId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;currency0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;topicToAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;currency1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;topicToAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remaining parameters are stored in log.data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;wordToUint24&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;getWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tickSpacing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;wordToInt24&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;getWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hooks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;wordToAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;getWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After decoding, what originally looked like an unreadable blockchain log becomes structured information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pool ID;&lt;/li&gt;
&lt;li&gt;token addresses;&lt;/li&gt;
&lt;li&gt;fee;&lt;/li&gt;
&lt;li&gt;tick spacing;&lt;/li&gt;
&lt;li&gt;hook.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. A Common Native ETH Pitfall in Uniswap V4
&lt;/h2&gt;

&lt;p&gt;In Uniswap V4, native ETH can be used directly as a Currency.&lt;/p&gt;

&lt;p&gt;Therefore, if the program sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x0000000000000000000000000000000000000000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we cannot simply assume it is an invalid address. In this context, it may represent native ETH.&lt;/p&gt;

&lt;p&gt;Our program therefore needs to recognize both native ETH and WETH.&lt;/p&gt;

&lt;p&gt;Native ETH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x0000000000000000000000000000000000000000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Robinhood Chain WETH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the program to filter pools such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ETH / new token
WETH / new token

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a new-token radar, these pools are usually worth prioritizing over ordinary token/token pools.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Reading Token Information
&lt;/h2&gt;

&lt;p&gt;Displaying only a token contract address is not very readable.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xffb1e3a069d2060cf42247ed67a8e712a05064de

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By looking at the address alone, we have no idea what the token is.&lt;/p&gt;

&lt;p&gt;After detecting the token, we can continue by using eth_call to call the standard ERC20 functions symbol(), name(), and decimals().&lt;/p&gt;

&lt;p&gt;First register a simple ERC20 ABI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ERC&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="err"&gt;_ABI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"stateMutability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"view"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"stateMutability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"view"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"decimals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"uint8"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"stateMutability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"view"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"function"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;`;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ERC20_ABI&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;symbol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original token address can now be displayed together with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Symbol;&lt;/li&gt;
&lt;li&gt;Name;&lt;/li&gt;
&lt;li&gt;Decimals;&lt;/li&gt;
&lt;li&gt;Contract Address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, there is no guarantee that every on-chain token implements the standard ERC20 ABI perfectly. The token-information query should therefore be wrapped in try...catch.&lt;/p&gt;

&lt;p&gt;Even if a strange token fails to return symbol(), it should not cause the entire new-pool radar to stop running.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Running It for Real: We Actually Captured New Robinhood Chain Pools
&lt;/h2&gt;

&lt;p&gt;After implementing the logic above, we ran the program on Robinhood Chain mainnet.&lt;/p&gt;

&lt;p&gt;It quickly captured real Uniswap V4 Initialize events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi5uly2aydri5c76ho3pu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi5uly2aydri5c76ho3pu.png" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
This means we have successfully completed the full data path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Robinhood Chain
    ↓
Uniswap V4 PoolManager
    ↓
Initialize Event
    ↓
FMZ new-pool monitoring

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  13. Building a Real-Time New Pool Radar with LogStatus
&lt;/h2&gt;

&lt;p&gt;If the program simply keeps calling Log(), after running for several hours the log output becomes very large and it becomes difficult to understand the current system state quickly.&lt;/p&gt;

&lt;p&gt;So we continue by using FMZ's LogStatus() to build a simple real-time dashboard.&lt;/p&gt;

&lt;p&gt;The dashboard mainly displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;network status;&lt;/li&gt;
&lt;li&gt;latest block;&lt;/li&gt;
&lt;li&gt;RPC latency;&lt;/li&gt;
&lt;li&gt;number of scans;&lt;/li&gt;
&lt;li&gt;number of newly detected pools;&lt;/li&gt;
&lt;li&gt;number of ETH/WETH pools;&lt;/li&gt;
&lt;li&gt;recently discovered pools;&lt;/li&gt;
&lt;li&gt;priority new-token information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsvsu1vt3hm7zh8vtckwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsvsu1vt3hm7zh8vtckwt.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxfadkyaa71tnrkkbptd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxfadkyaa71tnrkkbptd.png" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
At this point, the program is no longer just a simple event-log script. It is beginning to take the shape of an actual on-chain monitoring system.&lt;/p&gt;
&lt;h2&gt;
  
  
  14. Choosing an RPC Provider and the Problems We Actually Hit
&lt;/h2&gt;

&lt;p&gt;Robinhood Chain provides an official public RPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://rpc.mainnet.chain.robinhood.com

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our real tests, the free official RPC was already able to handle both eth_blockNumber and eth_getLogs queries successfully, so it is perfectly usable during development.&lt;/p&gt;

&lt;p&gt;However, if the program is expected to run 24/7 for long periods, or if we later add large numbers of Swap, Liquidity, and Token queries, RPC stability and rate limits become important considerations.&lt;/p&gt;

&lt;p&gt;So we also tested Alchemy.&lt;/p&gt;

&lt;p&gt;An Alchemy Robinhood Chain HTTP endpoint looks similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://robinhood-mainnet.g.alchemy.com/v2/YOUR_API_KEY

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On FMZ, we only need to replace the Rpc Address with the corresponding endpoint. The strategy can continue using standard JSON-RPC calls, and the core logic does not need to be rewritten simply because the provider changed.&lt;/p&gt;

&lt;p&gt;During testing, however, we encountered an interesting issue.&lt;/p&gt;

&lt;p&gt;When Alchemy's Free Plan received an eth_getLogs request covering too large a block range, it returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;400 Bad Request
Under the Free tier plan,
you can make eth_getLogs requests
with up to a 10 block range.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original program used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;maxBlockRange&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This meant a single request could cover as many as 100 blocks, triggering the free-plan limit.&lt;/p&gt;

&lt;p&gt;The solution was simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;maxBlockRange&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let the program divide a larger scan automatically into batches.&lt;/p&gt;

&lt;p&gt;For example, if the bot has been offline for a while and needs to catch up on 35 blocks, it can split the job into four eth_getLogs requests, each covering no more than 10 blocks.&lt;/p&gt;

&lt;p&gt;This looks like a small change, but it is actually very valuable.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;An on-chain monitoring program cannot assume that RPC services will always be stable, and it cannot assume that every provider has exactly the same limits.&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
If the project is improved further, we can add mechanisms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;primary RPC;&lt;/li&gt;
&lt;li&gt;backup RPC;&lt;/li&gt;
&lt;li&gt;automatic failover;&lt;/li&gt;
&lt;li&gt;retry handling for HTTP 429;&lt;/li&gt;
&lt;li&gt;exponential backoff.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  15. Complete FMZ New Pool Radar Code
&lt;/h2&gt;

&lt;p&gt;Below is the complete strategy used in this article.&lt;/p&gt;

&lt;p&gt;The role of this version is intentionally clear:&lt;/p&gt;

&lt;p&gt;monitor only, no trading.&lt;/p&gt;

&lt;p&gt;Robinhood Chain · Uniswap V4 New Pool Radar V2&lt;/p&gt;

&lt;p&gt;It does not execute Approve, call Swap, or automatically purchase any token.&lt;/p&gt;

&lt;p&gt;The first priority is to build the on-chain data layer correctly. Trading logic can then be added on top of that foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  16. Why Can't We Buy Immediately After Discovering a Pool?
&lt;/h2&gt;

&lt;p&gt;Once we reach this point, it is easy to have another idea:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If we can already detect a new pool immediately, why not simply call Swap as soon as the pool appears?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In practice, we are still far from that point.&lt;/p&gt;

&lt;p&gt;An Initialize event only tells us that the pool has been initialized. It does not mean the pool already has enough liquidity to be worth trading.&lt;/p&gt;

&lt;p&gt;After a pool is created, liquidity may only be added later. Even after liquidity has been added, we still need to evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;liquidity size;&lt;/li&gt;
&lt;li&gt;initial price;&lt;/li&gt;
&lt;li&gt;fee;&lt;/li&gt;
&lt;li&gt;hook;&lt;/li&gt;
&lt;li&gt;token contract risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, &lt;strong&gt;discovering a new pool&lt;/strong&gt; and &lt;strong&gt;discovering a trading opportunity&lt;/strong&gt; are two very different things.&lt;/p&gt;

&lt;p&gt;We have solved the first problem.&lt;/p&gt;

&lt;p&gt;The next step is to continue monitoring liquidity-related events after capturing Initialize.&lt;/p&gt;

&lt;p&gt;When the program discovers ETH / GREAT, for example, it should no longer stop at displaying the token name. It should continue answering questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has actual liquidity been added to this pool?&lt;/li&gt;
&lt;li&gt;How much ETH liquidity has been added?&lt;/li&gt;
&lt;li&gt;How many tokens were added on the other side?&lt;/li&gt;
&lt;li&gt;What is the approximate initial price?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this step is complete, the new-pool radar begins to evolve into a true new-token radar.&lt;/p&gt;

&lt;h2&gt;
  
  
  17. Next Step: Add a Liquidity Radar
&lt;/h2&gt;

&lt;p&gt;The code framework in this article is not a one-off script.&lt;/p&gt;

&lt;p&gt;We already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pool ID;&lt;/li&gt;
&lt;li&gt;Currency0;&lt;/li&gt;
&lt;li&gt;Currency1;&lt;/li&gt;
&lt;li&gt;Fee;&lt;/li&gt;
&lt;li&gt;TickSpacing;&lt;/li&gt;
&lt;li&gt;Hook;&lt;/li&gt;
&lt;li&gt;Block;&lt;/li&gt;
&lt;li&gt;Transaction Hash;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;basic token metadata.&lt;br&gt;
All of these can become inputs to later modules.&lt;/p&gt;

&lt;p&gt;The most useful next component to implement is a &lt;strong&gt;Liquidity Radar.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After Initialize detects a new ETH-token pool, the program can continue tracking changes in that pool's liquidity and attempt to calculate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ETH liquidity;&lt;/li&gt;
&lt;li&gt;token liquidity;&lt;/li&gt;
&lt;li&gt;initial price;&lt;/li&gt;
&lt;li&gt;speed of liquidity changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, we can continue monitoring Swap events.&lt;/p&gt;

&lt;p&gt;Once swaps can be decoded in real time, the system can begin tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first trade;&lt;/li&gt;
&lt;li&gt;the first 10 trades;&lt;/li&gt;
&lt;li&gt;number of buys;&lt;/li&gt;
&lt;li&gt;number of sells;&lt;/li&gt;
&lt;li&gt;trading volume;&lt;/li&gt;
&lt;li&gt;number of unique trading addresses;&lt;/li&gt;
&lt;li&gt;early trading speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these data points, the system is no longer limited to telling us that a new token has appeared. It can gradually build a richer profile such as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbm3biafyhchvshqgrdim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbm3biafyhchvshqgrdim.png" alt=" "&gt;&lt;/a&gt;&lt;br&gt;
Going further, we can research:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deployer addresses;&lt;/li&gt;
&lt;li&gt;early buyers;&lt;/li&gt;
&lt;li&gt;historical wallet behavior;&lt;/li&gt;
&lt;li&gt;liquidity withdrawals;&lt;/li&gt;
&lt;li&gt;token permissions;&lt;/li&gt;
&lt;li&gt;abnormal trading patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These modules can eventually feed into a unified &lt;strong&gt;Scoring Engine&lt;/strong&gt;, allowing large numbers of new pools to be automatically classified into categories such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ignore;&lt;/li&gt;
&lt;li&gt;Watch;&lt;/li&gt;
&lt;li&gt;High Priority.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only after reaching that stage does it become more meaningful to discuss automated trading.&lt;/p&gt;

&lt;h2&gt;
  
  
  18. What Can We Research Next?
&lt;/h2&gt;

&lt;p&gt;This entire series can continue expanding around the code built in this article instead of rewriting a completely different program each time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1: Pool Discovery&lt;/strong&gt;&lt;br&gt;
This is the Initialize monitoring implemented in this article. It answers:&lt;/p&gt;

&lt;p&gt;When did a new pool appear?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2: Liquidity Intelligence&lt;/strong&gt;&lt;br&gt;
The focus shifts to questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much ETH was added to the new pool?&lt;/li&gt;
&lt;li&gt;How many tokens were added?&lt;/li&gt;
&lt;li&gt;When does liquidity increase?&lt;/li&gt;
&lt;li&gt;When is liquidity withdrawn?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stage 3: Trading Intelligence&lt;/strong&gt;&lt;br&gt;
Begin monitoring Swap events and measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;early trades;&lt;/li&gt;
&lt;li&gt;buy/sell direction;&lt;/li&gt;
&lt;li&gt;trading speed;&lt;/li&gt;
&lt;li&gt;transaction value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stage 4: Wallet Intelligence&lt;/strong&gt;&lt;br&gt;
Analyze:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who is buying;&lt;/li&gt;
&lt;li&gt;who is selling;&lt;/li&gt;
&lt;li&gt;whether the deployer is trading;&lt;/li&gt;
&lt;li&gt;whether certain addresses repeatedly appear among the early traders of high-performing new tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stage 5: Risk Engine&lt;/strong&gt;&lt;br&gt;
Perform unified checks on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;token permissions;&lt;/li&gt;
&lt;li&gt;hooks;&lt;/li&gt;
&lt;li&gt;fees;&lt;/li&gt;
&lt;li&gt;liquidity;&lt;/li&gt;
&lt;li&gt;wallet concentration;&lt;/li&gt;
&lt;li&gt;abnormal trading behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Final Stage: Strategy Engine&lt;/strong&gt;&lt;br&gt;
Only then do we turn the previous on-chain data into rules or scores, decide whether a pool should enter a watchlist, and determine whether the strategy may eventually be allowed to execute swaps automatically.&lt;/p&gt;

&lt;p&gt;In this way, a new-pool listener that initially contains only a few hundred lines of code can gradually evolve into a complete Robinhood Chain on-chain quantitative data and strategy system.&lt;/p&gt;

&lt;h2&gt;
  
  
  19. Conclusion
&lt;/h2&gt;

&lt;p&gt;This exercise demonstrates something interesting.&lt;/p&gt;

&lt;p&gt;For a new EVM chain, we do not necessarily need to wait until a complete SDK, third-party data platform, or ready-made quantitative framework becomes available before we begin development.&lt;/p&gt;

&lt;p&gt;As long as the network exposes standard Ethereum JSON-RPC, FMZ can already read on-chain data directly from the underlying infrastructure.&lt;/p&gt;

&lt;p&gt;The core interfaces used in this article are actually quite limited:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;eth_chainId;&lt;/li&gt;
&lt;li&gt;eth_blockNumber;&lt;/li&gt;
&lt;li&gt;eth_getLogs;&lt;/li&gt;
&lt;li&gt;ERC20 eth_call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yet with these very basic interfaces, we have already completed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Robinhood Chain mainnet connectivity;&lt;/li&gt;
&lt;li&gt;Uniswap V4 PoolManager monitoring;&lt;/li&gt;
&lt;li&gt;Initialize event decoding;&lt;/li&gt;
&lt;li&gt;token metadata queries;&lt;/li&gt;
&lt;li&gt;ETH/WETH new-pool filtering;&lt;/li&gt;
&lt;li&gt;a real-time dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More importantly, during actual operation we successfully captured newly created V4 pools such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ETH / GREAT;&lt;/li&gt;
&lt;li&gt;ETH / MERRY;&lt;/li&gt;
&lt;li&gt;ETH / ANTIDOTE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strategy description:&lt;/p&gt;

&lt;p&gt;Robinhood Chain · Uniswap V4 New Pool Radar V2&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is provided solely for Web3 technical research and software-development practice and does not constitute investment advice. Newly issued tokens and low-liquidity on-chain assets can involve substantial price, smart-contract, and liquidity risks. Before adding any automated trading functionality, the system should be thoroughly tested and equipped with comprehensive risk controls.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>uniswapv4</category>
      <category>fmzquant</category>
      <category>web3</category>
      <category>defi</category>
    </item>
    <item>
      <title>`The Bull Is Now` Got Listed on Binance Futures. I Was Afraid to Chase Highs or Buy Lows, So I Built a Strategy That Doesn't Bet on Direction</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Fri, 04 Sep 2026 02:58:22 +0000</pubDate>
      <link>https://dev.to/quant001/the-bull-is-now-got-listed-on-binance-futures-i-was-afraid-to-chase-highs-or-buy-lows-so-i-2kal</link>
      <guid>https://dev.to/quant001/the-bull-is-now-got-listed-on-binance-futures-i-was-afraid-to-chase-highs-or-buy-lows-so-i-2kal</guid>
      <description>&lt;p&gt;At 11:30 on August 30, Binance listed the USDT-margined perpetual contract for 牛来, whose contract symbol is literally the Chinese 牛来USDT. The coin comes from a Chinese animated film that two people spent five years making and that went viral partly because of its rough-looking visuals. On top of that, 牛来 sounds like “the bull market is coming,” and that is how the meme took off. On its first day of trading, its 24-hour amplitude reached 57.4%, it rose 28.7% in a single day, and its minute-based realized volatility reached 95.7 basis points. I compared it cross-sectionally against 612 perpetual contracts across the entire market, and it ranked first in both the depth and frequency of lower wicks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3ivusiljmla5x1pqqg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz3ivusiljmla5x1pqqg4.png" alt=" " width="799" height="353"&gt;&lt;/a&gt;&lt;br&gt;
That is also where the problem starts. With volatility like this, if you chase the long side, you do not know whether you are buying at the top; if you short it, one wick can blow you up. I was afraid of both the highs and the lows, so I did not dare take either side. But with roughly $400 million in daily turnover sitting there, I also did not want to just watch from the sidelines.&lt;/p&gt;

&lt;p&gt;On this type of coin, I first tried several of the most intuitive approaches: placing low bids to catch wicks, running a two-sided grid, and arbitraging around funding settlements. None of them worked. Looking back, the reason for failure was the same in every case: they were all betting that “if price falls, it will bounce back.” Catching wicks bets on mean reversion after a deep wick; grids bet on price oscillating within a range; funding-rate sniping bets on a post-settlement price reversal. But the data gave a very consistent answer: the deep-wick reversion rate for 牛来 was only 0.31; shallow pullbacks had a reversion rate of 1.65, but the order-book queue made them practically impossible to fill; and in a 139-day grid sample, sideways and rising windows were almost always profitable, while large down-move windows had a win rate of only 8%.&lt;/p&gt;

&lt;p&gt;High-volatility meme perpetuals are momentum assets, not mean-reverting assets. Once I understood that, only one direction remained: do not fight them; move with them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4pekl1xgrk8c1dtwh9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4pekl1xgrk8c1dtwh9s.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Strong Stay Strong, and How to Define “Strong”
&lt;/h2&gt;

&lt;p&gt;The simplest form of a trend-following strategy is to buy the assets that have risen the most and sell the assets that have fallen the most. But looking only at returns has an obvious flaw: return is the result, not the cause. A coin may be up 20% because sustained new capital is pushing it higher, or it may be up because a few short-covering trades caused a temporary squeeze. The subsequent paths of those two situations can be completely different, even though the candles look identical.&lt;/p&gt;

&lt;p&gt;So I added a second dimension: the funding rate.&lt;/p&gt;

&lt;p&gt;The funding rate of a perpetual contract is essentially a mechanism for balancing the holding costs between longs and shorts. When the funding rate is pushed higher, it means a large number of traders are willing to keep paying to hold long positions. That is different from simply saying “the price went up.” A large order can push price higher, but the funding rate reflects the structure of open positions and therefore represents real willingness to pay to maintain that exposure.&lt;/p&gt;

&lt;p&gt;Price momentum tells you, “it went up.” The funding rate tells you, “someone is willing to pay to stay in this direction.” Combine these two dimensions into a composite momentum score, go long the highest-scoring group and short the lowest-scoring group, with equal notional amounts on both sides. If the whole sector rises or falls together, the long and short legs offset each other. The return comes only from the relative performance between “the strongest group” and “the weakest group.” This directly avoids the original dilemma: you do not need to predict whether 牛来 will rise or fall; you only need to judge whether it is stronger or weaker than the other speculative coins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnzyrtudqqp9s9ylosdn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnzyrtudqqp9s9ylosdn1.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How Much Weight Should Each of the Two Factors Get?
&lt;/h2&gt;

&lt;p&gt;At first, I used fixed coefficients: 1.0 for price momentum and 0.6 for funding. Once I ran it, I immediately felt something was wrong. Those two numbers were chosen by intuition and had no real basis. Why 0.6 instead of 0.4?&lt;/p&gt;

&lt;p&gt;The bigger problem is that even if I find the coefficients that are optimal right now, they may not remain stable. During testing I observed something interesting: for the very same funding-rate factor, changing the universe from 44 contracts to 35, or changing the time window from 41 days to 31 days, could flip the sign of its performance. If a factor can change sign as market conditions change, using a fixed weight effectively welds the characteristics of one historical regime permanently into the strategy.&lt;/p&gt;

&lt;p&gt;So I changed the system to let the data decide. The method is rolling cross-sectional regression: at each period, use all candidate contracts available at that time to run a cross-sectional OLS regression of the next-period realized return on the z-scores of the two factors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Single-period cross-sectional OLS:
// fwd = a + b1*z(momentum) + b2*z(funding)
function crossSectionOLS(zm, zf, fwd) {
    var n = fwd.length; if (n &amp;lt; 10) return null;
    var A = [[0,0,0],[0,0,0],[0,0,0]], b = [0,0,0];
    for (var i = 0; i &amp;lt; n; i++) {
        var x = [1, zm[i], zf[i]];
        for (var r = 0; r &amp;lt; 3; r++) {
            b[r] += x[r] * fwd[i];
            for (var c = 0; c &amp;lt; 3; c++) A[r][c] += x[r] * x[c];
        }
    }
    return solve3(A, b);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this period by period over the past 120 periods (one period every four hours, or about 20 days), obtain a time series of coefficients for both factors, and then take the time-series mean. This is the Fama-MacBeth approach. The mean gives you the weight; the standard deviation gives you the significance.&lt;/p&gt;

&lt;p&gt;The key question is how to use that significance. A coefficient mean can be large, but if it jumps around wildly over time, then the factor behaves very differently in different periods and should not receive a high weight. So the coefficient is shrunk using its t-statistic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function shrink(r) {
    var k = Math.min(1, Math.abs(r.t) / T_SHRINK);   // Full weight when |t|&amp;gt;=2; otherwise shrink proportionally
    return r.m * k;
}
var w1 = shrink(r1), w2 = shrink(r2);
var scale = Math.abs(w1) + Math.abs(w2);
w1 = w1 / scale;  w2 = w2 / scale;                  // Normalize so |w1|+|w2|=1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has three benefits. An insignificant factor is automatically downweighted, so I do not need to decide manually how much weight each one deserves. The sign is determined by the data: if, during some period, the correct use of the funding factor is contrarian, the regression will produce a negative coefficient and the strategy will follow it instead of stubbornly keeping the positive sign I originally wrote down. And once the weights are normalized, total exposure is unaffected; only the relative influence of the two factors changes.&lt;/p&gt;

&lt;p&gt;The model is refit every 12 hours. The dashboard directly displays the β and t-values of the two factors, so you can see where the current weights come from. If one day the t-value of the funding factor drops to 0.3, you will see its weight automatically shrink close to the lower bound instead of remaining hidden from view.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Pitfall That Must Be Fixed: The Time Basis of Funding Rates
&lt;/h2&gt;

&lt;p&gt;This is something I ran into during implementation, and its impact is large enough to deserve its own section.&lt;/p&gt;

&lt;p&gt;Different Binance contracts have different funding settlement intervals. Querying the fundingInfo endpoint, among the 768 contracts with records across the whole market, 440 settle every four hours, 324 every eight hours, and another four every one hour. 牛来 itself settles every four hours.&lt;/p&gt;

&lt;p&gt;The problem is that a contract settling every four hours at a funding rate of five basis points and a contract settling every eight hours at five basis points display the same nominal number, but the true holding cost differs by a factor of two. The former charges six times per day; the latter only three times.&lt;/p&gt;

&lt;p&gt;If raw funding rates are used directly for cross-sectional ranking, you are effectively comparing numbers with different units. This systematically pushes four-hour contracts lower and eight-hour contracts higher, even though more than half of the universe consists of four-hour contracts. The selected long and short legs become distorted.&lt;/p&gt;

&lt;p&gt;The fix is simple, but it cannot be skipped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Pull each contract's settlement interval
var fi = api('fundingInfo');
for (var i = 0; i &amp;lt; fi.length; i++)
    _ivh[fi[i].symbol] = parseInt(fi[i].fundingIntervalHours || 8);

// ★ Normalize to an 8-hour basis before ranking
c.fund8 = c.fr * (8.0 / ivh(c.bin));

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Risk Parity: Make Each Coin Contribute the Same Amount of Risk
&lt;/h2&gt;

&lt;p&gt;Volatility differs enormously across a meme-coin universe. Within the same candidate set, four-hour volatility can range from 15 basis points to 200 basis points. If notional amounts are allocated equally, the two or three most volatile coins can contribute more than 80% of the portfolio's risk while the rest are effectively irrelevant. On paper you may hold ten coins, but in practice you are betting on only two of them.&lt;/p&gt;

&lt;p&gt;So within each leg, weights are assigned inversely to volatility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function riskParity(g) {
    var iv = [], z = 0;
    for (var i = 0; i &amp;lt; g.length; i++) { var x = 1 / Math.max(5, g[i].sigma); iv.push(x); z += x; }
    var w = []; for (var j = 0; j &amp;lt; g.length; j++) w.push(iv[j] / z);
    return w;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Among all the improvements I tried, this was the only one that improved all three metrics across the full sample at the same time: the median increased from 36.5 basis points to 68.0, the win rate rose from 53% to 57%, and the worst single result narrowed from -2021 to -1422. This is not parameter fitting; it is structural. Once risk is distributed more evenly, portfolio performance is no longer determined by the luck of one or two coins.&lt;/p&gt;

&lt;p&gt;At the portfolio level, another layer of target-volatility scaling is added. First estimate the volatility level of the current candidate portfolio. If it is above the target, reduce total exposure proportionally; if it is below the target, keep exposure at the cap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var pv = 0;
for (var a = 0; a &amp;lt; L.length; a++) pv += wl[a] * L[a].sigma;
for (var b = 0; b &amp;lt; Sh.length; b++) pv += ws[b] * Sh[b].sigma;
pv = pv / 2 * 0.6 / 1e4 * 100;                         // Residual volatility estimate after long/short hedging
var scale = pv &amp;gt; 0 ? Math.min(1, TARGET_VOL_PCT / pv) : 1;
var gross = GROSS_MAX_USDT * scale;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of this is to make the return distribution more predictable. Meme-coin markets can be extremely quiet at times and explode all at once at others. Fixed notional exposure means your true risk can drift by several multiples depending on market conditions. Target-volatility scaling smooths that drift, at the cost of automatically reducing positions when the market is at its most violent — which is exactly when they should be reduced.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5vtztyk8j2tcitr2cre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5vtztyk8j2tcitr2cre.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Unified Account: Unrealized Losses on Long and Short Legs Offset Each Other
&lt;/h2&gt;

&lt;p&gt;This is one of the easiest things to overlook in live trading for this kind of strategy, yet it has one of the largest impacts on survival.&lt;/p&gt;

&lt;p&gt;Suppose you hold ten longs and ten shorts. Under isolated margin, maintenance margin is calculated independently for each leg. Any single leg that reaches its liquidation threshold can be forcibly liquidated — even if the portfolio as a whole is profitable. Intraday swings of 20% are common in meme coins, so under isolated margin this is almost guaranteed to happen eventually.&lt;/p&gt;

&lt;p&gt;Under a unified-account cross-margin mode, unrealized profits and losses on the long and short legs offset each other at the account level. When the whole sector rises, unrealized profits on the long leg directly offset unrealized losses on the short leg. With the same notional exposure, the maintenance-margin ratio is far higher than under isolated margin. For a market-neutral long-short portfolio, this is not optional; it is required.&lt;/p&gt;

&lt;p&gt;The strategy displays a reminder about this at startup. Leverage is also recommended to stay below 3x. Leverage is for improving capital efficiency, not for amplifying the signal. The strength of the signal itself does not become stronger just because leverage is higher.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotation Frequency: Faster Is Not Always Better
&lt;/h2&gt;

&lt;p&gt;The composite score changes in real time, so in theory the portfolio could be reranked every five minutes. I tested this directly, running nine different rotation intervals from five minutes to 24 hours on the same batch of data. The conclusion was very clear:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7d4ss1nx6xk00akk330t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7d4ss1nx6xk00akk330t.png" alt=" " width="479" height="220"&gt;&lt;/a&gt;&lt;br&gt;
Anything within one hour loses money. Turnover is extremely high at every interval — out of 20 legs, 16 to 19 need to be replaced each period, which shows that rankings are themselves highly unstable at short horizons. The higher the frequency, the more fees you pay, while the incremental signal you capture becomes smaller.&lt;/p&gt;

&lt;p&gt;The 12-hour and 24-hour numbers are higher, but there are only 83 and 41 samples respectively, and the gap between median and mean is enormous (for the raw 24-hour signal, the median is -22.62 while the mean is +151.45). That means the return is supported by only a few large wins. The four-hour interval is different: median +12.23, mean +17.30, 53% win rate, and 249 samples. The median and mean have the same sign and are close to each other — the typical trade is profitable rather than the result being propped up by the tail.&lt;/p&gt;

&lt;p&gt;There is also a structural reason for four hours: nearly half the contracts in the universe settle funding every four hours, so the rotation interval lines up with the funding settlement interval. That is why the default parameter is set to four hours.&lt;/p&gt;
&lt;h2&gt;
  
  
  Execution Layer: Incomplete Positions Are Not Allowed to Exist
&lt;/h2&gt;

&lt;p&gt;The strategy is supposed to hold 20 legs at the same time. In a backtest, that is one line of code, and every leg is assumed to fill at the mid-price. Live trading is not like that.&lt;/p&gt;

&lt;p&gt;At first I wrote a “failed leg counter.” If one leg could not be opened, I recorded the failure and abandoned the round after the count exceeded a threshold. After writing it, the design felt wrong. Eventually I realized why: a failed leg should not be counted. It should be eliminated.&lt;/p&gt;

&lt;p&gt;This is a market-neutral strategy, and all of its safety comes from the two sides offsetting each other. If the plan is 10 longs and 10 shorts, but only nine longs and seven shorts actually fill, what you hold is no longer a neutral portfolio. It is a directional net-long bet of two legs — while you may still think you are neutral. The risk model becomes invalid at that moment. Worse, the imbalance does not heal itself. Until the next rotation, you may carry that incorrect exposure for the entire four-hour period.&lt;/p&gt;

&lt;p&gt;So the first layer is in the planning stage: untradeable legs are removed directly, and the opposite side is trimmed at the same time to guarantee that the number of legs and notional amounts on both sides remain strictly equal. If one side cannot assemble enough valid contracts, reduce the number of legs for the entire portfolio and try again — from 10 pairs to 9 pairs, then 8 pairs — until both sides qualify.&lt;/p&gt;

&lt;p&gt;The second layer is in the execution stage, looping until the actual positions match the targets exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function strictOpen(targets) {
    S.execState = 'OPENING'; _targets = targets; saveState();
    while (n++ &amp;lt; MAX_REPAIR_LOOP) {
        if (!cancelAll(list)) { Sleep(ORDER_SETTLE_MS); continue; }   // Cancel orders first and confirm nothing remains
        var pq = queryPos(true);
        if (!pq.ok) { Sleep(ORDER_SETTLE_MS); continue; }             // Position query failed → never submit orders
        if (exactMatch(pq.positions, targets)) {
            var v = queryPos(true);                                    // Second confirmation
            if (v.ok &amp;amp;&amp;amp; exactMatch(v.positions, targets)) {
                S.execState = 'HOLDING'; saveState(); return true;
            }
            continue;
        }
        submitDiff(pq.positions, targets, LIMIT_FIRST &amp;amp;&amp;amp; n &amp;lt;= 2);       // Submit only the position difference
    }
    strictClose('Entry failed to converge');
    return false;
}
// Quantity comparisons must convert values to integers at the instrument precision;
// direct floating-point equality comparisons will never match reliably.
function qtyU(f, q) { return Math.round(Math.abs(q) * Math.pow(10, spec(f).ap)); }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is one rule here that cannot be omitted: if the true positions cannot be queried, all order submission must be prohibited. API errors, malformed responses, rate-limit rejections — under any of those conditions, do nothing. Sending orders when you do not know what positions you actually hold is one of the most classic types of trading-system accidents.&lt;/p&gt;

&lt;p&gt;Finally there is a net-exposure guard. Every time positions are reconciled, the strategy calculates the deviation between total long notional and total short notional. If it exceeds 8%, all positions are immediately closed and the system starts again. This also determines an execution trade-off: limit orders can reduce fees but may fail to fill; failed fills mean the legs are unbalanced. So the first two repair rounds use limit orders in an attempt to obtain maker execution, while the third round onward switches to market orders to force completion. For a neutral strategy, exposure imbalance is more dangerous than paying a little more in fees. That priority must not be reversed.&lt;/p&gt;

&lt;p&gt;牛来 is only the starting point. On the day it listed, its amplitude, volatility, and lower-wick depth were all near the top of the market. It is simply the newest sample in this category of speculative coins. The strategy itself does not depend on it at any point. The target is not one specific coin; it is the entire cross-section. Today the strongest name may be 牛来. Next week it may be another newly listed meme. The week after that, it may be an old coin that suddenly attracts capital again. You do not need to know which one comes next. You only need to ask every four hours: “Who is strongest now, and who is weakest?” Then let the positions follow the answer.&lt;/p&gt;

&lt;p&gt;That is also the difference between this strategy and “chasing hot themes.” Chasing a theme is a bet on one specific asset, and your odds depend on whether you picked the right one. Cross-sectional rotation is a bet on the statistical property that “dispersion between strength and weakness will persist.” If you choose one individual name incorrectly, the other nine legs dilute the mistake. The more volatile the meme-coin sector becomes, the more visible this dispersion tends to be. The high volatility that most strategies fear is precisely the fuel for this type of strategy.&lt;/p&gt;

</description>
      <category>cryptoquant</category>
      <category>cryptotrading</category>
      <category>binance</category>
      <category>strategy</category>
    </item>
    <item>
      <title>Can the Displayed 24h Change Affect Short-Term Prices? — Implementing a Roll-Out TradFi Strategy in FMZ Rust</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Fri, 04 Sep 2026 02:19:53 +0000</pubDate>
      <link>https://dev.to/quant001/can-the-displayed-24h-change-affect-short-term-prices-implementing-a-roll-out-tradfi-strategy-in-2o35</link>
      <guid>https://dev.to/quant001/can-the-displayed-24h-change-affect-short-term-prices-implementing-a-roll-out-tradfi-strategy-in-2o35</guid>
      <description>&lt;p&gt;&lt;em&gt;This article studies a behavioral-finance hypothesis created by a rolling statistical window. Instead of directly forecasting future prices, we calculate in advance how the 24h percentage change traders will see over the next several dozen minutes will mechanically evolve, and then observe whether price responds to that change in attention. The current strategy does not directly measure aggressive order flow, so the transmission mechanism discussed here is a plausible pathway rather than a proven causal relationship between order flow and price.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A Number That Looks Almost Too Ordinary to Matter
&lt;/h2&gt;

&lt;p&gt;Almost every crypto trading interface places the 24-hour percentage change in a highly visible position. Top-gainer lists, top-loser lists, contract tables, and mobile market pages all keep reinforcing this number.&lt;/p&gt;

&lt;p&gt;We usually interpret the 24h change as “what happened over the last day,” but it is actually a continuously moving window:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6biumucy1qh6jtzgkdlc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6biumucy1qh6jtzgkdlc.png" alt=" " width="216" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its evolution is jointly determined by two components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What the current price (P_t) does next — unknown.&lt;/li&gt;
&lt;li&gt;How the price from 24 hours ago, P_{t-24h} , leaves the rolling window — already observed and therefore known.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Suppose price rallied sharply yesterday between 14:00 and 14:30. When the market reaches the same time today, that rally gradually rolls out of the 24-hour window minute by minute. Even if the current price does not move at all, the displayed 24h gain will mechanically decline. Conversely, when a sharp selloff from yesterday rolls out, the displayed return will mechanically improve.&lt;/p&gt;

&lt;p&gt;That fact by itself is only a mathematical identity, not Alpha. The hypothesis that actually needs to be tested is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Predictable change in the displayed statistic
                ↓
Changes in rankings, screeners, and trader attention
                ↓
Trading behavior may change
                ↓
Potentially tradable price drift
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, the strategy is not directly predicting price. It is predicting what market participants are about to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Public Research Gives Us as a Starting Point
&lt;/h2&gt;

&lt;p&gt;The idea was popularized by Robot James in the article &lt;strong&gt;A Truly Idiotic Crypto Trade&lt;/strong&gt;, and was later reproduced on a large historical sample by the open-source project &lt;strong&gt;OctopusTakopi/24h-rollout-effect&lt;/strong&gt;, using Binance USDT perpetual-futures archives.&lt;/p&gt;

&lt;p&gt;Robot James article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://robotjames.substack.com/p/a-truly-idiotic-crypto-trade" rel="noopener noreferrer"&gt;https://robotjames.substack.com/p/a-truly-idiotic-crypto-trade&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Public replication:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/OctopusTakopi/24h-rollout-effect" rel="noopener noreferrer"&gt;https://github.com/OctopusTakopi/24h-rollout-effect&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The coarse-grained public rule is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the largest positive hourly candle from exactly 24 hours ago begins to roll out, short for one hour.&lt;/li&gt;
&lt;li&gt;When the largest negative hourly candle from exactly 24 hours ago begins to roll out, go long for one hour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The replication covers 788 Binance USDT perpetual contracts from 2020 to 2026, including delisted contracts. Its results support the presence of a 24h Roll-Out anomaly in the historical sample. Placebo tests over candle ages from 1 to 24 hours show that the effect is concentrated specifically around the 24th hour.&lt;/p&gt;

&lt;p&gt;But the research also provides a conclusion that matters more than the headline return: the raw signal is thin.&lt;/p&gt;

&lt;p&gt;High turnover allows fees to consume much of the gross edge, funding costs erode it further, and spectacular results from concentrated full-capital backtests often mix together path luck, compounding mathematics, and tail risk. In the public sample, most of the more recent edge is concentrated on the short side, while a single extreme move against a short position can wipe out the profits from a large number of ordinary trades.&lt;/p&gt;

&lt;p&gt;So the public result is enough to justify further research. It is not enough to declare that a production-ready strategy already exists.&lt;/p&gt;

&lt;p&gt;The original contribution of this article is not to rediscover the 24h anomaly. It is to convert the idea into a continuous minute-level model, transfer it into Binance's TradFi perpetual market, and implement a complete FMZ Rust prototype that is observable, simulatable, and capable of live execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why Not Simply Short Anything That Is Up a Lot Over 24 Hours?
&lt;/h2&gt;

&lt;p&gt;The current 24h percentage change can be used as an attention signal, but it does not determine the trading direction.&lt;/p&gt;

&lt;p&gt;Suppose a contract is currently up 20%, but the corresponding price path 24 hours earlier was almost flat. In that case there is no meaningful Roll-Out catalyst scheduled to occur over the next half hour. Shorting merely because the asset is “up a lot” would be nothing more than a conventional reversal strategy.&lt;/p&gt;

&lt;p&gt;This strategy therefore keeps two concepts strictly separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current 24h Change       = Attention Filter
Expected Roll-Out Shock = Alpha Trigger

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first is used to reduce the universe that requires deep monitoring. The second is what determines the candidate direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Turning a Single Hourly Candle into a Continuous Roll-Out Curve
&lt;/h2&gt;

&lt;p&gt;An hourly rule implicitly assumes that all the information arrives at the top of the hour. Real price paths are continuous.&lt;/p&gt;

&lt;p&gt;Price changes from yesterday at 14:05, 14:12, and 14:27 roll out of the 24h calculation today at the corresponding times. They do not disappear all at once at 14:00.&lt;/p&gt;

&lt;p&gt;Let the current price be  P(t) , and let the current starting point of the 24-hour window be P(t-24h). Assume, only for the purpose of calculating the mechanical display effect, that the current price remains unchanged over the next h minutes. The mechanical change in the displayed 24h return is then:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6ql6tcx5al4d0k95lwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6ql6tcx5al4d0k95lwn.png" alt=" " width="468" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shock(30) &amp;gt; 0: the displayed return is expected to mechanically improve, so the candidate direction is Long.&lt;/li&gt;
&lt;li&gt;Shock(30) &amp;lt; 0: the displayed return is expected to mechanically deteriorate, so the candidate direction is Short.&lt;/li&gt;
&lt;li&gt;Shock(5) and Shock(30) must have the same sign, so the old price path does not first move against the eventual direction.&lt;/li&gt;
&lt;li&gt;The five-minute shock must also reach a minimum intensity, so the 30-minute signal is not concentrated almost entirely at the far end of the window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current price                               120
Current start of the 24h window            100
Price 24h ago, 30 minutes later            115

Displayed 24h gain now                   +20.0%
Displayed gain in 30m if price is flat    +4.35%
Shock(30)                                -15.65 percentage points
Candidate direction                       SHORT

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The -15.65 percentage points represent the mechanical change in the displayed statistic. They do not mean that price is forecast to fall by 15.65%.&lt;/p&gt;

&lt;p&gt;How much, if any, of that display shock is reflected in price must be answered empirically.&lt;/p&gt;

&lt;p&gt;The strategy's core calculation maps directly to the formula. The final direction is determined solely by the sign of Shock(30):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signal.shock_5_pct =
    100.0 * current * (1.0 / old_5 - 1.0 / old_now);

signal.shock_30_pct =
    100.0 * current * (1.0 / old_30 - 1.0 / old_now);

signal.direction = if signal.shock_30_pct &amp;gt; 0.0 {
    1
} else if signal.shock_30_pct &amp;lt; 0.0 {
    -1
} else {
    0
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Why the First Version Targets TradFi Perpetuals
&lt;/h2&gt;

&lt;p&gt;Binance TradFi perpetuals map equities, ETFs, commodities, and other traditional-market exposures into USDT-margined perpetual contracts.&lt;/p&gt;

&lt;p&gt;This environment is interesting for several reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The products are still presented inside a crypto-style interface with 24h percentage changes and rankings.&lt;/li&gt;
&lt;li&gt;Traditional-market opens, closes, pre-market, after-hours, and overnight sessions may create more structured historical price paths.&lt;/li&gt;
&lt;li&gt;TradFi perpetuals have different participant mixes, liquidity profiles, and information rhythms from purely crypto-native assets, making them useful for testing whether the mechanism transfers.&lt;/li&gt;
&lt;li&gt;On the test account used during development, the displayed maker fee for these products was zero. That makes it easier to observe a thin gross signal at the research stage. This was only the fee state of a specific account at a specific point in time and must not be generalized to other accounts or future fee schedules.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A zero maker fee does not mean zero trading cost.&lt;/p&gt;

&lt;p&gt;Queue failure, adverse selection, bid-ask spreads, funding, and emergency-exit slippage still exist. The prototype therefore uses real order-book data and maker-fill constraints instead of assuming that sending a resting order means it has been filled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynfvi3pq44cyae5abtg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynfvi3pq44cyae5abtg1.png" alt=" " width="800" height="335"&gt;&lt;/a&gt;&lt;br&gt;
More importantly, the public historical replication validates Binance crypto USDT perpetuals, not the TradFi submarket studied here.&lt;/p&gt;

&lt;p&gt;Transferring the mechanism to TradFi perpetuals is a new hypothesis that still needs to be validated. The historical crypto results cannot be borrowed as evidence of profitability in the TradFi market.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Data Architecture of the FMZ Rust Prototype
&lt;/h2&gt;

&lt;p&gt;The first version uses a light full-market scan + deep tracking of a small candidate set architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Binance exchangeInfo
        │
        └── Dynamically discover TradFi perpetuals

Binance all-market WebSocket 24h ticker
        │
        └── Attention Scanner
                 │
                 └── Top-N candidates
                        ├── ~25h of recent 1m klines
                        ├── ticker
                        ├── bookTicker
                        ├── aggTrade
                        └── 1m kline
                                  │
                                  └── Roll-Out Engine

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At startup, REST is used only to read the contract directory.&lt;/p&gt;

&lt;p&gt;During normal operation, all-market market data and incremental candidate data are primarily consumed over WebSocket. The main loop uses non-blocking reads, so waiting for market data does not stop order reconciliation or state updates.&lt;/p&gt;

&lt;p&gt;The universe is not based on a manually maintained whitelist. Instead, the strategy reads contract metadata and keeps only instruments that are actively trading, quoted in USDT, and identified as TradFi perpetual products:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let subtype_tradfi = subtypes.map(|values| values.iter().any(|value| {
    value.as_str()
        .map(|text| text.eq_ignore_ascii_case("TradFi"))
        .unwrap_or(false)
})).unwrap_or(false);

let tradfi_contract =
    contract_type.eq_ignore_ascii_case("TRADIFI_PERPETUAL")
    || subtype_tradfi;

if !tradfi_contract || status != "TRADING" || quote != "USDT" {
    continue;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire market is rescanned once per minute.&lt;/p&gt;

&lt;p&gt;By default, the strategy first requires the absolute current 24h change to be at least 4%, then ranks candidates using the absolute 24h change plus a modest quote-volume weight. Only the top six candidates maintain minute history and detailed data streams.&lt;/p&gt;

&lt;p&gt;This avoids repeatedly downloading roughly 1,500 one-minute bars for hundreds of contracts.&lt;/p&gt;

&lt;p&gt;A candidate must have at least 1,475 valid one-minute bars, deduplicated by timestamp.&lt;/p&gt;

&lt;p&gt;When calculating the Roll-Out window, the implementation prefers the actual rolling-window start and end times reported by the ticker instead of blindly assuming that the window is always aligned with the local minute boundary.&lt;/p&gt;

&lt;p&gt;There is one v0.1.6 implementation boundary worth monitoring: if the initial history bootstrap for a candidate fails, bootstrap_requested_at is not automatically cleared within the current process, so the strategy will not automatically issue a second bootstrap request.&lt;/p&gt;

&lt;p&gt;If SHADOW mode remains stuck at HISTORY_NOT_READY, first check the network and API response, then restart the strategy. A future version should replace this behavior with automatic retry and backoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Signal Rules in the First Version
&lt;/h2&gt;

&lt;p&gt;Under the default parameters, a candidate must satisfy all of the following conditions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;abs(Current24hChange) &amp;gt;= 4%&lt;/li&gt;
&lt;li&gt;abs(Shock30) &amp;gt;= 0.8 percentage points&lt;/li&gt;
&lt;li&gt;abs(Shock5) &amp;gt;= 0.8 × 0.08 percentage points&lt;/li&gt;
&lt;li&gt;Shock5 and Shock30 have the same sign&lt;/li&gt;
&lt;li&gt;The ticker is no more than 20 seconds old&lt;/li&gt;
&lt;li&gt;Immediately before entry, book and trade data are no more than 5 seconds old&lt;/li&gt;
&lt;li&gt;Bid-ask spread is no wider than 30 bps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The design is intentionally simple.&lt;/p&gt;

&lt;p&gt;The original research material also discusses rank effects, open interest, funding, aggressive buy/sell flow, and Probe-then-Build position scaling. All of those modules are deliberately postponed in the first version.&lt;/p&gt;

&lt;p&gt;Otherwise, if performance changes, it becomes difficult to determine which component was responsible.&lt;/p&gt;

&lt;p&gt;The gating order in code is also kept interpretable: first attention, then 30-minute shock intensity, then confirmation that the first five minutes do not point in the opposite direction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if signal.current_change_pct.abs() &amp;lt; AttentionThresholdPct {
    signal.reason = "ATTENTION_LOW".to_string();
} else if signal.shock_30_pct.abs() &amp;lt; MinShock30Pct {
    signal.reason = "SHOCK_LOW".to_string();
} else if signal.shock_5_pct.abs() &amp;lt; MinShock30Pct * 0.08 {
    signal.reason = "EARLY_INTENSITY_LOW".to_string();
} else if signal.shock_5_pct.signum() != signal.shock_30_pct.signum() {
    signal.reason = "PATH_NOT_MONOTONIC".to_string();
} else {
    signal.ready = true;
    signal.reason = "READY".to_string();
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Three Operating Modes
&lt;/h2&gt;

&lt;p&gt;The strategy supports three modes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsy9bamolgfmth3on2wu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsy9bamolgfmth3on2wu.png" alt=" " width="800" height="137"&gt;&lt;/a&gt;&lt;br&gt;
PAPER mode does not simply assume that a signal is filled at the displayed quote.&lt;/p&gt;

&lt;p&gt;In v0.1.6, the strategy first leaves a two-second propagation grace period, then compares newly observed aggTrade prices with the resting order price.&lt;/p&gt;

&lt;p&gt;For a buy order, the observed trade price must be no higher than the resting buy price. For a sell order, the opposite condition applies.&lt;/p&gt;

&lt;p&gt;A single entry order may wait for up to 90 seconds, but it is canceled early if the signal becomes invalid, data becomes stale, or the resting price falls more than 10 bps behind the current best quote.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let is_buy =
    (execution.order_purpose == "ENTRY" &amp;amp;&amp;amp; execution.direction == 1)
    || (execution.order_purpose == "EXIT" &amp;amp;&amp;amp; execution.direction == -1);

if is_buy {
    symbol.last_trade &amp;lt;= execution.order_price
} else {
    symbol.last_trade &amp;gt;= execution.order_price
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still only an approximate fill model.&lt;/p&gt;

&lt;p&gt;It does not simulate queue position, and the current version does not record a per-order trade sequence number at order creation. Therefore, it cannot strictly prove that the trade used to infer a fill occurred after the simulated order entered the queue.&lt;/p&gt;

&lt;p&gt;PAPER results are appropriate for screening implementation problems. They should not be treated as an exact historical reconstruction of real maker fills.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Maker Execution and the Single-Position State Machine
&lt;/h2&gt;

&lt;p&gt;The first version allows at most one event position at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDLE
  └── READY ──&amp;gt; ENTRY_WORKING (GTX Maker)
                    ├── Timeout without fill ──&amp;gt; IDLE
                    └── Filled ──&amp;gt; POSITION
                                  └── Exit condition ──&amp;gt; EXIT_WORKING
                                                        ├── Maker fill ──&amp;gt; IDLE
                                                        └── Residual position
                                                            ──&amp;gt; Market cleanup

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normal entry and exit orders use GTX/Post-Only orders to prevent a limit order from accidentally becoming a taker.&lt;/p&gt;

&lt;p&gt;The default target notional is 50 USDT.&lt;/p&gt;

&lt;p&gt;Order quantity is not sent simply as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notional / price

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, the strategy first reads CtVal, amount step, price step, and minimum notional from GetMarkets(), then converts the desired quote-currency notional into the actual number of exchange contracts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let quote_per_contract =
    contract_quote_value(spec, meta, price)?;

let amount =
    round_amount(spec, notional / quote_per_contract);

let actual_notional =
    amount * quote_per_contract;

if actual_notional + 1e-9 &amp;lt; spec.min_notional {
    return Err(format!(
        "notional {} &amp;lt; MinNotional {}",
        actual_notional,
        spec.min_notional
    ));
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first version uses the following fixed exit conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Position return at or below -0.4%: stop loss&lt;/li&gt;
&lt;li&gt;Position return reaches +0.6%: take profit&lt;/li&gt;
&lt;li&gt;Maximum holding time of 30 minutes&lt;/li&gt;
&lt;li&gt;Signal direction reverses&lt;/li&gt;
&lt;li&gt;Remaining 30-minute Shock falls below 20% of its value at entry, meaning the catalyst has largely decayed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stop loss is treated as an emergency exit and may use a market order to remove the remaining position immediately.&lt;/p&gt;

&lt;p&gt;Normal exits first attempt to use a maker order.&lt;/p&gt;

&lt;p&gt;The objective is to control tail risk, not to preserve a zero-fee assumption at all costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Why “Order Not Found” Immediately After Submission Does Not Mean the Order Does Not Exist
&lt;/h2&gt;

&lt;p&gt;There can be a short propagation delay between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the exchange accepting an order;&lt;/li&gt;
&lt;li&gt;the order becoming visible in the open-order list;&lt;/li&gt;
&lt;li&gt;the order becoming queryable through historical-order endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, querying immediately after submission and receiving null or “order not found” does not prove that the order was never created.&lt;/p&gt;

&lt;p&gt;It certainly does not justify blindly sending another identical order.&lt;/p&gt;

&lt;p&gt;The prototype uses a two-second propagation grace period, but it does not block the strategy with Sleep(2000).&lt;/p&gt;

&lt;p&gt;The order state machine continues running. During the grace period, temporary invisibility is treated as propagation rather than failure.&lt;/p&gt;

&lt;p&gt;After the grace period, the strategy checks, in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Current open orders&lt;/li&gt;
&lt;li&gt;Recent historical orders&lt;/li&gt;
&lt;li&gt;Single-order query&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This allows WebSocket data, the status panel, and other reconciliation tasks to continue operating while reducing the risk of duplicate orders caused by temporary invisibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. In Live Trading, the Hard Part Is Not Sending an Order — It Is Managing Uncertainty
&lt;/h2&gt;

&lt;p&gt;In LIVE mode, the strategy persists the order intent before calling CreateOrder.&lt;/p&gt;

&lt;p&gt;Only after it receives a non-empty order ID and persists the updated state does it clear the pending intent.&lt;/p&gt;

&lt;p&gt;The core sequence is only three steps, but the order matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Persist the Intent&lt;/li&gt;
&lt;li&gt;Send the order&lt;/li&gt;
&lt;li&gt;Update local order state only after the result is unambiguous&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The following code is a compressed version of the critical path. The full strategy later distinguishes between an explicit maker rejection, a known failure, and an unknown result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;state.pending_intent = Some(intent);
save_state(runtime, state)?;

let create_result = exchange.CreateOrder(
    symbol.meta.fmz.as_str(),
    side.as_str(),
    price,
    amount
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the network fails at the exact moment when the exchange has accepted the order but the strategy has not yet received the order ID, the strategy does not blindly resend.&lt;/p&gt;

&lt;p&gt;Instead, it enters an automatic reconciliation state and uses non-blocking queries against open orders, recent order history, and positions.&lt;/p&gt;

&lt;p&gt;If the original order is found, the strategy takes ownership of it.&lt;/p&gt;

&lt;p&gt;If a matching position is found, the local position state is reconstructed.&lt;/p&gt;

&lt;p&gt;Only after three consecutive reconciliation attempts succeed and the account is confirmed clean does the strategy conclude that the request did not create an order and resume normal operation.&lt;/p&gt;

&lt;p&gt;There is another narrower edge case.&lt;/p&gt;

&lt;p&gt;If CreateOrder follows its success-return path but returns an empty order-ID string, v0.1.6 retains the Intent and enters a conservative halt state.&lt;/p&gt;

&lt;p&gt;Automatic reconciliation still continues, but the halt flag may not clear automatically in exactly the same way as an ordinary “unknown result” branch.&lt;/p&gt;

&lt;p&gt;Before unattended LIVE deployment, this empty-ID path should be tested explicitly.&lt;/p&gt;

&lt;p&gt;If it occurs, first verify open orders, historical orders, and positions. Do not simply restart and resend.&lt;/p&gt;

&lt;p&gt;This design addresses one of the most dangerous realities in automated execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A failed return value
        ≠
The exchange definitely did not execute

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy also refuses to enter automatically if the target contract already has an external order or external position.&lt;/p&gt;

&lt;p&gt;Cancellation uses only strongly typed order IDs obtained from the current reconciliation query.&lt;/p&gt;

&lt;p&gt;Closing direction is delegated to FMZ's closebuy / closesell semantics instead of manually assembling reduceOnly flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Why There Are Only Four Parameters
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh769hw0s1o0tu9dgryjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh769hw0s1o0tu9dgryjn.png" alt=" " width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3xudb8lnbv2ih0b9pomg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3xudb8lnbv2ih0b9pomg.png" alt=" " width="694" height="183"&gt;&lt;/a&gt;&lt;br&gt;
The deep-tracking candidate count is fixed at six and is not exposed as a tuning parameter.&lt;/p&gt;

&lt;p&gt;Runtime interaction is also deliberately limited to a single pause/resume-entry control.&lt;/p&gt;

&lt;p&gt;Having few parameters does not mean the model is simplistic.&lt;/p&gt;

&lt;p&gt;The goal is to make the first batch of samples interpretable.&lt;/p&gt;

&lt;p&gt;At this stage, the most important questions are not about optimizing the backtest curve. They are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the displayed Shock in TradFi perpetuals correspond to subsequent price drift?&lt;/li&gt;
&lt;li&gt;Is there a stable gradient as Shock intensity increases?&lt;/li&gt;
&lt;li&gt;After accounting for unfilled orders, funding, and emergency exits, is any return left?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  13. A Real WebSocket Failure and How It Was Fixed
&lt;/h2&gt;

&lt;p&gt;A LIVE screenshot from v0.1.1 once showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LIVE / v0.1.1                 RUNNING
TradFi 174 / selected 0
WS age = 2100 ms
reconnects = 2
scan #1 / RUN
execution = IDLE
halt = false

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The REST contract-directory logic and execution state machine had indeed started.&lt;/p&gt;

&lt;p&gt;However, continued observation showed that the reconnect counter increased approximately every 20 seconds.&lt;/p&gt;

&lt;p&gt;After checking Binance's current WebSocket documentation, the issue became clear.&lt;/p&gt;

&lt;p&gt;v0.1.1 had mixed the all-market !ticker@arr, symbol ticker, aggTrade, kline, and bookTicker subscriptions on /public/stream.&lt;/p&gt;

&lt;p&gt;The current USDⓈ-M WebSocket interface separates the stream paths: ticker, trade, and kline-style market streams belong to the market path, while order-book streams such as bookTicker belong to the public path.&lt;/p&gt;

&lt;p&gt;Therefore, the screenshot's WS age only showed recent connection or control-message activity. It did not prove that valid market data was being received.&lt;/p&gt;

&lt;p&gt;Likewise, selected 0 could not be interpreted as evidence that no contract had crossed the 4% attention threshold.&lt;/p&gt;

&lt;p&gt;This incident demonstrates an important principle:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A successful WebSocket handshake does not prove that the subscription is valid. Liveness monitoring should be refreshed only by real business data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;v0.1.2 made four changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Market and Book data use two separate WebSocket connections and subscription sets.&lt;/li&gt;
&lt;li&gt;Subscription acknowledgements, error replies, and actual market-data messages are handled separately.&lt;/li&gt;
&lt;li&gt;After reconnection, status shows WAIT_DATA rather than pretending that the connection timestamp is the latest market-data timestamp.&lt;/li&gt;
&lt;li&gt;When candidates change, detailed streams for candidates that leave the tracked set are unsubscribed so the subscription set does not grow indefinitely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The healthy-state criteria after the fix are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Market status remains DATA_OK.&lt;/li&gt;
&lt;li&gt;Market age repeatedly falls back toward zero as new data arrives.&lt;/li&gt;
&lt;li&gt;Reconnect count does not increase on a fixed ~20-second cycle.&lt;/li&gt;
&lt;li&gt;When candidates exist, Book status should also remain DATA_OK.&lt;/li&gt;
&lt;li&gt;With no candidates, Book status should display IDLE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Subsequent LIVE validation exposed another important boundary: a GTX Maker order can be explicitly rejected because the market moves while the request is in transit.&lt;/p&gt;

&lt;p&gt;That situation is fundamentally different from a network timeout that leaves the execution result unknown.&lt;/p&gt;

&lt;p&gt;Starting in v0.1.3, the strategy preserves both the Rust Err and GetLastError() evidence.&lt;/p&gt;

&lt;p&gt;An explicit maker rejection only triggers a short cooldown.&lt;/p&gt;

&lt;p&gt;v0.1.4 then removed the manual recovery mechanism for genuinely unknown execution outcomes.&lt;/p&gt;

&lt;p&gt;An unresolved Intent is latched, and a non-blocking state machine automatically queries open orders, recent order history, and positions.&lt;/p&gt;

&lt;p&gt;If an order is found, the strategy adopts it.&lt;/p&gt;

&lt;p&gt;If a matching position is found, the local position state is reconstructed.&lt;/p&gt;

&lt;p&gt;Only after three consecutive successful reconciliation cycles show a clean account is the request classified as not having created an order, after which the strategy automatically resumes.&lt;/p&gt;

&lt;p&gt;LIVE testing also exposed a repeated-order problem under persistent signals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rest order for 20 seconds
→ cancel
→ submit again
→ cancel
→ submit again

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This strategy is a time-effect-driven directional strategy, not a market maker that continuously maintains the best quote.&lt;/p&gt;

&lt;p&gt;Frequent cancel-replace behavior sacrifices queue priority and can end up chasing a price that has already moved.&lt;/p&gt;

&lt;p&gt;v0.1.6 therefore changed the logic so that each continuous signal may create only one maker entry order.&lt;/p&gt;

&lt;p&gt;That order may wait for up to 90 seconds.&lt;/p&gt;

&lt;p&gt;It is canceled early if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the signal becomes invalid;&lt;/li&gt;
&lt;li&gt;market data becomes stale;&lt;/li&gt;
&lt;li&gt;the order falls more than 10 bps behind the current best quote.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether the order fills or not, the strategy must then wait until either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the direction reverses; or&lt;/li&gt;
&lt;li&gt;attention/Shock falls back into a lower hysteresis reset zone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;before the signal is armed again.&lt;/p&gt;

&lt;p&gt;This prevents threshold noise from repeatedly generating new orders.&lt;/p&gt;

&lt;p&gt;Partial fills are converted directly into a position. The strategy does not chase the remaining target quantity.&lt;/p&gt;

&lt;p&gt;The Paper PnL field remains zero in LIVE mode.&lt;/p&gt;

&lt;p&gt;It is only a simulation-performance field retained in the shared status layout and should not be used to judge real-account performance.&lt;/p&gt;

&lt;p&gt;A future version would benefit from separate LIVE realized-PnL and fee accounting.&lt;/p&gt;

&lt;p&gt;Even after the data path is functioning correctly, a runtime screenshot proves only that the system is receiving and processing data.&lt;/p&gt;

&lt;p&gt;It does not prove that the signal is valid, and it certainly does not prove that the strategy is profitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. How the Strategy Should Be Validated Instead of Looking Only at Total Return
&lt;/h2&gt;

&lt;p&gt;The most useful next step is to accumulate event-level observations and evaluate them using the following structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Shock-Intensity Gradient&lt;/strong&gt;&lt;br&gt;
Bucket events by abs(Shock30) and measure side-adjusted 5-, 15-, and 30-minute forward returns.&lt;/p&gt;

&lt;p&gt;A credible mechanism should generally strengthen as Shock intensity rises.&lt;/p&gt;

&lt;p&gt;If only one isolated bucket happens to be profitable, that is not stable evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Separate Long and Short&lt;/strong&gt;&lt;br&gt;
The public crypto research shows strong directional asymmetry and regime changes.&lt;/p&gt;

&lt;p&gt;TradFi samples must also be evaluated separately by side.&lt;/p&gt;

&lt;p&gt;A combined average can easily hide the failure of one side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. 24h Placebo Tests&lt;/strong&gt;&lt;br&gt;
Repeat the same calculation using neighboring windows such as 20h, 21h, 22h, and 23h.&lt;/p&gt;

&lt;p&gt;If every horizon appears “effective,” the observed return may simply be trend, reversal, or intraday seasonality rather than a mechanism tied specifically to the 24-hour display window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Fills and Trading Costs&lt;/strong&gt;&lt;br&gt;
At minimum, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of maker orders, fill rate, and timeout rate&lt;/li&gt;
&lt;li&gt;Delay from signal creation to actual fill&lt;/li&gt;
&lt;li&gt;Maximum favorable and adverse movement after entry&lt;/li&gt;
&lt;li&gt;Funding cost, share of emergency market exits, and realized slippage&lt;/li&gt;
&lt;li&gt;Performance under different bid-ask spread conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. New Information Overwhelming the Weak Catalyst&lt;/strong&gt;&lt;br&gt;
Roll-Out is only a weak catalyst.&lt;/p&gt;

&lt;p&gt;Earnings, macroeconomic data, company news, or broad crypto-market moves can completely dominate it.&lt;/p&gt;

&lt;p&gt;Event-level samples should therefore tag traditional-market sessions and unusual market conditions so that stronger information shocks are not incorrectly attributed to the display statistic.&lt;/p&gt;
&lt;h2&gt;
  
  
  15. What the Current Version Explicitly Does Not Do
&lt;/h2&gt;

&lt;p&gt;To avoid misunderstanding, v0.1.6 does not implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-symbol concurrent positions&lt;/li&gt;
&lt;li&gt;Probe / Build / Core staged scaling&lt;/li&gt;
&lt;li&gt;Averaging down against the signal&lt;/li&gt;
&lt;li&gt;Prediction of future full-market ranking positions&lt;/li&gt;
&lt;li&gt;Open-interest, funding-rate, or aggressive-flow filters&lt;/li&gt;
&lt;li&gt;A TradFi return model already proven by historical samples&lt;/li&gt;
&lt;li&gt;A complete queue-position and market-impact model&lt;/li&gt;
&lt;li&gt;Automatic backoff retry after the first historical-data bootstrap failure&lt;/li&gt;
&lt;li&gt;Forced retention of bookTicker, aggTrade, and kline detailed subscriptions when an instrument with an active order or position falls out of the Top 6 candidate set&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not “missing advanced features” that must be added immediately.&lt;/p&gt;

&lt;p&gt;They are deliberately postponed until the basic hypothesis has been validated.&lt;/p&gt;

&lt;p&gt;If there is no stable gradient from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shock → Future Return

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then adding more parameters will only create more opportunities to overfit.&lt;/p&gt;

&lt;h2&gt;
  
  
  16. Conclusion
&lt;/h2&gt;

&lt;p&gt;The most interesting part of the 24h Roll-Out effect is not that it appears strange.&lt;/p&gt;

&lt;p&gt;It is that it turns a vague behavioral-finance story into an event that can be calculated in advance:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We already know which segment of historical price action will be removed from the rolling statistic next.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But there is still a long chain between a mathematically predictable change in a displayed statistic and genuinely tradable return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Displayed-number change
        ↓
Attention
        ↓
Order flow
        ↓
Fill probability
        ↓
Fees and funding
        ↓
Tail risk
        ↓
Realized return

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Public crypto research suggests that the historical anomaly exists but is fragile.&lt;/p&gt;

&lt;p&gt;The TradFi prototype in this article attempts to answer the next question using a more continuous, restrained, and observable implementation.&lt;/p&gt;

&lt;p&gt;A credible first version should not rush to prove that it makes money.&lt;/p&gt;

&lt;p&gt;It should first ensure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the data path is reliable;&lt;/li&gt;
&lt;li&gt;the signal has a clear meaning;&lt;/li&gt;
&lt;li&gt;every order state can be reconciled;&lt;/li&gt;
&lt;li&gt;failures do not create duplicate orders;&lt;/li&gt;
&lt;li&gt;both positive and negative results are recorded in a form that can be explained later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the role of this FMZ Rust prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy
&lt;/h2&gt;

&lt;p&gt;The strategy is still a prototype and may be adjusted or upgraded as more real-world observations are collected.&lt;/p&gt;

&lt;p&gt;It is shared as a research implementation for discussion and learning. Anyone considering live deployment should independently evaluate, analyze, and optimize it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Risk Warning: This article and the accompanying strategy are intended solely for quantitative research and engineering validation and do not constitute investment advice. Perpetual contracts can experience large gaps, liquidity collapse, abnormal funding rates, and forced liquidation. As more participants discover a historical anomaly, the effect may weaken or disappear.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tradfi</category>
      <category>tradingbot</category>
      <category>quantstrategy</category>
      <category>crypto</category>
    </item>
    <item>
      <title>Adaptive Conditional Probability Modeling: A Practical Exploration and Implementation for Binary Markets</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Thu, 27 Aug 2026 08:24:09 +0000</pubDate>
      <link>https://dev.to/quant001/adaptive-conditional-probability-modeling-a-practical-exploration-and-implementation-for-binary-3kog</link>
      <guid>https://dev.to/quant001/adaptive-conditional-probability-modeling-a-practical-exploration-and-implementation-for-binary-3kog</guid>
      <description>&lt;p&gt;I had wanted for some time to build a relatively complete model for binary markets.&lt;/p&gt;

&lt;p&gt;At first glance, these markets look simple. Take Polymarket's 15-minute BTC Up/Down market as an example: each round has only two outcomes. If the settlement price finishes above the benchmark price, Up wins; otherwise, Down wins. There is no complex payoff curve, nor the large matrix of strikes and maturities found in traditional options.&lt;/p&gt;

&lt;p&gt;But once you try to turn the idea into a quantitative system, it becomes surprisingly difficult to find the right entry point.&lt;/p&gt;

&lt;p&gt;If the task is reduced to predicting whether BTC will rise or fall next, the problem quickly becomes another moving-average, momentum, breakout, or order-flow strategy. If we simply treat the Polymarket contract price as the probability, it becomes difficult to answer another important question: has the market price already incorporated the information we currently observe?&lt;/p&gt;

&lt;p&gt;When I revisited Bayesian ideas recently, a different framing occurred to me: perhaps what a binary market really requires us to model is not direction, but conditional probability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffll6aqyw43ikec7xguw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffll6aqyw43ikec7xguw.png" alt=" " width="595" height="79"&gt;&lt;/a&gt;&lt;br&gt;
Here,Y is the final settlement outcome, while X_t represents the information observable at time t, including BTC's position relative to the benchmark price for the current round, remaining time, volatility across multiple horizons, short-term drift, the price path, and the uncertainty of the information currently available.&lt;/p&gt;

&lt;p&gt;The question therefore changes from:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Will BTC go up?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Given the conditions that have already occurred, what is the probability that BTC will ultimately finish above the benchmark price?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This may sound like a simple change in wording, but it determines the architecture of the entire system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Conditional Probability Fits Binary Markets
&lt;/h2&gt;

&lt;p&gt;Suppose the Up contract can currently be bought at 0.58. Temporarily ignoring trading frictions, we can interpret that as the market assigning Up a price of roughly 58%.&lt;/p&gt;

&lt;p&gt;If our model also estimates the probability at 58%, then even if the directional conclusion is Up, there is no trading value. A trade becomes potentially interesting only when the model's conditional probability differs sufficiently from the price we can actually execute.&lt;/p&gt;

&lt;p&gt;We therefore need two separate quantities:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrcxategpte4l3ylln6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrcxategpte4l3ylln6v.png" alt=" " width="592" height="213"&gt;&lt;/a&gt;&lt;br&gt;
The second quantity,q_t , cannot simply be the best Ask displayed on the page. A real order may consume multiple levels of the order book, while fees and slippage also matter.&lt;/p&gt;

&lt;p&gt;The implementation therefore walks through Ask depth using the intended purchase quantity, calculates the full VWAP, and then computes the expected absolute profit per token:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9efwckw9wg5f638nefk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9efwckw9wg5f638nefk.png" alt=" " width="595" height="80"&gt;&lt;/a&gt;&lt;br&gt;
The corresponding code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn expected_value(prob: f64, buy_vwap: f64) -&amp;gt; f64 {
    prob * (1.0 - FEE_RATE)
        - (buy_vwap + SLIPPAGE) * (1.0 + FEE_RATE)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A winning binary contract ultimately pays 1, while a losing contract pays 0. The expression above therefore represents the expected number of dollars earned per token rather than an abstract rate of return.&lt;/p&gt;

&lt;p&gt;The current entry threshold is 0.025. In other words, after estimated fees and slippage, each token must still retain at least 2.5 cents of expected edge.&lt;/p&gt;

&lt;p&gt;This is also the most important difference between a conditional-probability model and an ordinary directional indicator.&lt;/p&gt;

&lt;p&gt;A directional indicator tells us whether the market currently leans bullish or bearish. A probability model tries to answer how likely a specific final outcome is under the current state. The execution layer then asks whether that probability can actually be monetized at prices available in the real order book.&lt;/p&gt;

&lt;p&gt;Polymarket prices naturally contain a great deal of information, but there is an easy circularity trap here. If the market price is used as an input feature and the model output is then compared with that same market price to calculate EV, the model can end up in a circular argument: the market price helps generate the forecast, and the forecast is then used to claim that the market price is wrong.&lt;/p&gt;

&lt;p&gt;For that reason, I keep the two information paths separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binance market data and the official benchmark price generate an independent terminal probability.&lt;/li&gt;
&lt;li&gt;The Polymarket order book is used only for trading prices, depth, spread, and execution cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finding OpenMarket: A Research Framework Honest Enough to Be Useful
&lt;/h2&gt;

&lt;p&gt;While looking for related work, I came across Gregory Young's open-source OpenMarket project and the paper OpenMarket: A Synchronized Polymarket-Binance Dataset for High-Frequency Prediction-Market Research.&lt;br&gt;
OpenMarket GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gregyoung14/openmarket" rel="noopener noreferrer"&gt;https://github.com/gregyoung14/openmarket&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paper:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://arxiv.org/abs/2607.26245" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2607.26245&lt;/a&gt;&lt;br&gt;
The project studies exactly the relationship between Binance BTC/USDT market data and Polymarket's 15-minute BTC binary markets.&lt;/p&gt;

&lt;p&gt;The author built a fairly complete research framework: millisecond-level Binance trades and Polymarket order books are collected and synchronized; 43 features are constructed across time, returns, volatility, order flow, and market microstructure; and terminal probabilities are then evaluated using Logistic Regression, Platt Scaling, and walk-forward validation.&lt;/p&gt;

&lt;p&gt;What I find especially valuable is that the author does not package the results as a story about having discovered stable arbitrage.&lt;/p&gt;

&lt;p&gt;The paper's conclusion is much more honest: the public 43-feature model does not consistently outperform the probability implied by the Polymarket order book out of sample. After fees, spreads, and slippage are included, the model also does not produce a trading edge that can simply be declared effective.&lt;/p&gt;

&lt;p&gt;That does not make the framework less useful. For quantitative research, a negative result with public features, data, model parameters, calibration methods, and out-of-sample results is often more valuable than a "successful strategy" that shows only an equity curve and cannot be reproduced.&lt;/p&gt;

&lt;p&gt;At a minimum, OpenMarket answers three important questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What data should be collected?&lt;/li&gt;
&lt;li&gt;How can short-horizon market state be transformed into a conditional probability?&lt;/li&gt;
&lt;li&gt;How should probability-fitting ability be separated from actual tradability?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I retained the data-collection part of this research framework. The 43 stage features are written to the database every five seconds, and the final label is added after settlement. These records can later be used for stage-bucketed training and out-of-sample validation.&lt;/p&gt;

&lt;p&gt;For now, however, the fixed feature weights do not directly participate in the live trading probability.&lt;/p&gt;

&lt;p&gt;The reason is concrete. At the 60-second mark, a 180-second return does not yet exist, and a feature called "180-second volume" actually contains only 60 seconds of data. By the tenth minute, that same feature name finally refers to a complete 180-second window.&lt;/p&gt;

&lt;p&gt;If one uncalibrated set of weights is used from a few seconds after market open all the way to minute 14, the feature names may stay the same while their statistical meaning changes substantially.&lt;/p&gt;

&lt;p&gt;So this implementation does not rush a complex model into the production decision path. Instead, it starts from the terminal event itself and builds a transparent probability baseline that can be inspected directly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Starting with the Distance to the Terminal Event
&lt;/h2&gt;

&lt;p&gt;Predicting the 15-minute result at minute 1 does not mean knowing the future. It means estimating a terminal probability under the current information set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbwsqcm1u7eotl8780hy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsbwsqcm1u7eotl8780hy.png" alt=" " width="587" height="73"&gt;&lt;/a&gt;&lt;br&gt;
where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;K is the official Price-to-Beat for the current round;&lt;/li&gt;
&lt;li&gt;S_t is the current BTC reference price;&lt;/li&gt;
&lt;li&gt;\tau is the remaining time;&lt;/li&gt;
&lt;li&gt;\sigma_t is the estimate of future volatility;&lt;/li&gt;
&lt;li&gt;\mathcal represents the information already available at time t.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most basic standardized distance is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fah1t5s9inyejfly8zkcx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fah1t5s9inyejfly8zkcx.png" alt=" " width="592" height="93"&gt;&lt;/a&gt;&lt;br&gt;
The formula itself is not complicated. The difficult part is estimating volatility and drift.&lt;/p&gt;

&lt;p&gt;If a round has been open for only 15 seconds and we extrapolate those 15 seconds of volatility over the remaining 885 seconds, the result can be seriously distorted. If the most recent seconds happen to be quiet, the denominator becomes too small. If those seconds happen to be strongly directional, the drift estimate can become too large.&lt;/p&gt;

&lt;p&gt;The combination can easily create false certainty shortly after market open.&lt;/p&gt;

&lt;p&gt;For that reason, volatility is not estimated only from data inside the current round.&lt;/p&gt;

&lt;p&gt;The system builds one-second closes from the Binance real-time trade stream and combines realized volatility over 60-second, 5-minute, 15-minute, and 30-minute windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let windows = [
    (60usize, 0.15),
    (300usize, 0.25),
    (900usize, 0.30),
    (1800usize, 0.30),
];

for &amp;amp;(window, weight) in windows.iter() {
    if available &amp;gt;= window * 3 / 4 {
        let vol = realized_vol(&amp;amp;close, window);
        variance += weight * vol * vol;
        total_weight += weight;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the model to use pre-open price history to estimate remaining volatility even when a new round has just started.&lt;/p&gt;

&lt;p&gt;The system also imposes a conservative floor on per-second volatility so that an accidentally quiet short sample does not push the estimated probability toward an extreme.&lt;/p&gt;

&lt;p&gt;Drift is handled even more conservatively.&lt;/p&gt;

&lt;p&gt;The average return over the most recent 60 seconds is not simply multiplied by the entire remaining time. Instead, it is shrunk toward zero according to the number of effective observations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnstgzbn8u4ns299jr24s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnstgzbn8u4ns299jr24s.png" alt=" " width="591" height="85"&gt;&lt;/a&gt;&lt;br&gt;
The total contribution of drift to the terminal distribution is then capped at 0.25 times the remaining volatility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let reliability = n as f64 / (n as f64 + 300.0);
let drift_total = drift_per_sec * remaining * reliability;

let drift = clamp(
    drift_total,
    -0.25 * remaining_sigma,
     0.25 * remaining_sigma,
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short-term trend is allowed to adjust the probability, but it is not allowed to turn a single trade into something that appears almost certain on its own.&lt;/p&gt;

&lt;p&gt;During the first five minutes after market open, the system also reduces confidence in the probability's log-odds.&lt;/p&gt;

&lt;p&gt;This is not an arbitrary rule saying that no trades are allowed during the first few minutes, nor does it force the probability to remain at 50%. It simply recognizes that the same price distance carries different information quality at different stages of the round.&lt;/p&gt;

&lt;p&gt;As the stage data becomes more complete, the confidence coefficient gradually returns to 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Central Probability Is Not Necessarily the Probability You Should Trade On
&lt;/h2&gt;

&lt;p&gt;This is one of the most important steps in the entire framework.&lt;/p&gt;

&lt;p&gt;The model's central probability tells us where the current estimate is centered. It does not imply that the estimation error is zero.&lt;/p&gt;

&lt;p&gt;This matters especially just after market open, when volatility history is incomplete, or when the estimated probability itself is changing rapidly. If the central probability is used directly to calculate EV, the system can easily confuse uncertainty with certainty.&lt;/p&gt;

&lt;p&gt;For the side the strategy is considering buying, it therefore uses a probability discounted by uncertainty:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftfnh86rw60apzyg1wtc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftfnh86rw60apzyg1wtc2.png" alt=" " width="595" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The current implementation uses z_u=1.&lt;br&gt;
The uncertainty term u_t has three components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recent volatility of the probability sequence itself;&lt;/li&gt;
&lt;li&gt;information deficiency during the early stage of the round;&lt;/li&gt;
&lt;li&gt;the amount of volatility history currently available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not another hard rule such as "wait for N warm-up observations before trading."&lt;/p&gt;

&lt;p&gt;The first valid probability estimate is still calculated immediately. The difference is that less information leads to a lower tradable probability. Only if EV remains positive after this conservative discount does the candidate continue to the next stage.&lt;/p&gt;

&lt;p&gt;One simulated log entry illustrates the distinction well.&lt;/p&gt;

&lt;p&gt;At the 15-second mark, the central probability of Down was approximately 49.75%, while the contract Ask was 0.43. Looking only at the central probability, the system appeared to have more than six percentage points of room.&lt;/p&gt;

&lt;p&gt;But after taking VWAP and slippage into account, the effective entry cost was approximately 0.435, while the full-position liquidation value was only 0.42. The position therefore started with an immediate mark-to-market loss of 5.36%.&lt;/p&gt;

&lt;p&gt;After stage uncertainty was included, if the bot had a complete 30-minute volatility history, the tradable probability fell to approximately 44.81%, and fee-adjusted net EV was only 0.0043, below the 0.025 threshold.&lt;/p&gt;

&lt;p&gt;If the bot had just started and only 15 seconds of volatility history were available, the tradable probability fell to approximately 41.96%, and net EV became negative.&lt;/p&gt;

&lt;p&gt;The trade would therefore fail at the probability layer before reaching execution.&lt;/p&gt;

&lt;p&gt;The point of adaptation here is not that the model continuously changes all of its parameters. The important adaptation is that &lt;strong&gt;the credibility of the probability changes with information quality&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  FMZ Happened to Add Rust Support at the Right Time
&lt;/h2&gt;

&lt;p&gt;Once the modeling framework was defined, the next question was how to implement something suitable for continuous operation.&lt;/p&gt;

&lt;p&gt;FMZ happened to add Rust support, which made this architecture much easier to implement cleanly.&lt;/p&gt;

&lt;p&gt;Rust is well suited to real-time data handling, fixed-dimension numerical work, and complex state management. Its compiler also catches many field, type, and ownership errors before the strategy is allowed to run.&lt;/p&gt;

&lt;p&gt;The current framework requires only one Polymarket exchange object to be configured in FMZ.&lt;/p&gt;

&lt;p&gt;Binance BTC/USDT data is consumed directly through the public WebSocket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const BINANCE_WS_URL: &amp;amp;str =
    "wss://stream.binance.com:9443/ws/btcusdt@trade";

let mut binance_ws = Dial(BINANCE_WS_URL);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Polymarket BTC 15-minute market is assembled deterministically from UTC time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let start = (now_ms() / 900_000) * 900_000;
let slug = format!("btc-updown-15m-{}", start / 1000);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system validates the market and retrieves token information through the Gamma Market API.&lt;/p&gt;

&lt;p&gt;At the same time, it subscribes to Chainlink and TWAP60 data from Polymarket RTDS, keeping four concepts separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binance real-time trade price;&lt;/li&gt;
&lt;li&gt;Chainlink reference price;&lt;/li&gt;
&lt;li&gt;current 60-second TWAP;&lt;/li&gt;
&lt;li&gt;the fixed official Price-to-Beat for the current round.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These prices must not be treated as interchangeable.&lt;/p&gt;

&lt;p&gt;The real-time price describes the current state. The Price-to-Beat defines the terminal event:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhmc3tt3i5fjn4uzwe05m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhmc3tt3i5fjn4uzwe05m.png" alt=" " width="595" height="77"&gt;&lt;/a&gt;&lt;br&gt;
If the benchmark K is wrong, every probability calculation downstream becomes meaningless.&lt;/p&gt;

&lt;p&gt;For that reason, the trading path requires the official openPrice. Alternative sources may be stored for research and comparison, but a current price near settlement must never be substituted for the official opening benchmark.&lt;/p&gt;

&lt;p&gt;Multi-horizon volatility is updated and cached once per second. Terminal probability is recalculated roughly every 200 milliseconds, while the main loop polls data and manages state at approximately 50-millisecond intervals.&lt;/p&gt;

&lt;p&gt;The implementation deliberately does not confuse "record features every five seconds" with "make a decision only every five seconds."&lt;/p&gt;

&lt;p&gt;Five seconds is the research snapshot frequency, not the trading-response frequency.&lt;/p&gt;
&lt;h2&gt;
  
  
  Passing the Probability Test Still Does Not Mean an Order Will Be Sent
&lt;/h2&gt;

&lt;p&gt;Even if uncertainty-adjusted EV reaches 0.025, the system does not immediately trade the first frame that appears profitable.&lt;/p&gt;

&lt;p&gt;When a new market opens, the order book is often still being assembled. Quotes on both sides, available depth, and market-maker inventory can change very quickly.&lt;/p&gt;

&lt;p&gt;A single qualifying EV observation means only that a candidate opportunity exists at that moment. It does not prove that the edge persists.&lt;/p&gt;

&lt;p&gt;The system therefore maintains a very short entry-evidence state.&lt;/p&gt;

&lt;p&gt;The candidate direction must remain unchanged, net EV must remain above the threshold for at least 1.2 seconds, and the signal must accumulate at least five valid updates.&lt;/p&gt;

&lt;p&gt;If the direction changes, EV disappears, or an order-book check fails, the evidence counter is immediately reset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let ready = evidence_count &amp;gt;= 5
    &amp;amp;&amp;amp; now_ms - evidence_first &amp;gt;= 1_200;

if !ready {
    signal.decision = "EV confirmation pending";
    return;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 1.2-second requirement is not a slow warm-up period. It is simply a way to distinguish persistent edge from a momentary order-book dislocation.&lt;/p&gt;

&lt;p&gt;Immediately before submitting an order, the system reloads both the Up and Down books, checks whether their timestamps are synchronized, and validates the complementary relationship of the binary market:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv82wd423jchxd1bxmnmn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv82wd423jchxd1bxmnmn.png" alt=" " width="590" height="141"&gt;&lt;/a&gt;&lt;br&gt;
It then calculates the fee-adjusted round-trip loss of buying now and immediately selling the entire position.&lt;/p&gt;

&lt;p&gt;This check incorporates Ask depth, Bid depth, slippage, and fees on both sides. The current maximum allowed loss is 4%.&lt;/p&gt;

&lt;p&gt;The earlier example that would begin with an immediate 5.36% loss would therefore be rejected by the execution layer even if the probability EV barely passed.&lt;/p&gt;

&lt;p&gt;Position size uses one-fifth Kelly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1huc7nndbt642ph5f2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1huc7nndbt642ph5f2z.png" alt=" " width="601" height="95"&gt;&lt;/a&gt;&lt;br&gt;
The final allocation is capped between 0.1% and 3% of account equity.&lt;/p&gt;

&lt;p&gt;Therefore, if the simulation account has 100 USDC, the maximum capital committed to a single position is 3 USDC.&lt;/p&gt;

&lt;p&gt;This is not a balance-reading error. It is an intentional cap on per-trade risk.&lt;/p&gt;

&lt;p&gt;The probability model is not responsible for submitting orders directly.&lt;/p&gt;

&lt;p&gt;It proposes a probability view that can be tested. The order book determines whether execution is available at acceptable terms. Persistent evidence, round-trip cost, and position sizing determine whether the probability view is worth expressing with real capital.&lt;/p&gt;
&lt;h2&gt;
  
  
  After Entry: Prediction Makes the Decision, Price Provides the Backstop
&lt;/h2&gt;

&lt;p&gt;After a position has been opened, the system continuously calculates the VWAP at which the entire position could actually be sold instead of looking only at the best Bid.&lt;/p&gt;

&lt;p&gt;Actual PnL is measured using the current executable liquidation value and the true cost basis:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7daelary1nuw56pgpsfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7daelary1nuw56pgpsfv.png" alt=" " width="594" height="93"&gt;&lt;/a&gt;&lt;br&gt;
The prediction side continuously calculates the EV of the current position:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5xk5sxswjxaa8l3ul5fj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5xk5sxswjxaa8l3ul5fj.png" alt=" " width="596" height="95"&gt;&lt;/a&gt;&lt;br&gt;
A normal prediction-based exit is not triggered simply because the probability falls by a few percentage points.&lt;/p&gt;

&lt;p&gt;Instead, the current holding EV must fall below -0.005 and decline by at least 0.025 relative to its prior level. This condition must then be confirmed three consecutive times for at least 600 milliseconds.&lt;/p&gt;

&lt;p&gt;Once all conditions are met, the strategy exits with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREDICTED_EV_SHOCK_NEGATIVE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that if probability declines but the position's EV remains positive, the strategy continues to hold.&lt;/p&gt;

&lt;p&gt;Conversely, even a probability change that does not look large can trigger an exit if it is sufficient to turn the original trading thesis into negative expected value.&lt;/p&gt;

&lt;p&gt;On the price side, the system retains only one fixed stop line, determined at entry and never moved afterward:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqp75t2vjxuu7tihdf480.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqp75t2vjxuu7tihdf480.png" alt=" " width="593" height="74"&gt;&lt;/a&gt;&lt;br&gt;
The code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let fixed_stop_price =
    (position.vwap - 0.50).max(0.35);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stop is not based on a percentage of entry cost, and it does not follow the highest Bid observed during the holding period.&lt;/p&gt;

&lt;p&gt;This avoids triggering a trailing stop simply because the order book briefly spikes and then normalizes.&lt;/p&gt;

&lt;p&gt;The strategy uses the executable VWAP obtained by walking through the entire Bid side to determine whether the stop has actually been breached.&lt;/p&gt;

&lt;p&gt;A fixed take-profit at 0.95 is also retained.&lt;/p&gt;

&lt;p&gt;This stop is better understood as catastrophe protection than as the normal prediction-management mechanism.&lt;/p&gt;

&lt;p&gt;When the entry price is below 0.85, the stop line remains 0.35. With an entry price of 0.915, the stop becomes 0.415.&lt;/p&gt;

&lt;p&gt;That can correspond to a large percentage loss on the token, which is why normal exits still rely primarily on predicted EV turning negative.&lt;/p&gt;

&lt;p&gt;Whether this fixed stop is appropriately calibrated still needs to be tested against real holding paths and maximum adverse excursion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Database Gives the Research Time Continuity
&lt;/h2&gt;

&lt;p&gt;A system like this loses much of its value if it cannot persist state.&lt;/p&gt;

&lt;p&gt;If the bot restarts and all probability history, positions, and simulated-account state disappear, the research process itself becomes discontinuous.&lt;/p&gt;

&lt;p&gt;The system uses FMZ DBExec to persist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each market round;&lt;/li&gt;
&lt;li&gt;the official benchmark price;&lt;/li&gt;
&lt;li&gt;stage-feature snapshots;&lt;/li&gt;
&lt;li&gt;probability sequences;&lt;/li&gt;
&lt;li&gt;orders;&lt;/li&gt;
&lt;li&gt;positions;&lt;/li&gt;
&lt;li&gt;exit reasons;&lt;/li&gt;
&lt;li&gt;settlement outcomes;&lt;/li&gt;
&lt;li&gt;simulated balance;&lt;/li&gt;
&lt;li&gt;risk state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a restart, the bot can recover unsettled positions instead of pretending that already-existing risk no longer exists.&lt;/p&gt;

&lt;p&gt;Paper trading and live trading use the same state machine.&lt;/p&gt;

&lt;p&gt;The difference is that paper fills do not send real orders. Instead, they update the internal ledger using full VWAP, slippage, and estimated fees.&lt;/p&gt;

&lt;p&gt;Real-time logs explicitly display simulated buys and sells, position direction, quantity, cost, executable liquidation value, actual PnL, central probability, tradable probability, predicted EV, fixed stop price, and exit reason.&lt;/p&gt;

&lt;p&gt;The status panel continuously shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binance, Chainlink, TWAP, and the official benchmark price;&lt;/li&gt;
&lt;li&gt;Up/Down central probabilities, stage uncertainty, and tradable probabilities;&lt;/li&gt;
&lt;li&gt;real Ask prices on both sides, full VWAP, and fee-adjusted net EV;&lt;/li&gt;
&lt;li&gt;accumulated entry evidence and reasons why no order has been sent;&lt;/li&gt;
&lt;li&gt;current position cost, executable full-position value, and actual PnL;&lt;/li&gt;
&lt;li&gt;holding EV, EV shock magnitude, exit-confirmation count, and fixed stop price;&lt;/li&gt;
&lt;li&gt;recent trades, simulated balance, and cumulative realized PnL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These records are not there to make the status table look complicated.&lt;/p&gt;

&lt;p&gt;They are needed to answer one key research question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Did the strategy lose because the probability forecast was wrong, or because the order book, slippage, liquidity, and execution costs prevented the forecast from being monetized?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If those two categories cannot be separated, later model optimization can easily move in the wrong direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Still Only a Practical Research Experiment
&lt;/h2&gt;

&lt;p&gt;I prefer to think of the current system as an adaptive conditional-probability experimentation platform rather than a fully validated trading strategy.&lt;/p&gt;

&lt;p&gt;"Adaptive" here does not mean changing the model after every outcome, nor does it mean continuously feeding the market price back into the forecast.&lt;/p&gt;

&lt;p&gt;What really needs to adapt is information quality.&lt;/p&gt;

&lt;p&gt;The same price distance should not carry the same credibility at second 30 and minute 13.&lt;/p&gt;

&lt;p&gt;Likewise, the same 50% central probability should not produce the same tradable probability or position size when one estimate has 30 minutes of volatility history behind it and another has only 15 seconds of data.&lt;/p&gt;

&lt;p&gt;The current implementation first trades using a transparent terminal-distance model while continuing to store the 43 stage features and the final settlement label.&lt;/p&gt;

&lt;p&gt;Once enough data has accumulated, the dataset can be divided into stage buckets such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0–1 minute;&lt;/li&gt;
&lt;li&gt;1–3 minutes;&lt;/li&gt;
&lt;li&gt;3–6 minutes;&lt;/li&gt;
&lt;li&gt;6–10 minutes;&lt;/li&gt;
&lt;li&gt;10–14 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each bucket can then be evaluated out of sample using calibration curves, Brier Score, Log Loss, and actual executable trading returns.&lt;/p&gt;

&lt;p&gt;Only if a feature set continues to provide incremental information beyond the Polymarket order book during the corresponding stage should it be allowed into the production probability model.&lt;/p&gt;

&lt;p&gt;Otherwise, a more complex model merely adds more parameters to a simple hypothesis.&lt;/p&gt;

&lt;p&gt;Future work can also explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conditional distributions across different market regimes;&lt;/li&gt;
&lt;li&gt;jump risk caused by macroeconomic releases and breaking news;&lt;/li&gt;
&lt;li&gt;First-Passage Probability;&lt;/li&gt;
&lt;li&gt;regime-specific Logistic models;&lt;/li&gt;
&lt;li&gt;dynamic volatility models;&lt;/li&gt;
&lt;li&gt;nonlinear models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But continuous validation matters more than adding complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are the probabilities calibrated?&lt;/li&gt;
&lt;li&gt;Does the edge exist at prices that can actually be executed?&lt;/li&gt;
&lt;li&gt;Can the expected return survive fees, spreads, slippage, and execution latency?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a binary market, the difficult part is not simply deciding Up or Down.&lt;/p&gt;

&lt;p&gt;The real challenge is repeatedly answering three questions:&lt;/p&gt;

&lt;p&gt;Given the current conditions, what is the probability of the event?&lt;br&gt;
How much confidence should we place in that probability?&lt;br&gt;
Can the probability difference be realized after actual trading costs?&lt;br&gt;
Placed back into the trading pipeline, the structure is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Central Probability
        ↓
Stage Uncertainty
        ↓
Tradable Probability
        ↓
Real VWAP + Fees
        ↓
Persistent EV Evidence
        ↓
Position Sizing and Exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prediction model proposes a view.&lt;/p&gt;

&lt;p&gt;Market prices define the trading terms.&lt;/p&gt;

&lt;p&gt;The execution layer determines whether that view can be monetized.&lt;/p&gt;

&lt;p&gt;The risk layer limits losses when the view is wrong.&lt;/p&gt;

&lt;p&gt;At the current stage, the system should still be used primarily for paper trading and data collection.&lt;/p&gt;

&lt;p&gt;A logically complete system is not the same thing as a statistically validated one.&lt;/p&gt;

&lt;p&gt;Whether this framework can ultimately produce a stable edge can only be answered through stage-specific out-of-sample results and returns measured at genuinely executable prices.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Risk Warning: This article discusses quantitative research and engineering implementation only and does not constitute investment advice. Binary contracts can expire worthless. Liquidity deterioration, order-book jumps, model failure, and settlement-rule changes can all cause significant losses.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>predictionmarkets</category>
      <category>polymarket</category>
      <category>cryptoquant</category>
      <category>quant</category>
    </item>
    <item>
      <title>The Grid Shouldn't Determine Position Size: Adding a Fractional Kelly Capital Budget to a Long-Only Grid Strategy</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Thu, 27 Aug 2026 06:04:59 +0000</pubDate>
      <link>https://dev.to/quant001/the-grid-shouldnt-determine-position-size-adding-a-fractional-kelly-capital-budget-to-a-long-only-53dc</link>
      <guid>https://dev.to/quant001/the-grid-shouldnt-determine-position-size-adding-a-fractional-kelly-capital-budget-to-a-long-only-53dc</guid>
      <description>&lt;p&gt;&lt;em&gt;A grid answers the question, “At what price should the strategy take an action?” Kelly answers a different question: “How much capital may the entire strategy use at most?” Only by separating these two responsibilities can a grid evolve from a set of averaging-down rules into a system that can be backtested, reconciled, and knows when it should stop taking additional risk.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article discusses a long-only dynamic grid prototype for linear perpetual contracts supported by FMZ. It can be used with traditional crypto assets, and it can also be used to study TradFi-mapped perpetual products such as NVDA, without relying on any exchange-specific private interface.&lt;/p&gt;

&lt;p&gt;First, a boundary condition: this article presents the strategy architecture and an FMZ engineering implementation. It makes no promise of profitability, nor does it use an attractive in-sample equity curve as a substitute for out-of-sample validation. A grid strategy accumulates inventory during prolonged declines, while Kelly estimates are themselves affected by sampling error. Combining the two does not automatically produce a low-risk strategy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The strategy is only an implementation of the ideas discussed in this article and should be treated as a research prototype. It may require further upgrades and revisions. It is intended solely for learning and discussion.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What I Really Want to Solve Is Not “How Wide Should the Grid Be?”
&lt;/h2&gt;

&lt;p&gt;When people build grid strategies, it is easy to focus almost entirely on grid spacing: 0.5% or 1%, five levels or ten, equal spacing or exponential spacing.&lt;/p&gt;

&lt;p&gt;But what determines whether the strategy can survive is often not one of those parameters. It is a more fundamental question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If price keeps falling, how much capital is this entire grid allowed to consume at most?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Traditional grids often tie “how much to buy at each level” directly to “how far price has fallen.” One contract on the first level, two on the second, four on the third. On the surface, this seems to optimize the average entry price. In practice, it automatically expands the risk budget as price moves against the strategy.&lt;/p&gt;

&lt;p&gt;This prototype deliberately separates these responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;grid layer&lt;/strong&gt; decides when to buy and when to exit.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Kelly layer&lt;/strong&gt; decides the total fraction of capital that the entire grid is currently allowed to use.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;execution layer&lt;/strong&gt; converts a quote-currency budget into contract quantity and handles partial fills and order reconciliation.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;risk-control layer&lt;/strong&gt; has the authority to stop adding new risk and, when necessary, exit positions batch by batch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The grid is not allowed to increase the total budget on its own. Even if price crosses five grid levels in succession, the combined capital allocated to those five levels may not exceed the ceiling provided by the Kelly layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why the Strategy Is Long-Only
&lt;/h2&gt;

&lt;p&gt;This is not based on the assumption that “long-only always makes money.” The purpose of the strategy is to study a specific structure: on assets for which there is a hypothesis of long-term positive drift, use a pullback grid to improve entries and exits instead of maintaining two opposing inventories at the same time.&lt;/p&gt;

&lt;p&gt;For stock-mapped perpetuals such as NVDA, that hypothesis may come from the possibility of long-term growth in the underlying company’s value. For major crypto assets, it comes from a different and less stable assumption about long-term adoption. In either case, positive drift is a hypothesis to be tested, not a law of nature.&lt;/p&gt;

&lt;p&gt;Long-only also does not mean “always keep buying.” The prototype requires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FastEMA &amp;gt;= SlowEMA
and Close &amp;gt;= SlowEMA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only when this bullish regime is satisfied may the strategy add new grid positions. When price crosses downward through a grid level, the strategy buys. If price later rebounds by one entry-time grid spacing, the corresponding batch is closed.&lt;/p&gt;

&lt;p&gt;Each batch has its own actual average fill price and exit target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TargetPrice = ActualEntryPrice × (1 + EntrySpacing)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an easily overlooked detail here: once a batch has been filled, its target is no longer modified by ATR.&lt;/p&gt;

&lt;p&gt;ATR may change grid levels that have not yet been filled, but it must not retroactively alter the plan of a trade that is already carrying risk. Otherwise, when volatility expands, the take-profit line keeps moving farther away, and a dynamic parameter becomes an excuse to postpone exits.&lt;/p&gt;

&lt;p&gt;The anchor is also allowed to move upward only when the strategy is flat, has no active order, and the trend is healthy. While holding positions, the anchor does not chase price downward. This prevents the grid from moving its “reference starting point” lower and lower during a decline until even the hard-stop benchmark loses its meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Grid Spacing Should Cover Costs Before It Tries to Model Volatility
&lt;/h2&gt;

&lt;p&gt;If the grid is too narrow, the trade count may look impressive, but gross profit can be completely consumed by fees and slippage. If the grid is too wide, the strategy may remain inactive for a long time.&lt;/p&gt;

&lt;p&gt;The prototype defines grid spacing as the maximum of three constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spacing = clamp(
    max(
        MinGridPct,
        ATR / Price × AtrMultiplier,
        2 × (FeePerSide + SlippagePerSide) × CostFloorMultiple
    ),
    MinGridPct,
    MaxGridPct
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three components answer three different questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is the minimum operational grid spacing the strategy is willing to use?&lt;/li&gt;
&lt;li&gt;What is the current scale of market volatility?&lt;/li&gt;
&lt;li&gt;Given the estimated cost of one entry plus one exit, how much safety margin should be left?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Suppose one-way fees and slippage are both estimated at 0.05%, and the safety multiple is 2. Then the cost floor is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 × (0.05% + 0.05%) × 2 = 0.4%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if ATR becomes temporarily very low, the grid will not shrink below 0.4%.&lt;/p&gt;

&lt;p&gt;This does not guarantee that every completed trade will be profitable. It simply avoids designing a grid whose expected gross spread is already smaller than its assumed transaction costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Do Not Use the “Win Rate of Completed Grid Trades” to Calculate Kelly
&lt;/h2&gt;

&lt;p&gt;This is the most important part of the entire design.&lt;/p&gt;

&lt;p&gt;A grid trade that has completed is usually a small winner. The inventory with the largest losses is often precisely the inventory that has not yet completed.&lt;/p&gt;

&lt;p&gt;If we count only batches that have already exited, we can easily obtain data like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Completed grid trades: 93
Winning trades:        90
Losing trades:          3
Win rate:             96.8%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that win rate is then plugged into the classic binary Kelly formula, the result may recommend an extremely aggressive position size.&lt;/p&gt;

&lt;p&gt;The problem is that the unrealized losses still sitting in the account are excluded from the sample.&lt;/p&gt;

&lt;p&gt;The strategy is hiding unrealized losses outside the statistical sample and then using a selectively inflated win rate to guide position sizing. That is a textbook form of self-deception.&lt;/p&gt;

&lt;p&gt;For this reason, the prototype does not treat “one completed grid cycle” as a Kelly event. Instead, it periodically samples mark-to-market equity, including unrealized PnL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EquityReturn = Equity_t / Equity_(t-1) - 1

AverageExposure =
    (ExposureFraction_t + ExposureFraction_(t-1)) / 2

UnitRiskReturn = EquityReturn / AverageExposure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ExposureFraction is the current notional exposure divided by a fixed reference equity.&lt;/p&gt;

&lt;p&gt;The purpose is to normalize equity changes observed under different position sizes into something closer to the empirical return distribution generated by one unit of risk exposure.&lt;/p&gt;

&lt;p&gt;Instead of using the simplified Kelly formula designed for a binary gamble, the prototype searches directly over historical empirical samples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f_raw = argmax mean(log(1 + f × UnitRiskReturn))
        f ∈ [0, 1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a candidate f would make 1 + f × r &amp;lt;= 0 for any observation, that candidate is invalid.&lt;/p&gt;

&lt;p&gt;If the average log growth of every positive-position candidate is no better than staying flat, the optimum becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f_raw = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That still does not mean the estimate is reliable enough to use directly. The actual allocation is discounted further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Confidence = min(1, SampleCount / (2 × MinSamples))

f_used = min(
    MaxStrategyEquityPct,
    f_raw × KellyFraction × Confidence
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the prototype uses one-quarter Kelly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KellyFraction = 0.25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the sample count has only just reached the minimum requirement, the confidence multiplier is still only 0.5. The discount is gradually relaxed only as more valid samples accumulate.&lt;/p&gt;

&lt;p&gt;Before there are enough Kelly samples, the strategy uses a small warm-up allocation.&lt;/p&gt;

&lt;p&gt;Without that step, the strategy would fall into a paradox: without exposure there are no strategy-return samples, and without samples the strategy would never be allowed to create exposure.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Kelly Controls the Total Budget, Not Each Individual Grid Level
&lt;/h2&gt;

&lt;p&gt;Assume the fixed reference equity is 100,000 USDT and the warm-up allocation is 10%. The entire grid is therefore allowed a maximum notional budget of 10,000 USDT during the warm-up phase.&lt;/p&gt;

&lt;p&gt;The five grid levels do not increase in size as price falls. Instead, their weights decrease by a factor of 0.85:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.0000, 0.8500, 0.7225, 0.6141, 0.5220
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After normalization, the allocation looks approximately like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F984f5yelkv5he9up1wzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F984f5yelkv5he9up1wzr.png" alt=" " width="633" height="219"&gt;&lt;/a&gt;&lt;br&gt;
This design is not claiming that deeper grid levels are inherently “safer.” It is explicitly rejecting the traditional Martingale structure: the more adverse the price movement becomes, the smaller the incremental risk added by each new level.&lt;/p&gt;

&lt;p&gt;Suppose the later empirical return distribution produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f_raw = 40%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the one-quarter Kelly multiplier is 0.25, while the current confidence discount is 0.75:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f_used = 40% × 0.25 × 0.75 = 7.5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The total grid budget is now reduced to 7,500 USDT.&lt;/p&gt;

&lt;p&gt;If existing notional exposure is clearly above the new budget plus a rebalancing buffer, the strategy reduces positions batch by batch, starting with deeper lots, rather than continuing to wait for every batch to reach its original take-profit target.&lt;/p&gt;

&lt;p&gt;One point deserves emphasis: reducing exposure when the Kelly estimate falls also creates turnover and slippage. A buffer is therefore important. A noisy estimator should not trigger a trade every time it changes slightly.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Converting a Quote-Currency Budget into FMZ Contract Quantity
&lt;/h2&gt;

&lt;p&gt;The strategy interface takes a capital budget as input, while the amount passed to an FMZ contract order is usually the number of contracts.&lt;/p&gt;

&lt;p&gt;You cannot simply pass 1,000 USDT as the amount argument of an order function.&lt;/p&gt;

&lt;p&gt;The prototype uses exchange.GetMarkets() to inspect market metadata, focusing on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CtVal: how much base asset each contract represents;&lt;/li&gt;
&lt;li&gt;CtValCcy: the currency in which the contract value is expressed;&lt;/li&gt;
&lt;li&gt;AmountSize: quantity precision or step size;&lt;/li&gt;
&lt;li&gt;MinQty: minimum order quantity;&lt;/li&gt;
&lt;li&gt;MinNotional: minimum notional value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Version v0.1.0 accepts only linear contracts for which CtValCcy is the base asset.&lt;/p&gt;

&lt;p&gt;In that case, the quantity can be estimated as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RawAmount = QuoteBudget / (Price × CtVal)
Amount = floor_to_step(RawAmount, AmountStep)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After rounding the quantity downward to the valid step size, the strategy checks the minimum quantity and minimum notional requirements.&lt;/p&gt;

&lt;p&gt;If the contract-value unit cannot be confirmed, the strategy refuses to start instead of guessing how the conversion should work.&lt;/p&gt;

&lt;p&gt;The symbol also uses the full FMZ format, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BTC_USDT.swap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When researching stock-mapped perpetuals, this only needs to be replaced with the actual full symbol provided by the target FMZ exchange.&lt;/p&gt;

&lt;p&gt;The strategy itself does not switch exchanges, modify leverage, change isolated/cross-margin settings, alter one-way/hedge position modes, or pass exchange-specific order parameters inside the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The Hard Part Is Not the Formula — It Is Order State
&lt;/h2&gt;

&lt;p&gt;Once the strategy formulas are written, only half of the engineering work is done.&lt;/p&gt;

&lt;p&gt;A single exchange.CreateOrder() request can produce several different situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exchange returns an order ID, but the order is only partially filled.&lt;/li&gt;
&lt;li&gt;The request reaches the exchange, but the response times out.&lt;/li&gt;
&lt;li&gt;A cancellation request succeeds, but part of the order fills before cancellation takes effect.&lt;/li&gt;
&lt;li&gt;The strategy process restarts after sending an order.&lt;/li&gt;
&lt;li&gt;The account contains manual orders or positions created by another strategy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the program blindly sends another order whenever it gets an empty response, it can easily create duplicate orders.&lt;/p&gt;

&lt;p&gt;The prototype therefore follows a more conservative path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Persist PendingIntent first
        ↓
Call CreateOrder
        ↓
Valid order ID received
        → Save ActiveOrder
        → Clear PendingIntent
        ↓
No valid ID received
        → Keep PendingIntent
        → Enter SAFE_HALT
        → Do not blindly resend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Partial fills must also be processed carefully.&lt;/p&gt;

&lt;p&gt;DealAmount is typically cumulative, so the program must not add the full value again on every polling cycle. It should process only the increment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DeltaAmount = CurrentDealAmount - PreviousDealAmount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If AvgPrice is also a cumulative average price, the fill price of the newly added quantity can be derived as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DeltaPrice =
    (CurrentDealAmount × CurrentAvgPrice
     - PreviousDealAmount × PreviousAvgPrice)
    / DeltaAmount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After an order timeout, the strategy sends a cancellation request only if the order still appears in GetOrders(Symbol).&lt;/p&gt;

&lt;p&gt;The return value of CancelOrder does not mean the order has already reached a final state. The next cycle must still query the final cumulative filled quantity.&lt;/p&gt;

&lt;p&gt;To reduce ambiguity about order ownership, the prototype allows only one active order at a time and strongly recommends using a dedicated account or sub-account.&lt;/p&gt;

&lt;p&gt;Generality does not mean the strategy can safely take over an account that contains manual positions and orders from other strategies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Three Types of “Stop” Must Not Be Collapsed into One Switch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpdftz0r18yydthj3t5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpdftz0r18yydthj3t5w.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frpxy9b32uirkmzn1km7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frpxy9b32uirkmzn1km7c.png" alt=" " width="800" height="190"&gt;&lt;/a&gt;&lt;br&gt;
The prototype divides abnormal states into three layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function runtimeStateName(runtime) {
    var state = runtime.state
    if (state.safeHalt) { return "SAFE_HALT" }
    if (state.riskHalt) { return "RISK_HALT" }
    if (state.dataFreeze) { return "DATA_FREEZE" }
    if (state.activeOrder) { return state.activeOrder.intent.kind + "_PENDING" }
    if (state.lots.length) {
        if (runtime.trendBroken) { return "DEFENSE" }
        return runtime.trendHealthy ? "HOLDING" : "PROBE_HOLDING"
    }
    if (runtime.trendHealthy) { return "GRID_READY" }
    return runtime.probeEligible ? "PROBE_READY" : "WAIT_TREND"
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. DATA_FREEZE&lt;/strong&gt;&lt;br&gt;
Triggered by repeated market-data errors, unknown open orders, or a mismatch between account positions and the local ledger.&lt;/p&gt;

&lt;p&gt;It freezes only the addition of new risk. Exits from existing positions, active-order checks, and reconciliation continue to run.&lt;/p&gt;

&lt;p&gt;After several consecutive healthy cycles, the strategy may recover automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. RISK_HALT&lt;/strong&gt;&lt;br&gt;
Triggered when price reaches the hard-stop threshold relative to the anchor, or when session equity drawdown from its peak exceeds the configured limit.&lt;/p&gt;

&lt;p&gt;The strategy exits positions batch by batch and remains latched in the halted state.&lt;/p&gt;

&lt;p&gt;Even after all positions have been closed, it does not automatically recover. The operator must first arm the reset and then confirm it a second time within 60 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. SAFE_HALT&lt;/strong&gt;&lt;br&gt;
Triggered when the result of an order submission is unknown, persisted state is corrupted, or a short/unknown-direction position is detected.&lt;/p&gt;

&lt;p&gt;This state should not be automatically cleared merely because the network later recovers, because the program can no longer prove that it knows the true state and ownership of orders and positions.&lt;/p&gt;

&lt;p&gt;The difference among the three states can be summarized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATA_FREEZE:
The data is temporarily untrustworthy.
Do not add new risk.

RISK_HALT:
A strategy risk condition has been triggered.
Exit and remain halted.

SAFE_HALT:
Order or state ownership cannot be proven.
Do not guess or recover automatically.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  9. Whether This Structure Has a Trading Edge Must Be Tested This Way
&lt;/h2&gt;

&lt;p&gt;Kelly can scale an existing return distribution up or down. It cannot turn a negative-expectancy strategy into a positive-expectancy one.&lt;/p&gt;

&lt;p&gt;A dynamic grid may look more intelligent, but that does not mean it actually outperforms a simple long position.&lt;/p&gt;

&lt;p&gt;At minimum, five baseline variants should be compared:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6mml51q46t9pq7kubqj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6mml51q46t9pq7kubqj7.png" alt=" " width="453" height="216"&gt;&lt;/a&gt;&lt;br&gt;
Evaluation should not focus only on cumulative return. It should also include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maximum drawdown, annualized volatility, and Calmar ratio;&lt;/li&gt;
&lt;li&gt;average log growth;&lt;/li&gt;
&lt;li&gt;total turnover and transaction costs as a percentage of gross profit;&lt;/li&gt;
&lt;li&gt;average and maximum number of active grid levels;&lt;/li&gt;
&lt;li&gt;number of trend exits and risk halts;&lt;/li&gt;
&lt;li&gt;number of times the Kelly estimate falls to zero;&lt;/li&gt;
&lt;li&gt;parameter stability across neighboring ranges, timeframes, and instruments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Training and testing must not be mixed together.&lt;/p&gt;

&lt;p&gt;A more appropriate process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Estimate parameters on the training window
        ↓
Freeze parameters and run the next out-of-sample period
        ↓
Roll the window forward
        ↓
Aggregate all out-of-sample results
that were never used for fitting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If only the best full-sample parameters look good, while small changes in the ATR multiplier, EMA periods, or cost assumptions make the result disappear, then the strategy is more likely adapting to historical noise than discovering a robust edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. What Else Is Needed for Stock-Mapped Perpetuals?
&lt;/h2&gt;

&lt;p&gt;This general prototype intentionally avoids hard-coding the product mechanics of any single platform.&lt;/p&gt;

&lt;p&gt;Therefore, if it is applied to TradFi-mapped perpetual products such as NVDA, several second-stage issues still need to be addressed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trading-session differences: the perpetual product may trade continuously, while the underlying stock has a clearly defined primary price-discovery session.&lt;/li&gt;
&lt;li&gt;Overnight and weekend pricing: after the reference market closes, order-book depth, spreads, and price-anchoring mechanisms may change significantly.&lt;/li&gt;
&lt;li&gt;Earnings and corporate events: gap risk cannot be fully absorbed by an ATR-based grid.&lt;/li&gt;
&lt;li&gt;Funding rates and premium: the carrying cost of staying structurally long may consume the grid’s gross profit.&lt;/li&gt;
&lt;li&gt;Product-tracking mechanism: index prices, oracles, suspension handling, and listing/delisting rules may differ across platforms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These components should be added only after stable and verifiable data interfaces are available.&lt;/p&gt;

&lt;p&gt;Reading exchange-private fields merely to make the implementation “look complete” would instead weaken the strategy’s general design boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Kelly Is Not the Accelerator — It Is the Budget Approver
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpot0on8ou1hb8ef9v06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpot0on8ou1hb8ef9v06.png" alt=" " width="800" height="397"&gt;&lt;/a&gt;&lt;br&gt;
I prefer to think of Kelly in this strategy as a budget approval mechanism, not an automatic accelerator.&lt;/p&gt;

&lt;p&gt;The grid says:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Price has reached the second level. I want to buy again.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Kelly layer does not answer:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“How many times larger should the next order be?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead, it asks:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How much total risk budget is currently supported by the sample? Will this action push total exposure beyond that budget?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the sample does not support positive log growth, the answer may be zero.&lt;/p&gt;

&lt;p&gt;If existing exposure is already above the newly estimated budget, the answer may be to reduce the position first.&lt;/p&gt;

&lt;p&gt;Only by separating &lt;strong&gt;when to trade&lt;/strong&gt; from &lt;strong&gt;how much total risk the strategy is allowed to take&lt;/strong&gt; can a grid avoid automatically granting itself a larger risk budget precisely when the market is moving most strongly against it.&lt;/p&gt;

&lt;p&gt;The next genuinely valuable step is not to add more formulas. It is to complete FMZ import validation, establish the five baseline variants, and place fees, slippage, funding costs, and out-of-sample periods into the same results table.&lt;/p&gt;

&lt;p&gt;Only then can we answer the most important question behind this idea:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Does adding fractional Kelly to a grid strategy actually improve the trade-off between capital growth and drawdown, or does it merely make the strategy look more sophisticated?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Risk Warning: This article is intended solely for research on quantitative strategies, program design, and platform-interface discussion. It does not constitute investment advice. Perpetual contracts may involve leverage, liquidation, funding rates, liquidity risk, gap risk, tracking error, and platform technical failures. A research prototype must go through backtesting, out-of-sample validation, paper trading, and extremely small-capital testing before it can be considered for production use.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading and for your support.&lt;/p&gt;

</description>
      <category>quanttrading</category>
      <category>algorithmictrading</category>
      <category>cryptotrading</category>
      <category>gridstrategy</category>
    </item>
    <item>
      <title>You Got the Direction Right—Why Can Options Still Lose Money? A Four-Dimensional Crypto Options Selector Based on Deribit, Binance, and OKX</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Thu, 27 Aug 2026 03:37:13 +0000</pubDate>
      <link>https://dev.to/quant001/you-got-the-direction-right-why-can-options-still-lose-money-a-four-dimensional-crypto-options-ikb</link>
      <guid>https://dev.to/quant001/you-got-the-direction-right-why-can-options-still-lose-money-a-four-dimensional-crypto-options-ikb</guid>
      <description>&lt;p&gt;&lt;em&gt;This article focuses on the contract-selection problem in directional options trading: once you already have a bullish or bearish view, how do you choose the contract that best expresses that view from an entire option chain spanning different expiries, strikes, and exchanges?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The thresholds and examples in this article are for explaining the methodology only. They are not fixed parameters or investment advice. API fields and trading rules should always follow the latest official exchange documentation. Information in this article was checked in August 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Many people approach options for the first time with a very simple idea:&lt;/p&gt;

&lt;p&gt;If you are bullish, buy a Call.&lt;br&gt;
If you are bearish, buy a Put.&lt;/p&gt;

&lt;p&gt;As long as the direction is right, the option should make money.&lt;/p&gt;

&lt;p&gt;After trading options a few times, however, you quickly discover that reality is not that simple.&lt;/p&gt;

&lt;p&gt;BTC may rise while the Call you bought barely moves. ETH may fall while your Put is still losing money. In a more extreme case, you may even get both the direction and the magnitude roughly right, yet still lose because time value decays, implied volatility collapses, and the bid-ask spread eats away the rest.&lt;/p&gt;

&lt;p&gt;The reason is not mysterious.&lt;/p&gt;

&lt;p&gt;Over a short horizon, the change in an option's value can be approximated as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv3xwo86iylzeq8erqmhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv3xwo86iylzeq8erqmhq.png" alt=" " width="510" height="74"&gt;&lt;/a&gt;&lt;br&gt;
Direction determines only part of the first term.&lt;/p&gt;

&lt;p&gt;What you are really buying is a bundle of &lt;strong&gt;Delta, Gamma, Vega, Theta, and liquidity exposure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is why I prefer to split options trading into two layers:&lt;/p&gt;

&lt;p&gt;The first layer answers:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where are BTC or ETH likely to go next?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second layer answers:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Which option should I use to express that view?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article focuses only on the second layer.&lt;/p&gt;

&lt;p&gt;I organize the problem into a four-dimensional selection framework:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Convexity&lt;/strong&gt;: Delta, Gamma, and Speed — which Strike should I choose?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing&lt;/strong&gt;: DTE, IV Term Structure, Basis/Funding — which Expiry should I choose?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Valuation&lt;/strong&gt;: IV Smile / Skew / Surface — is this option expensive or cheap?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution&lt;/strong&gt;: Spread, Depth, OI, Volume — can the theoretical edge actually be executed?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, Deribit, Binance Options, and OKX Options are placed into the same candidate pool for cross-venue comparison.&lt;/p&gt;

&lt;p&gt;Because this is a selector strategy designed mainly for research, analysis, and testing, the implementation directly calls public exchange APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function httpJson(url, source) {
    var raw = null
    try {
        G.requestCount++
        raw = HttpQuery(url, {
            method: "GET",
            timeout: G.cfg.requestTimeoutMs,
            headers: {"User-Agent": "FMZ-Four-Dimensional-Option-Selector/1.0"}
        })
        if (!raw) throw "Empty response"
        var parsed = typeof raw === "string" ? JSON.parse(raw) : raw
        if (parsed &amp;amp;&amp;amp; parsed.error) throw JSON.stringify(parsed.error)
        if (parsed &amp;amp;&amp;amp; parsed.code !== undefined &amp;amp;&amp;amp; String(parsed.code) !== "0" &amp;amp;&amp;amp; !Array.isArray(parsed)) {
            throw "code=" + parsed.code + ", msg=" + safeText(parsed.msg)
        }
        return parsed
    } catch (e) {
        throw source + " request failed: " + e + ", url=" + url
    } finally {
        Sleep(G.cfg.requestGapMs)
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. First, Make Data From Three Exchanges Speak the Same Language
&lt;/h2&gt;

&lt;p&gt;This step is more important than the formulas.&lt;/p&gt;

&lt;p&gt;Deribit, Binance, and OKX all provide relatively complete options-market data, but their field names, quoting conventions, contract specifications, and Greek conventions are not identical.&lt;/p&gt;

&lt;p&gt;If you directly compare values such as gamma, premium, or open interest returned by the three venues, you can easily end up comparing quantities that are not economically equivalent.&lt;/p&gt;

&lt;p&gt;At the time this article was checked, the public APIs could be summarized roughly as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4g9s6m98b7umvtfbocpd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4g9s6m98b7umvtfbocpd.png" alt=" " width="799" height="159"&gt;&lt;/a&gt;&lt;br&gt;
That is already enough to build an automated option selector.&lt;/p&gt;

&lt;p&gt;Before feeding the data into the model, however, I would not keep the venue-specific raw structures. I would normalize them into one standard record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;venue              Deribit / Binance / OKX
underlying         BTC / ETH
symbol             Original contract symbol
expiry             Expiration timestamp
DTE                Days to expiry
option_type        Call / Put
strike             K
forward            F
bid_price_usd      Standardized USD bid value
ask_price_usd      Standardized USD ask value
mid_price_usd      Mid price
bid_iv             Bid IV
ask_iv             Ask IV
mark_iv            Mark IV
delta_norm         Normalized Delta
gamma_norm         Normalized Gamma
theta_norm         Normalized Theta
vega_norm          Normalized Vega
open_interest_usd  Standardized OI
volume_24h_usd     Standardized 24h volume
spread_ratio       Relative bid-ask spread
depth_slippage     Estimated slippage for target notional
quote_age_ms       Quote age

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two important principles here.&lt;/p&gt;

&lt;p&gt;First, normalize prices into a common USD economic value before comparing them.&lt;/p&gt;

&lt;p&gt;Premium quotes, contract multipliers, and settlement assets may differ across venues. Seeing 0.02 on one venue and 800 on another does not mean the first contract is cheaper.&lt;/p&gt;

&lt;p&gt;Second, I prefer to recalculate the Greeks using one common model.&lt;/p&gt;

&lt;p&gt;Exchange-provided Greeks are extremely useful for validation and real-time display, but for cross-venue ranking I would rather use the same Forward, IV, DTE, and pricing model to recompute the Greeks.&lt;/p&gt;

&lt;p&gt;At least then, the Delta and Gamma being compared are defined on the same basis.&lt;/p&gt;

&lt;p&gt;For crypto options, it is often more natural to compare strikes against the corresponding forward price F, rather than simply using spot S .&lt;/p&gt;

&lt;p&gt;A unified moneyness coordinate can therefore be written as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3anzom0erq4zf86ew4j3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3anzom0erq4zf86ew4j3.png" alt=" " width="698" height="413"&gt;&lt;/a&gt;&lt;br&gt;
Once this variable is available, different strike grids across the three venues can be mapped into the same coordinate system.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Dimension One: Delta, Gamma, and Speed — Do Not Buy "Cheap"; Buy Convexity
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6lzda8qiy83w29v26rus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6lzda8qiy83w29v26rus.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
One of the easiest mistakes in directional options trading is to treat out-of-the-money as equivalent to high payoff potential.&lt;/p&gt;

&lt;p&gt;A very deep OTM Call may indeed look cheap, but at the same time it may have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;very low Delta;&lt;/li&gt;
&lt;li&gt;poor liquidity;&lt;/li&gt;
&lt;li&gt;rapid time decay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying may rise and the option can still behave as though almost nothing happened.&lt;/p&gt;

&lt;p&gt;The question I care about more is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If BTC moves in my predicted direction, will this option's Delta increase quickly?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Delta is the first-order sensitivity of the option price to the underlying:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3l1p4mfhxcecwpd7yuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3l1p4mfhxcecwpd7yuq.png" alt=" " width="798" height="226"&gt;&lt;/a&gt;&lt;br&gt;
Taken together, these three quantities help describe how an option can move from "not yet following the underlying very much" toward becoming increasingly sensitive to the underlying.&lt;/p&gt;

&lt;p&gt;Suppose I buy a BTC Call with a Delta of only 0.18.&lt;/p&gt;

&lt;p&gt;Initially, if BTC rises by $100, the first-order price exposure of the option is only about $18 per standardized unit of underlying exposure.&lt;/p&gt;

&lt;p&gt;But if BTC continues moving toward the strike, Gamma can increase and Delta may move from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.18
→ 0.25
→ 0.35
→ 0.50

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is not the number 0.18 by itself.&lt;/p&gt;

&lt;p&gt;The important question is whether the Delta path is accelerating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do Not Treat Delta = 0.15 as a Magic Number&lt;/strong&gt;&lt;br&gt;
Many traders favor OTM options with Delta in the 0.10–0.20 range because they often provide attractive leverage characteristics.&lt;/p&gt;

&lt;p&gt;But no fixed Delta should be treated as a mathematical law.&lt;/p&gt;

&lt;p&gt;The location of the Speed extreme moves with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IV;&lt;/li&gt;
&lt;li&gt;DTE;&lt;/li&gt;
&lt;li&gt;Forward;&lt;/li&gt;
&lt;li&gt;Strike;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;model assumptions.&lt;br&gt;
The best region for BTC under low volatility with 30 days to expiry is not necessarily the best region for ETH under high volatility with only 3 days to expiry.&lt;/p&gt;

&lt;p&gt;So in the program, I only use Delta as a first-stage candidate filter.&lt;/p&gt;

&lt;p&gt;For a bullish view, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.10 &amp;lt;= Delta &amp;lt;= 0.30

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a bearish view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-0.30 &amp;lt;= Delta &amp;lt;= -0.10

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not the final answer.&lt;/p&gt;

&lt;p&gt;It simply removes contracts that are extremely far out of the money or already too close to behaving like in-the-money options.&lt;/p&gt;

&lt;p&gt;After that, I look at Gamma, Speed, and premium cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You Do Not Have to Trust the Exchange for Speed — Just Bump the Model&lt;/strong&gt;&lt;br&gt;
Speed can be calculated analytically, but a finite-difference approximation is often more practical.&lt;/p&gt;

&lt;p&gt;Bump Forward slightly upward and downward by a small percentage &lt;br&gt;
, then recalculate Gamma:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulmdy2xupk9jbuhmht04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulmdy2xupk9jbuhmht04.png" alt=" " width="492" height="70"&gt;&lt;/a&gt;&lt;br&gt;
This has an important advantage:&lt;/p&gt;

&lt;p&gt;Deribit, Binance, and OKX do not need to provide Speed natively.&lt;/p&gt;

&lt;p&gt;As long as we have Strike, DTE, IV, and Forward, we can calculate it ourselves using the same model across all three venues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I Prefer to Ask: "How Much Convexity Did I Buy per Dollar of Premium?"&lt;/strong&gt;&lt;br&gt;
Comparing Gamma alone also has a problem.&lt;/p&gt;

&lt;p&gt;ATM options often have high Gamma, but they are also expensive.&lt;/p&gt;

&lt;p&gt;So we can build a rough but practical convexity-efficiency ranking metric:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlr6k73qea9cedlmypn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlr6k73qea9cedlmypn8.png" alt=" " width="313" height="78"&gt;&lt;/a&gt;&lt;br&gt;
This is not a new law of finance.&lt;/p&gt;

&lt;p&gt;It is simply a ranking measure:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For each dollar of premium, which contract gives me more second-order exposure?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The goal of the first dimension is therefore not to find the smallest Delta or the largest Gamma.&lt;/p&gt;

&lt;p&gt;It is to find a contract where:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Directional exposure has not yet been fully released, Gamma is in a favorable region, Speed has acceleration potential, and convexity is not too expensive relative to premium.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is another common misconception worth stating explicitly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A doubling of Delta does not mean the option price doubles.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Delta is a marginal sensitivity, not a price multiplier.&lt;/p&gt;

&lt;p&gt;Explosive option behavior emerges from the interaction of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;continued movement in the underlying;&lt;/li&gt;
&lt;li&gt;Gamma convexity;&lt;/li&gt;
&lt;li&gt;remaining time;&lt;/li&gt;
&lt;li&gt;IV changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  3. Dimension Two: DTE — Do Not First Ask "How Many Days Should I Buy?" Ask "When Should My View Be Realized?"
&lt;/h2&gt;

&lt;p&gt;Traditional options textbooks often summarize DTE with one sentence:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The closer an option gets to expiration, the faster Theta decays.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is correct, but it is not enough for real contract selection.&lt;/p&gt;

&lt;p&gt;Suppose one view is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;BTC may break out within the next 48 hours.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Another view is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Risk appetite may continue recovering over the next month.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Both are bullish views, but they should not use the same expiry.&lt;/p&gt;

&lt;p&gt;In crypto markets, I combine four groups of information when choosing expiry:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Expected realization horizon + ATM IV term structure + futures basis + perpetual funding&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Start With Your Own Forecast Horizon&lt;/strong&gt;&lt;br&gt;
This is one of the most overlooked inputs.&lt;/p&gt;

&lt;p&gt;If your model only has directional edge over the next 3–5 days, buying a 90-day option may mean paying for far more long-dated time value than you actually need.&lt;/p&gt;

&lt;p&gt;On the other hand, if your thesis requires two weeks to play out but you buy an option expiring tomorrow, you can be directionally correct and still be wiped out by Theta before the move arrives.&lt;/p&gt;

&lt;p&gt;The first rule for expiry selection is therefore not:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Near-dated options have more Gamma.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;DTE must cover the forecast horizon, with some additional room for timing error.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Forecasting when something may happen is part of options trading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Then Look at the ATM IV Term Structure&lt;/strong&gt;&lt;br&gt;
Connect the ATM implied volatility for each expiry and you get the simplest form of volatility term structure.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3D ATM IV     78%
7D ATM IV     72%
14D ATM IV    61%
30D ATM IV    58%
60D ATM IV    56%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells us that the market is paying a large premium for near-term risk.&lt;/p&gt;

&lt;p&gt;If my view is merely "short-term bullish," buying the 3D Call may mean making two bets at the same time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;buying direction;&lt;/li&gt;
&lt;li&gt;buying very expensive short-term volatility.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Suppose BTC rises, but after the event passes IV falls from 78% to 60%.&lt;/p&gt;

&lt;p&gt;The Vega loss can easily consume part of the Delta gain.&lt;/p&gt;

&lt;p&gt;That is why DTE selection is not only about Theta management.&lt;/p&gt;

&lt;p&gt;It is also about managing the price of volatility across maturities.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use Basis and Funding as Crowding Indicators, Not Direct Directional Signals
&lt;/h2&gt;

&lt;p&gt;One of the major differences between crypto and traditional commodity futures markets is the importance of perpetual contracts and funding rates.&lt;/p&gt;

&lt;p&gt;Suppose we observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Persistently high perpetual funding
+
Clearly positive forward basis
+
Expensive near-term Call skew

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At minimum, this tells us that bullish positioning is crowded.&lt;/p&gt;

&lt;p&gt;Even if I remain bullish, I should ask again:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Am I paying the highest price to express the market's most popular direction?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The opposite is also true.&lt;/p&gt;

&lt;p&gt;Very negative funding does not automatically mean we should buy Calls.&lt;/p&gt;

&lt;p&gt;Funding and basis can remain extreme for long periods.&lt;/p&gt;

&lt;p&gt;They describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;positioning demand;&lt;/li&gt;
&lt;li&gt;financing;&lt;/li&gt;
&lt;li&gt;term pricing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are not standalone signals that reliably predict direction.&lt;/p&gt;

&lt;p&gt;In this framework, they act more like maturity and crowding filters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Should the Second Dimension Output?&lt;/strong&gt;&lt;br&gt;
I would not let the program simply say:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Buy the 7-day option.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead, it should calculate a Timing Score for each expiry, incorporating at least:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does DTE cover the forecast horizon?
Is Theta / Premium excessively high?
Is ATM IV unusually expensive at this expiry?
Do Basis / Funding indicate excessive directional crowding?
Does this expiry have enough liquidity?

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should be two or three candidate expiries, which then move to the next dimension.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Dimension Three: IV Smile / Skew / Surface — Even If the Direction Is Right, Do Not Buy the Most Expensive Option
&lt;/h2&gt;

&lt;p&gt;If the first dimension decides which part of the strike range to consider, and the second dimension decides which expiry to use, the third dimension asks:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Within the same direction and expiry, is the volatility price of this option reasonable?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For crypto options, I think this is where systematic selection becomes much more interesting than simply screening by Delta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop Using Strike Directly; Use Log-Moneyness&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm0axabkcd68cw9luh4ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm0axabkcd68cw9luh4ck.png" alt=" " width="798" height="209"&gt;&lt;/a&gt;&lt;br&gt;
This makes curves across different expiries easier to handle within a common framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why You Should Not Force a Symmetric "Smile"&lt;/strong&gt;&lt;br&gt;
BTC and ETH option surfaces are often asymmetric.&lt;/p&gt;

&lt;p&gt;At one point, Puts may become much more expensive because of hedging demand.&lt;/p&gt;

&lt;p&gt;At another point, far OTM Calls may be bid up by speculative demand or structured-product flows.&lt;/p&gt;

&lt;p&gt;If the model forces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call at +10% from ATM
=
Put at -10% from ATM

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will erase information contained in the skew.&lt;/p&gt;

&lt;p&gt;For a formal implementation, I therefore prefer a skew-capable volatility parameterization such as SVI.&lt;/p&gt;

&lt;p&gt;The classic SVI total-variance form can be written as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvackvtdin3fjngmw0xp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvackvtdin3fjngmw0xp7.png" alt=" " width="687" height="120"&gt;&lt;/a&gt;&lt;br&gt;
It is not the only possible choice.&lt;/p&gt;

&lt;p&gt;If an expiry has very few valid quotes, a simpler asymmetric quadratic or cubic smoother may be more robust.&lt;/p&gt;

&lt;p&gt;The goal is not to use the most complicated model.&lt;/p&gt;

&lt;p&gt;The goal is to avoid forcing a clearly asymmetric market into a symmetric curve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Cheap" Does Not Mean Low IV — It Means Low Relative to the Surface&lt;br&gt;
Suppose a BTC Call has an IV of 55%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The number 55% means very little by itself.&lt;/p&gt;

&lt;p&gt;If neighboring contracts at the same expiry imply a fair surface level around 62%, the option may be relatively cheap.&lt;/p&gt;

&lt;p&gt;If the fair surface level is only 48%, the same 55% option is actually expensive.&lt;/p&gt;

&lt;p&gt;Define:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frz14cxify0f7zgq12jfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frz14cxify0f7zgq12jfg.png" alt=" " width="670" height="101"&gt;&lt;/a&gt;&lt;br&gt;
For a contract I intend to buy, I care even more about:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbaiq6fq65fddwfpn8dd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbaiq6fq65fddwfpn8dd8.png" alt=" " width="667" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;because I pay the Ask when entering.&lt;/p&gt;

&lt;p&gt;I do not pay Mark IV, and I do not trade at a beautiful theoretical mid curve.&lt;/p&gt;

&lt;p&gt;This is an easy detail to overlook in live trading.&lt;/p&gt;

&lt;p&gt;An option may display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mark IV    52%
Fair IV    54%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It appears to be "2 vol cheap."&lt;/p&gt;

&lt;p&gt;But the real order book may be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bid IV     48%
Ask IV     58%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I am the buyer, my executable volatility is close to 58%.&lt;/p&gt;

&lt;p&gt;The supposedly cheap option is not cheap at all.&lt;/p&gt;

&lt;p&gt;Add 25-Delta Skew&lt;br&gt;
For directional trading, the 25-Delta Call and Put are useful reference points for seeing which wing the market is charging more for.&lt;/p&gt;

&lt;p&gt;Two simple metrics are:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjlfxv3wdrp6sx2w0vvkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjlfxv3wdrp6sx2w0vvkb.png" alt=" " width="682" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RR is closer to a directional-skew measure.&lt;/p&gt;

&lt;p&gt;BF is closer to the relative richness of the wings versus ATM.&lt;/p&gt;

&lt;p&gt;Suppose I am bullish BTC, but 25D Calls are already much more expensive than 25D Puts.&lt;/p&gt;

&lt;p&gt;That does not mean I cannot express a bullish view.&lt;/p&gt;

&lt;p&gt;It means I should acknowledge that I am buying an expensive Call wing.&lt;/p&gt;

&lt;p&gt;Moving closer to ATM, switching expiry, or even switching venue may be more efficient than blindly chasing the deepest OTM Call.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Deribit, Binance, and OKX Should Not Be Three Separate Strategies — They Should Be One Candidate Pool
&lt;/h2&gt;

&lt;p&gt;At this point, using three exchanges starts to become genuinely useful.&lt;/p&gt;

&lt;p&gt;If I look at only one venue, I can answer:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is this option expensive or cheap relative to other options on the same exchange?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once I add two more venues, I can ask another question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is the same BTC risk exposure cheaper somewhere else?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But I cannot simply place the premiums of the same strike across three exchanges side by side.&lt;/p&gt;

&lt;p&gt;The correct approach is to fit each venue's own volatility surface first, then project those surfaces into the same (k_T) coordinate system.&lt;/p&gt;

&lt;p&gt;For example, at a target point such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BTC
DTE ≈ 14 days
Delta ≈ 0.20 Call

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we may obtain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deribit Fair IV
Binance Fair IV
OKX Fair IV

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From these, we can build a liquidity-weighted Cross-Venue Consensus IV.&lt;/p&gt;

&lt;p&gt;Then every candidate contract receives two residuals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Residual
= Current Ask IV - Venue Fair IV

Cross-Venue Residual
= Current Ask IV - Three-Venue Consensus Fair IV

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to distinguish two very different meanings of "cheap."&lt;/p&gt;

&lt;p&gt;One contract may simply fall into a local depression in its own venue's Smile.&lt;/p&gt;

&lt;p&gt;Or an entire section of one venue's volatility surface may be cheaper than the equivalent region on the other two venues.&lt;/p&gt;

&lt;p&gt;The latter may come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;genuine cross-market supply and demand;&lt;/li&gt;
&lt;li&gt;contract specification differences;&lt;/li&gt;
&lt;li&gt;margin rules;&lt;/li&gt;
&lt;li&gt;settlement methods;&lt;/li&gt;
&lt;li&gt;participant structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I would &lt;strong&gt;not&lt;/strong&gt; automatically call it arbitrage.&lt;/p&gt;

&lt;p&gt;A cross-venue IV difference is useful selection information.&lt;/p&gt;

&lt;p&gt;It is not automatically a risk-free arbitrage opportunity.&lt;/p&gt;

&lt;p&gt;Actual cross-market arbitrage must also deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;capital distribution;&lt;/li&gt;
&lt;li&gt;margin;&lt;/li&gt;
&lt;li&gt;trading fees;&lt;/li&gt;
&lt;li&gt;contract multipliers;&lt;/li&gt;
&lt;li&gt;settlement rules;&lt;/li&gt;
&lt;li&gt;transfer time;&lt;/li&gt;
&lt;li&gt;leg risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a different system.&lt;/p&gt;

&lt;p&gt;This article only asks one question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If I already want to buy a directional option, which contract among the three venues deserves to enter the final candidate set?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Dimension Four: Liquidity — "Undervalued" Means Nothing if You Cannot Trade It
&lt;/h2&gt;

&lt;p&gt;One of the easiest places for an option surface to generate false signals is in far-out, inactive contracts with little or no trading.&lt;/p&gt;

&lt;p&gt;A Call may have an extremely attractive Mark IV, while the order book looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bid      20
Ask      35
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An algorithm looking only at Mark IV may rank it first.&lt;/p&gt;

&lt;p&gt;In reality, the 15-point spread can consume the theoretical edge immediately.&lt;/p&gt;

&lt;p&gt;That is why I prefer to use &lt;strong&gt;hard filters&lt;/strong&gt; for liquidity rather than assigning liquidity a small score that other attractive features can offset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Relative Bid-Ask Spread&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03d0mgowt25kqbcgp5e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03d0mgowt25kqbcgp5e3.png" alt=" " width="666" height="104"&gt;&lt;/a&gt;&lt;br&gt;
For a directional option buyer, the spread is effectively part of the loss you absorb immediately after entering.&lt;/p&gt;

&lt;p&gt;A reasonable threshold varies across DTE and Delta.&lt;/p&gt;

&lt;p&gt;So I would not hard-code a permanent 3% or 5% rule.&lt;/p&gt;

&lt;p&gt;A more adaptive approach is to rank spreads among nearby contracts within the same expiry and keep, for example, only the best 60%–70% by liquidity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Real Slippage for the Target Position&lt;/strong&gt;&lt;br&gt;
A good Best Ask does not mean enough size is available there.&lt;/p&gt;

&lt;p&gt;If I intend to buy $20,000 of premium, I should actually walk the order book and calculate:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodxv5unfnd16fxkzbjab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodxv5unfnd16fxkzbjab.png" alt=" " width="668" height="102"&gt;&lt;/a&gt;&lt;br&gt;
instead of assuming the entire position can be filled at level one.&lt;/p&gt;

&lt;p&gt;All three exchanges provide order-book endpoints, so this cost can be estimated before the trade.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look at OI and Volume Separately
High Open Interest means existing positions are present.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;High 24-hour Volume means people have actually been trading recently.&lt;/p&gt;

&lt;p&gt;I prefer to use both rather than rely on only one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High OI + Low Volume

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may simply indicate a large number of old positions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Low OI + Suddenly High Volume

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;may indicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a new contract;&lt;/li&gt;
&lt;li&gt;an event-driven burst;&lt;/li&gt;
&lt;li&gt;temporary short-term activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A more stable candidate should generally satisfy:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;There is an existing position base + recent trading activity + no obvious break in the order book.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Quotes Must Be Fresh&lt;/strong&gt;&lt;br&gt;
Timestamps are especially important in cross-venue scanning.&lt;/p&gt;

&lt;p&gt;If one exchange is a few seconds behind the others, that alone can create a fake IV depression during a fast market.&lt;/p&gt;

&lt;p&gt;So every normalized candidate record should keep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quote_age_ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quotes older than the accepted threshold should be discarded.&lt;/p&gt;

&lt;p&gt;They should not be used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;surface fitting;&lt;/li&gt;
&lt;li&gt;candidate ranking;&lt;/li&gt;
&lt;li&gt;cross-venue comparison.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Connect the Four Dimensions: From "Bullish BTC" to a Final Top 3
&lt;/h2&gt;

&lt;p&gt;Suppose the upstream strategy provides only three inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Underlying: BTC
Direction:  Long
Horizon:    7 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not tell me which Call to buy.&lt;/p&gt;

&lt;p&gt;The full selector can be expressed as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Directional View: BTC Bullish, Expected Horizon 7 Days
                                      │
                                      ▼
                 Deribit + Binance Options + OKX Options
                                      │
                                      ▼
                       Load All Tradable Call Options
                                      │
                                      ▼
          Normalize Prices, Contract Multipliers, Forward, DTE, Greeks
                                      │
                                      ▼
             Layer 1: Delta Filter (for example 0.10–0.30)
                                      │
                                      ▼
               Rank Gamma / Speed / Convexity Efficiency
                                      │
                                      ▼
          Layer 2: DTE + ATM IV Term Structure + Basis/Funding
                                      │
                                      ▼
                    Keep the Best-Matched 2–3 Expiries
                                      │
                                      ▼
               Layer 3: Fit IV Surface for Each Exchange
                                      │
                                      ▼
        Use Local Residual + Cross-Venue Residual for Relative Value
                                      │
                                      ▼
          Layer 4: Spread / Depth / OI / Volume / Staleness
                                      │
                                      ▼
                                  Final Top 3

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do not recommend compressing everything into one "magic total score" from the beginning.&lt;/p&gt;

&lt;p&gt;A more robust process is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Hard filters first, layered ranking second.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Pseudocode: illustrates the process only

candidates = load_options_from_deribit_binance_okx("BTC")
candidates = normalize_contracts(candidates)

# Direction and minimum tradability
candidates = [x for x in candidates if x.option_type == "CALL"]
candidates = [x for x in candidates if 0.10 &amp;lt;= x.delta_norm &amp;lt;= 0.30]
candidates = [x for x in candidates if not x.stale]
candidates = [x for x in candidates if x.depth_slippage &amp;lt;= MAX_SLIPPAGE]

# Convexity
for x in candidates:
    x.speed = calc_speed_by_bump(x)
    x.gamma_eff = x.gamma_norm * x.forward**2 / x.ask_price_usd

# Timing
expiries = rank_expiry_by_horizon_term_structure_basis(
    candidates,
    horizon_days=7
)
candidates = keep_selected_expiries(candidates, expiries)

# IV Surface
surfaces = fit_surface_by_venue_and_expiry(candidates)

for x in candidates:
    x.fair_iv = surfaces[x.venue, x.expiry].iv(x.log_moneyness)
    x.ask_residual = x.ask_iv - x.fair_iv

# Liquidity and execution cost
candidates = hard_liquidity_filter(candidates)

# Final ranking
top3 = rank_candidates(candidates)[:3]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real implementation, fixed thresholds can be replaced with percentiles.&lt;/p&gt;

&lt;p&gt;That allows the selector to adapt across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BTC;&lt;/li&gt;
&lt;li&gt;ETH;&lt;/li&gt;
&lt;li&gt;high-volatility regimes;&lt;/li&gt;
&lt;li&gt;low-volatility regimes;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;instead of relying on values that remain hard-coded forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. I Would Not Place an Order After the Four-Dimensional Filter — I Would Run Scenario Repricing First
&lt;/h2&gt;

&lt;p&gt;After the four-dimensional screen, there is still one critical check:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If my directional view actually plays out, how much will this option make?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;High Gamma, cheap IV, and good liquidity still do not guarantee that the return profile meets the trading objective.&lt;/p&gt;

&lt;p&gt;Suppose the upstream model provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current BTC Forward: F
Expected in 5 days:  F × 1.06
Expected IV change:  -3 vol / 0 / +3 vol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F43sjqhtx92vy5wrfqne9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F43sjqhtx92vy5wrfqne9.png" alt=" " width="670" height="241"&gt;&lt;/a&gt;&lt;br&gt;
I would run at least three IV scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Direction realized + IV falls
Direction realized + IV unchanged
Direction realized + IV rises
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a Call only produces an attractive result when:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;BTC rallies sharply and IV continues rising,&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;then the position is really a demanding two-factor bet.&lt;/p&gt;

&lt;p&gt;In contrast, if BTC rises as expected and the option still maintains an acceptable return even after IV falls by several vol points, that contract is more consistent with the original purpose:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;expressing a directional view through options.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This step also puts the Greeks back into their proper role.&lt;/p&gt;

&lt;p&gt;Greeks are &lt;strong&gt;local sensitivities.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final trade decision should return to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full repricing;&lt;/li&gt;
&lt;li&gt;realistic execution costs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9pshychz9ss99wp8nu63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9pshychz9ss99wp8nu63.png" alt=" " width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. What Role Should Each Exchange Play in the System?
&lt;/h2&gt;

&lt;p&gt;When building the model, I would not simply ask which venue is "best."&lt;/p&gt;

&lt;p&gt;I would rather treat the three venues as markets that can validate one another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deribit: Suitable as a Primary Surface and High-Frequency Greeks Source&lt;/strong&gt;&lt;br&gt;
Deribit's ticker / WebSocket ticker provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bid IV;&lt;/li&gt;
&lt;li&gt;ask IV;&lt;/li&gt;
&lt;li&gt;mark IV;&lt;/li&gt;
&lt;li&gt;Greeks;&lt;/li&gt;
&lt;li&gt;OI;&lt;/li&gt;
&lt;li&gt;market data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its options toolchain is highly concentrated, which makes it suitable as one of the primary data sources for BTC/ETH volatility surfaces.&lt;/p&gt;

&lt;p&gt;Deribit also provides historical volatility and volatility-index-related interfaces.&lt;/p&gt;

&lt;p&gt;So if the model later expands from:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is current IV expensive?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IV vs. realized volatility;&lt;/li&gt;
&lt;li&gt;DVOL;&lt;/li&gt;
&lt;li&gt;historical IV percentiles;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the extension path is relatively straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binance Options: Useful as a Second Quote Center and USDT-Ecosystem Reference&lt;/strong&gt;&lt;br&gt;
Binance /eapi/v1/mark provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bidIV;&lt;/li&gt;
&lt;li&gt;askIV;&lt;/li&gt;
&lt;li&gt;markIV;&lt;/li&gt;
&lt;li&gt;Delta;&lt;/li&gt;
&lt;li&gt;Gamma;&lt;/li&gt;
&lt;li&gt;Theta;&lt;/li&gt;
&lt;li&gt;Vega.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ticker, openInterest, and depth add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trading activity;&lt;/li&gt;
&lt;li&gt;open interest;&lt;/li&gt;
&lt;li&gt;order-book depth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the selector, the value of Binance is not merely that it is "another exchange."&lt;/p&gt;

&lt;p&gt;It helps answer a more useful question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If one region of the Deribit Smile looks cheap or expensive, is that a Deribit-specific phenomenon, or is the broader market pricing the same way?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OKX Options: Useful as an Independent Surface and Forward Cross-Check&lt;/strong&gt;&lt;br&gt;
OKX /api/v5/public/opt-summary is particularly useful for this framework.&lt;/p&gt;

&lt;p&gt;Its output includes fields such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delta / Gamma / Vega / Theta;&lt;/li&gt;
&lt;li&gt;deltaBS;&lt;/li&gt;
&lt;li&gt;gammaBS;&lt;/li&gt;
&lt;li&gt;thetaBS;&lt;/li&gt;
&lt;li&gt;vegaBS;&lt;/li&gt;
&lt;li&gt;markVol;&lt;/li&gt;
&lt;li&gt;bidVol;&lt;/li&gt;
&lt;li&gt;askVol;&lt;/li&gt;
&lt;li&gt;fwdPx;&lt;/li&gt;
&lt;li&gt;ATM volatility-related fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In one request, the system can obtain much of the data required for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smile construction;&lt;/li&gt;
&lt;li&gt;Skew analysis;&lt;/li&gt;
&lt;li&gt;Forward checks;&lt;/li&gt;
&lt;li&gt;Greek validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined with public/open-interest and order-book endpoints, OKX can form a complete independent candidate pool.&lt;/p&gt;

&lt;p&gt;The goal is for all three venues to cross-check one another, not replace one another.&lt;/p&gt;
&lt;h2&gt;
  
  
  10. What Is Really Worth Automating Is Not "Predicting Up or Down" — It Is Translating a View Into a Contract
&lt;/h2&gt;

&lt;p&gt;Many quantitative strategies spend most of their effort on directional signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trend;&lt;/li&gt;
&lt;li&gt;momentum;&lt;/li&gt;
&lt;li&gt;machine learning;&lt;/li&gt;
&lt;li&gt;order flow;&lt;/li&gt;
&lt;li&gt;on-chain data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final output is often just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Long
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But once the execution instrument changes from futures to options, the directional signal has only completed half the job.&lt;/p&gt;

&lt;p&gt;The same view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BTC is bullish over the next week
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be translated into very different risk structures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3D 40Delta Call
7D 20Delta Call
14D 15Delta Call
30D ATM Call
Call Spread
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these positions are "bullish."&lt;/p&gt;

&lt;p&gt;Yet their sensitivity to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;price;&lt;/li&gt;
&lt;li&gt;time;&lt;/li&gt;
&lt;li&gt;volatility;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;is completely different.&lt;/p&gt;

&lt;p&gt;A more reasonable system architecture is therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Directional Model
   ↓
Output:
Direction + Confidence + Forecast Horizon + Expected Move
   ↓
Option Selector
   ↓
Delta / Gamma / Speed
DTE / Term Structure
IV Surface / Skew
Liquidity / Cost
   ↓
Scenario Repricing
   ↓
Top 3 Contracts
   ↓
Risk Budget Determines Final Position Size
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key shift is this:&lt;/p&gt;

&lt;p&gt;We stop asking:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Which option is the cheapest?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;and start asking:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Which option is the best instrument for expressing this particular forecast?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those are completely different questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Risk: Limited Maximum Loss for the Buyer Does Not Mean the Strategy Is Low Risk
&lt;/h2&gt;

&lt;p&gt;One of the major advantages of directional long-option positions is that the maximum loss on a single trade can usually be constrained explicitly through the premium budget.&lt;/p&gt;

&lt;p&gt;But this creates another illusion:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If I can only lose the premium, I can just keep buying.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The real danger is a sequence of small losses that go to zero.&lt;/p&gt;

&lt;p&gt;A model that repeatedly buys low-Delta, short-DTE options may exhibit a distribution like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Small maximum loss per trade
Low win rate
Persistently negative Theta
Persistently negative spread cost
Occasional very large winner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whether this return distribution has positive expectancy cannot be proven simply by saying:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Losses are limited.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The four-dimensional selector only solves one problem:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Once the decision has been made to express a directional view through options, reduce obviously poor contract choices.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; replace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;statistical edge in the directional model itself;&lt;/li&gt;
&lt;li&gt;per-trade premium budgets;&lt;/li&gt;
&lt;li&gt;daily / weekly maximum-loss rules;&lt;/li&gt;
&lt;li&gt;same-direction concentration limits;&lt;/li&gt;
&lt;li&gt;cross-exchange capital and operational risk management;&lt;/li&gt;
&lt;li&gt;stress tests for extreme markets, IV collapse, and disappearing liquidity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is especially important to remember:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Undervalued IV" is only a relative value under the model.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It does not mean IV must revert toward the fitted value.&lt;/p&gt;

&lt;p&gt;The entire volatility surface can move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: An Option Is Not a "Future With Insurance" — It Is a Payoff Curve You Can Design
&lt;/h2&gt;

&lt;p&gt;If I had to summarize the framework in one sentence:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Getting the direction right only means you solved the sign of Delta. The quality of the option trade depends on how much Gamma you bought, how much Theta you paid, whether Vega was expensive or cheap, and whether you can actually execute the position at a reasonable price.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One major advantage of crypto options is that these data are highly structured.&lt;/p&gt;

&lt;p&gt;Deribit, Binance Options, and OKX Options all provide sufficiently complete public APIs to turn the traditional process of:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;staring at an option chain and choosing contracts from experience&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;into a repeatable program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Direction
→ Strike
→ Expiry
→ IV Surface
→ Liquidity
→ Cross-Venue Compare
→ Scenario Repricing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I continue developing this framework, I am less interested in adding a fifth indicator than in fully separating the selector from the upstream directional model.&lt;/p&gt;

&lt;p&gt;The directional model should only predict:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where?
How long?
How far?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The option module should specialize in finding the most appropriate risk expression.&lt;/p&gt;

&lt;p&gt;At that point, options stop being a simple trading button where:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;bullish = buy Call&lt;/em&gt;&lt;br&gt;
&lt;em&gt;bearish = buy Put&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;and become what they really are:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;a payoff function that can be calculated, filtered, compared, and designed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cryptooptions</category>
      <category>quanttrading</category>
      <category>cryptotrading</category>
      <category>deribit</category>
    </item>
    <item>
      <title>Win Rate, Payoff Ratio, Frequency, and Position Sizing: The Four Dimensions That Really Matter in Quantitative Trading</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 26 Aug 2026 08:47:28 +0000</pubDate>
      <link>https://dev.to/quant001/win-rate-payoff-ratio-frequency-and-position-sizing-the-four-dimensions-that-really-matter-in-4il</link>
      <guid>https://dev.to/quant001/win-rate-payoff-ratio-frequency-and-position-sizing-the-four-dimensions-that-really-matter-in-4il</guid>
      <description>&lt;p&gt;A quantitative strategy can be as complex as you like, but complexity alone does not tell you whether the strategy is worth trading.&lt;/p&gt;

&lt;p&gt;Moving averages, RSI, machine-learning models, and even more sophisticated Agents all eventually come down to a few very simple questions:&lt;/p&gt;

&lt;p&gt;How likely is the strategy to make money? How much does it make when it wins? How often do such opportunities appear? How much risk does it take each time?&lt;/p&gt;

&lt;p&gt;In actual strategy operation, these correspond to the four dimensions that are most worth monitoring continuously:&lt;/p&gt;

&lt;p&gt;win rate, payoff ratio, trading frequency, and position sizing.&lt;/p&gt;

&lt;p&gt;These four dimensions do not describe every risk in a trading system, but they are extremely useful for answering one practical question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How does a strategy's statistical edge ultimately translate into returns and drawdowns?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Using the cryptocurrency market and an FMZ strategy implementation as examples, this article focuses on how these four dimensions interact and how to turn them into live-trading monitoring metrics and risk constraints.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is for strategy research and software development purposes only and does not constitute investment advice.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Before Looking at Returns, Look at Net Expectancy per Trade
&lt;/h2&gt;

&lt;p&gt;Let:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the win rate be E;&lt;/li&gt;
&lt;li&gt;the average winning trade be p times the average losing trade;&lt;/li&gt;
&lt;li&gt;commissions, slippage, and funding costs per trade equal c times the average loss.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we normalize the average loss to one unit of risk, the net expectancy per trade can be written as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy46mvcf1nculmar9ihjm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy46mvcf1nculmar9ihjm.png" alt=" " width="204" height="52"&gt;&lt;/a&gt;&lt;br&gt;
This formula puts win rate, payoff ratio, and trading costs into the same framework.&lt;/p&gt;

&lt;p&gt;For example, consider a BTC perpetual strategy with:&lt;/p&gt;

&lt;p&gt;a 42% win rate;&lt;br&gt;
a realized payoff ratio of 2.2:1;&lt;br&gt;
total trading costs of approximately 0.08 risk units per trade.&lt;br&gt;
Then:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1540dnhrjuhb99atyrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1540dnhrjuhb99atyrj.png" alt=" " width="302" height="70"&gt;&lt;/a&gt;&lt;br&gt;
In other words, if these statistical characteristics can be maintained, each completed trade has an average net expectancy of approximately 0.264 risk units.&lt;/p&gt;

&lt;p&gt;If the strategy plans to risk 0.5% of account equity per trade and produces 12 valid trades per month, then under the simplified assumptions of no compounding and no overlapping risk:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fksvlemvu5qlu520bj22f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fksvlemvu5qlu520bj22f.png" alt=" " width="235" height="63"&gt;&lt;/a&gt;&lt;br&gt;
This is not a return forecast.&lt;/p&gt;

&lt;p&gt;What it really tells us is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Win rate alone is meaningless, and payoff ratio alone is also meaningless. What matters is their combined expectancy after costs.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Win Rate and Payoff Ratio Must Be Evaluated Together
&lt;/h2&gt;

&lt;p&gt;Among the four dimensions, this pair is probably the easiest to optimize incorrectly.&lt;/p&gt;

&lt;p&gt;Many strategies can increase their win rate simply by changing the exit logic.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;taking profits earlier;&lt;/li&gt;
&lt;li&gt;widening stop losses;&lt;/li&gt;
&lt;li&gt;continuing to wait after a trade moves into a loss;&lt;/li&gt;
&lt;li&gt;averaging down to reduce the average entry price.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is often:&lt;/p&gt;

&lt;p&gt;The win rate improves, but the average winner becomes smaller, the average loser becomes larger, and tail losses become more dangerous.&lt;/p&gt;

&lt;p&gt;Consider a strategy with an 85% win rate, an average winner of 0.3R, and an average loser of 2.5R:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa7kpj9ln9yg7yc8wb6x0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa7kpj9ln9yg7yc8wb6x0.png" alt=" " width="271" height="61"&gt;&lt;/a&gt;&lt;br&gt;
It still has negative expectancy.&lt;/p&gt;

&lt;p&gt;Conversely, a trend-following strategy may have a win rate of only around 30%–40%, yet a small number of large trends can compensate for many small losses.&lt;/p&gt;

&lt;p&gt;So in live trading, it is not enough to save only one overall win-rate number. The following metrics are much more useful:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzloptapcrli9a7e3mtjp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzloptapcrli9a7e3mtjp.png" alt=" " width="723" height="252"&gt;&lt;/a&gt;&lt;br&gt;
Here, using R-multiples is especially useful because directly comparing USDT profit and loss can be misleading.&lt;/p&gt;

&lt;p&gt;Define:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flnksi58ihlpgbmp7ujls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flnksi58ihlpgbmp7ujls.png" alt=" " width="326" height="94"&gt;&lt;/a&gt;&lt;br&gt;
For example:&lt;/p&gt;

&lt;p&gt;A trade earns 100 USDT while the original planned risk was only 50 USDT. The result is therefore +2R.&lt;/p&gt;

&lt;p&gt;Another trade earns 300 USDT but takes 500 USDT of planned risk. Its result is only +0.6R.&lt;/p&gt;

&lt;p&gt;The second trade makes more money in absolute terms, but the first trade uses risk much more efficiently.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. What Really Matters About Frequency Is the Number of Effective Opportunities
&lt;/h2&gt;

&lt;p&gt;Frequency should not simply mean how many times a strategy trades per day or per month.&lt;/p&gt;

&lt;p&gt;At a minimum, three different concepts should be distinguished:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;signal frequency, order frequency, and completed-trade frequency.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A strategy may have only 20 genuine entry opportunities in a month while submitting, modifying, and canceling thousands of orders.&lt;/p&gt;

&lt;p&gt;In that case, what increases may not be Alpha, but instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commissions;&lt;/li&gt;
&lt;li&gt;bid-ask spread costs;&lt;/li&gt;
&lt;li&gt;slippage;&lt;/li&gt;
&lt;li&gt;API and order-state-management pressure;&lt;/li&gt;
&lt;li&gt;the probability of duplicate orders;&lt;/li&gt;
&lt;li&gt;market impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a more important question than "How many trades are there?" is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Does each additional trade still have enough net edge?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Suppose a minute-level strategy has an average gross edge of only 0.06% per trade, while the complete round-trip trading cost has already reached 0.05%.&lt;/p&gt;

&lt;p&gt;Only 0.01% of edge remains.&lt;/p&gt;

&lt;p&gt;A small deterioration in live execution may be enough to eliminate the positive expectancy entirely.&lt;/p&gt;

&lt;p&gt;On the other hand, trading frequency cannot be reduced indefinitely either.&lt;/p&gt;

&lt;p&gt;If a strategy produces only a few trades per year, then even if all of them are profitable, it is difficult to determine whether the strategy has a persistent long-term edge or whether that particular market regime simply happened to suit it.&lt;/p&gt;

&lt;p&gt;Therefore, what should actually be optimized is neither "high frequency" nor "low frequency," but:&lt;/p&gt;

&lt;p&gt;obtaining as many relatively independent positive-expectancy opportunities as possible while keeping trading costs manageable.&lt;/p&gt;

&lt;p&gt;The word independent is important here.&lt;/p&gt;

&lt;p&gt;If BTC, ETH, and SOL all generate long signals at the same time, that may look like three separate opportunities, but they may simply be three manifestations of the same crypto-market risk factor.&lt;/p&gt;

&lt;p&gt;Opening three positions does not necessarily mean that you have obtained three independent sources of edge.&lt;/p&gt;

&lt;p&gt;That is why a live system should also monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same-direction risk;&lt;/li&gt;
&lt;li&gt;cross-asset correlation;&lt;/li&gt;
&lt;li&gt;concentrated signals within the same time window;&lt;/li&gt;
&lt;li&gt;total notional exposure;&lt;/li&gt;
&lt;li&gt;total planned stop-loss risk.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. Position Sizing Is Not Leverage; It Is Risk Budgeting
&lt;/h2&gt;

&lt;p&gt;Position sizing is the variable among the four dimensions that actually converts a statistical edge into an account-level result.&lt;/p&gt;

&lt;p&gt;The correct calculation sequence should be:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;First decide the maximum amount you are willing to lose, then work backward to determine how much you should buy.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Account equity:10,000 USDT&lt;br&gt;
Maximum planned risk per trade:0.5%&lt;br&gt;
The risk budget is therefore:10,000 × 0.5% = 50 USDT&lt;br&gt;
If the distance between the entry price and the stop-loss price is 2.5%, the approximate notional position size is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F663a9o16cwivm434xvb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F663a9o16cwivm434xvb0.png" alt=" " width="214" height="63"&gt;&lt;/a&gt;&lt;br&gt;
What actually determines the risk here is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;stop distance × notional position size.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Leverage mainly affects how much margin is occupied.&lt;/p&gt;

&lt;p&gt;Of course, real markets also involve slippage, price gaps, insufficient liquidity, and liquidation risk. So "planning to lose 50 USDT" does not mean the actual loss will always be exactly 50 USDT.&lt;/p&gt;

&lt;p&gt;This is why the system should record not only planned risk, but also:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bkf8ahgwj8a6gehem0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bkf8ahgwj8a6gehem0i.png" alt=" " width="175" height="79"&gt;&lt;/a&gt;&lt;br&gt;
If a strategy is designed to lose no more than 1R per trade but live trading repeatedly produces losses of -1.5R or -2R, the first thing to inspect is not the signal logic but execution and risk control.&lt;/p&gt;
&lt;h2&gt;
  
  
  Portfolio Risk Must Also Be Controlled
&lt;/h2&gt;

&lt;p&gt;Suppose the system simultaneously holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a BTC long position;&lt;/li&gt;
&lt;li&gt;an ETH long position;&lt;/li&gt;
&lt;li&gt;a SOL long position.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each trade has a planned risk of 0.5% of account equity.&lt;/p&gt;

&lt;p&gt;Viewed independently, none of them appears large.&lt;/p&gt;

&lt;p&gt;But if all three positions are fundamentally exposed to the same market direction, the real issue is what happens if all three stop losses are triggered together.&lt;/p&gt;

&lt;p&gt;Therefore, position management should have at least three layers of risk limits:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr7ups1duvtn3e8g63zq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr7ups1duvtn3e8g63zq3.png" alt=" " width="313" height="149"&gt;&lt;/a&gt;&lt;br&gt;
The exact numbers are not the point.&lt;/p&gt;

&lt;p&gt;The important principle is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is not enough to limit the size of each individual trade. The system must also limit portfolio risk after correlated positions are stacked together.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  5. The Four Dimensions Form a Profitability Chain
&lt;/h2&gt;

&lt;p&gt;Putting the relationships above together, a simplified expectancy over a given period can be written as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zpzj13m3vg1q8y5wtcm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zpzj13m3vg1q8y5wtcm.png" alt=" " width="191" height="59"&gt;&lt;/a&gt;&lt;br&gt;
where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;E_R: average net expectancy per trade, measured in R;&lt;/li&gt;
&lt;li&gt;N: number of effective trading opportunities;&lt;/li&gt;
&lt;li&gt;r: risk per trade as a percentage of account equity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More generally, if the amount of risk differs from trade to trade, the relationship is closer to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzdn9vk6q0qvr95vx4323.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzdn9vk6q0qvr95vx4323.png" alt=" " width="193" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This reveals an important point:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The four dimensions cannot be maximized independently.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Raising the profit target may increase the payoff ratio but reduce the win rate.&lt;/p&gt;

&lt;p&gt;Relaxing signal conditions may increase trading frequency but reduce expectancy per trade.&lt;/p&gt;

&lt;p&gt;Increasing position size can amplify returns, but it also amplifies drawdowns and model error.&lt;/p&gt;

&lt;p&gt;Reducing the use of stop losses may improve the short-term win rate while transferring risk into a small number of extreme losses.&lt;/p&gt;

&lt;p&gt;What really needs to be optimized is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The overall structure formed by all four dimensions under trading-cost, drawdown, and execution constraints.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  6. In FMZ, You Do Not Actually Need to Record Very Much
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3yn2z7r96ehpp5zl9x1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3yn2z7r96ehpp5zl9x1x.png" alt=" " width="799" height="266"&gt;&lt;/a&gt;&lt;br&gt;
If the complete strategy source code already exists, there is little value in explaining GetTicker(), GetDepth(), or order-placement APIs one by one.&lt;/p&gt;

&lt;p&gt;What the four-dimensional framework really needs is a unified trade record.&lt;/p&gt;

&lt;p&gt;After every fully closed trade, at least the following data should be saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group 1: Trade Identity and Time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnxwk8oyxl1ahngjwfph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnxwk8oyxl1ahngjwfph.png" alt=" " width="799" height="170"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Group 2: Risk Baseline at Entry&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faznsl0k3okfvp6xaod8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faznsl0k3okfvp6xaod8f.png" alt=" " width="800" height="91"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Group 3: Realized PnL and Trading Costs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fee6vy4u5l7jcdr7e6ok6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fee6vy4u5l7jcdr7e6ok6.png" alt=" " width="800" height="164"&gt;&lt;/a&gt;&lt;br&gt;
Slippage is best stored separately as an execution-quality metric.&lt;/p&gt;

&lt;p&gt;One definition requires particular care:&lt;/p&gt;

&lt;p&gt;If grossPnl is already calculated from actual execution prices, the impact caused by execution prices deviating from theoretical prices has already entered the trading PnL.&lt;/p&gt;

&lt;p&gt;Subtracting the same slippage from netPnl again may therefore result in double counting.&lt;/p&gt;

&lt;p&gt;The system should clearly distinguish between:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cash costs and execution-attribution costs.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  A More Robust Four-Dimensional Statistics Function
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzxlxpdet7uvgr0akupv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzxlxpdet7uvgr0akupv.png" alt=" " width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The principle is actually very simple.&lt;/p&gt;

&lt;p&gt;Instead of reconstructing expectancy from the win rate and average win/loss, it is more robust to directly average the already standardized realizedR values of all trades.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function average(arr) {
    if (!arr || arr.length === 0) {
        return 0
    }

    return arr.reduce((sum, x) =&amp;gt; sum + x, 0) / arr.length
}

function calcFourDimensions(trades) {
    if (!trades || trades.length === 0) {
        return null
    }

    const rs = trades.map(t =&amp;gt; t.realizedR)
    const wins = rs.filter(r =&amp;gt; r &amp;gt; 0)
    const losses = rs.filter(r =&amp;gt; r &amp;lt; 0)

    const avgWinR = wins.length ? average(wins) : 0
    const avgLossR = losses.length ? Math.abs(average(losses)) : 0

    return {
        count: trades.length,

        winRate:
            wins.length / trades.length,

        payoffRatio:
            avgLossR &amp;gt; 0
                ? avgWinR / avgLossR
                : null,

        expectancyR:
            average(rs),

        avgRiskRate:
            average(
                trades
                    .filter(t =&amp;gt; t.equityAtEntry &amp;gt; 0)
                    .map(t =&amp;gt; t.plannedRisk / t.equityAtEntry)
            )
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important line is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expectancyR = average(realizedR)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A breakeven trade is therefore simply 0. There is no need to artificially classify every trade as either a "win" or a "loss" before calculating expectancy.&lt;/p&gt;

&lt;p&gt;It is also advisable to maintain at least two rolling windows, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Most recent 20–30 trades
Most recent 100 trades

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare them with the historical backtest baseline.&lt;/p&gt;

&lt;p&gt;Full-history statistics are useful only for answering:&lt;/p&gt;

&lt;p&gt;"How has this strategy performed overall in the past?"&lt;/p&gt;

&lt;p&gt;Rolling statistics are more useful for answering:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is it still behaving the way it used to?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Testing the Four-Dimensional Framework With a Simple Strategy
&lt;/h2&gt;

&lt;p&gt;There is no need to use a complicated signal model just to observe these four dimensions.&lt;/p&gt;

&lt;p&gt;In fact, the Alpha component should be kept as simple as possible.&lt;/p&gt;

&lt;p&gt;For example, we can use the following research strategy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F05em0xddycxfx4ok1re2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F05em0xddycxfx4ok1re2.png" alt=" " width="640" height="354"&gt;&lt;/a&gt;&lt;br&gt;
The strategy itself is not the Alpha this article is trying to prove.&lt;/p&gt;

&lt;p&gt;It is only an experimental vehicle.&lt;/p&gt;

&lt;p&gt;What we actually want to observe is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens to the four dimensions after the signal passes through position sizing, costs, and execution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, suppose the backtest shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Win rate               41%
Payoff ratio            2.4
Average trades/month    11
Average risk             0.5%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the rolling live-trading window suddenly changes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Win rate                34%
Payoff ratio             1.3
Monthly trade frequency 27
Actual risk               0.8%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the issue is no longer simply "recent bad luck."&lt;/p&gt;

&lt;p&gt;It may mean:&lt;/p&gt;

&lt;p&gt;More false breakouts are reducing the win rate.&lt;/p&gt;

&lt;p&gt;Earlier exits or shorter trends are reducing the payoff ratio.&lt;/p&gt;

&lt;p&gt;Range-bound conditions are creating repeated entries and rapidly increasing trade frequency.&lt;/p&gt;

&lt;p&gt;Slippage and execution anomalies are causing actual risk to exceed planned risk.&lt;/p&gt;

&lt;p&gt;This is where a four-dimensional health check becomes genuinely useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Strategy Failure Should Be Divided Into Two Different Cases
&lt;/h2&gt;

&lt;p&gt;One of the easiest mistakes to make in live trading is to confuse:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;statistical strategy degradation with system execution failure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They should be handled very differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type 1: Statistical Degradation&lt;/strong&gt;&lt;br&gt;
For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rolling expectancy continues to decline;&lt;/li&gt;
&lt;li&gt;win rate and payoff ratio deteriorate at the same time;&lt;/li&gt;
&lt;li&gt;effective opportunity frequency declines;&lt;/li&gt;
&lt;li&gt;costs consume a significantly larger share of gross profit;&lt;/li&gt;
&lt;li&gt;drawdown moves outside its historically normal range.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This type of situation usually still leaves room for observation.&lt;/p&gt;

&lt;p&gt;Risk can be reduced first while the system continues collecting samples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type 2: System Failure&lt;/strong&gt;&lt;br&gt;
For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;abnormal market data;&lt;/li&gt;
&lt;li&gt;local position records do not match exchange positions;&lt;/li&gt;
&lt;li&gt;duplicate orders;&lt;/li&gt;
&lt;li&gt;order status cannot be confirmed;&lt;/li&gt;
&lt;li&gt;stop orders were not actually submitted or executed;&lt;/li&gt;
&lt;li&gt;actual risk repeatedly exceeds the system limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These problems do not require waiting for statistical significance.&lt;/p&gt;

&lt;p&gt;The system should immediately stop adding new risk.&lt;/p&gt;

&lt;p&gt;At that point, the question is no longer whether the Alpha is still valid, but whether the system can still execute correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Response Mechanism Can Be Compressed Into Three Levels
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdg2i8bxqy1jxzeo4ddp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdg2i8bxqy1jxzeo4ddp.png" alt=" " width="800" height="116"&gt;&lt;/a&gt;&lt;br&gt;
There is another important principle:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do not immediately re-optimize parameters simply because the strategy has recently lost money.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Otherwise, it is easy to mistake normal random variation for strategy failure and then continuously chase the most recent market regime.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. A Backtest Really Needs to Pass Only Three Gates
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkrxxlqepce14frl2owds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkrxxlqepce14frl2owds.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Instead of stacking up a large number of attractive-looking performance metrics, focus on three questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 1: Does the Edge Still Exist After Costs?&lt;/strong&gt;&lt;br&gt;
After commissions, slippage, funding costs, and bid-ask spread are included, does the strategy still have positive expectancy?&lt;/p&gt;

&lt;p&gt;If the strategy fails as soon as realistic trading costs are added, the original edge may have existed only because of idealized execution assumptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 2: Does the Edge Exist Only for One Specific Parameter Set?&lt;/strong&gt;&lt;br&gt;
Change the breakout lookback from 20 bars to 18, 19, 21, and 22.&lt;/p&gt;

&lt;p&gt;Change the ATR stop from 2 to 1.8 or 2.2.&lt;/p&gt;

&lt;p&gt;Test a different time period.&lt;/p&gt;

&lt;p&gt;Test BTC and ETH separately.&lt;/p&gt;

&lt;p&gt;Then run the test again.&lt;/p&gt;

&lt;p&gt;If only one extremely narrow parameter combination performs well, overfitting should be treated as a serious risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 3: Can the Backtest Statistics Be Reproduced Under Real Execution?&lt;/strong&gt;&lt;br&gt;
During the small-capital live-trading stage, what actually needs to be verified is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether realizedR matches expectations;&lt;/li&gt;
&lt;li&gt;whether actual costs are close to the assumptions;&lt;/li&gt;
&lt;li&gt;whether order states are reliable;&lt;/li&gt;
&lt;li&gt;whether stop-loss risk remains under control;&lt;/li&gt;
&lt;li&gt;whether trading frequency becomes abnormal;&lt;/li&gt;
&lt;li&gt;whether portfolio risk is calculated and controlled correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key at this stage is not how much money the strategy makes.&lt;/p&gt;

&lt;p&gt;The real objective is to confirm:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Does the four-dimensional structure observed in the backtest still exist in the real market?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Quantitative trading is not a "price-guessing contest."&lt;/p&gt;

&lt;p&gt;A strategy does not need to be right every time.&lt;/p&gt;

&lt;p&gt;What really needs to be built is a stable profitability chain:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signals with a statistical edge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A reasonable realized payoff ratio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enough effective trading opportunities, but not too many&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position sizing that can survive consecutive losses and model error&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Win rate tells us how often we make money.&lt;/p&gt;

&lt;p&gt;Payoff ratio tells us how many losing trades one winning trade can cover.&lt;/p&gt;

&lt;p&gt;Frequency determines whether the statistical edge can be realized within a finite amount of time, while also determining whether trading costs will consume that edge.&lt;/p&gt;

&lt;p&gt;Position sizing determines whether the account can survive long enough for the edge to materialize.&lt;/p&gt;

&lt;p&gt;The code simply keeps executing these rules consistently.&lt;/p&gt;

&lt;p&gt;A mature quantitative trading system should always know three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How much am I prepared to lose on this trade at most?&lt;/li&gt;
&lt;li&gt;Why might this strategy make money over the long run?&lt;/li&gt;
&lt;li&gt;Under what conditions should the system stop taking additional risk?&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>quantitativetrading</category>
      <category>tradingedge</category>
      <category>riskmanagement</category>
      <category>positionsizing</category>
    </item>
    <item>
      <title>From Pairs to Matrices: Let the Machine Find the Arbitrage Basket</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 26 Aug 2026 07:23:25 +0000</pubDate>
      <link>https://dev.to/quant001/from-pairs-to-matrices-let-the-machine-find-the-arbitrage-basket-4im3</link>
      <guid>https://dev.to/quant001/from-pairs-to-matrices-let-the-machine-find-the-arbitrage-basket-4im3</guid>
      <description>&lt;p&gt;I have recently become interested in relative-value and arbitrage strategies. In an earlier example, we traded EWY against Samsung Electronics and SK Hynix. That trade, however, depended on a human being first recognizing the economic relationship.&lt;/p&gt;

&lt;p&gt;Can we automate the search itself—screening the market for baskets that may contain a tradable relative-value structure?&lt;/p&gt;

&lt;p&gt;This article develops one possible workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What Did the EWY Trade Really Cost?
&lt;/h2&gt;

&lt;p&gt;Let us first clarify the earlier example.&lt;/p&gt;

&lt;p&gt;EWY is a South Korea ETF, and Samsung Electronics and SK Hynix are among its most important constituents. If EWY becomes expensive relative to those constituents, the trade is to short EWY, go long the constituents, and wait for the relative prices to move back toward their previous relationship.&lt;/p&gt;

&lt;p&gt;There is nothing wrong with that idea. But it contains a hidden cost that is rarely measured: &lt;strong&gt;how much prior knowledge did you need before you could even identify those three instruments?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You had to know that EWY tracks South Korea. You had to know that Samsung and SK Hynix carry large weights. You had to understand how the memory-chip cycle propagates through the group. You also had to estimate how much the Korean won could contaminate the spread.&lt;/p&gt;

&lt;p&gt;None of that information sits directly inside a price matrix. It sits in your head.&lt;/p&gt;

&lt;p&gt;That knowledge is an asset accumulated over many years. But precisely because it is a human asset, it is difficult to replicate, scale, or delegate to a machine.&lt;/p&gt;

&lt;p&gt;That is the motivation for the entire strategy:&lt;/p&gt;

&lt;p&gt;A market may contain hundreds of instruments, tens of thousands of pairs, and millions of three-name combinations. How many can one person examine?&lt;/p&gt;

&lt;p&gt;The ceiling of pair trading is not necessarily its return. The ceiling is that a human can only reason carefully about a limited number of relationships at once.&lt;/p&gt;

&lt;p&gt;The goal here is to take the step of deciding which instruments belong together away from manual selection and turn it into a process that can be rerun every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Step One: Arrange Prices as a Matrix
&lt;/h2&gt;

&lt;p&gt;Everything begins with a simple operation: place prices into a table.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rows represent time.&lt;/li&gt;
&lt;li&gt;Columns represent instruments.&lt;/li&gt;
&lt;li&gt;Each cell contains a log price.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P = np.array([[series[s][t] for s in kept] for t in ts], dtype=float)
L = np.log(P)          # T × N log-price matrix

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logarithm is not cosmetic. Relative-value trading is fundamentally concerned with ratios, and logarithms convert ratios into differences.&lt;/p&gt;

&lt;p&gt;A move from 10 to 20 and a move from 100 to 200 produce the same log return. Only after this transformation do later linear operations—weighting, projection, and regression—have a consistent interpretation.&lt;/p&gt;

&lt;p&gt;This matrix, with T rows and N columns, becomes the object on which the rest of the workflow operates. Its structure determines whether the instruments contain a potentially tradable relative relationship.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ten Semiconductor Instruments May Really Be One
&lt;/h2&gt;

&lt;p&gt;Consider a ten-instrument semiconductor-related basket. A typical candidate set may include AMD, ARM, ASML, INTC, MRVL, NVDA, TSM, QQQ, SPY, and another instrument admitted by the screen.&lt;/p&gt;

&lt;p&gt;Visually, the columns often look highly similar: when one rises, most of the others rise; when one falls, most of the others fall.&lt;/p&gt;

&lt;p&gt;In linear algebra, this is described as &lt;strong&gt;low rank&lt;/strong&gt;. The matrix may have ten columns, but its effective number of common degrees of freedom may be much smaller.&lt;/p&gt;

&lt;p&gt;Singular value decomposition provides a way to separate those directions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_, S, Vt = np.linalg.svd(Xc, full_matrices=False)
V  = Vt.T
Vf = V[:, :k]      # estimated common-trend directions
Vn = V[:, k:]      # estimated residual subspace

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choosing k is the first genuinely difficult question.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If k is too small, real common movement is treated as noise and directional market exposure remains in the basket.&lt;/li&gt;
&lt;li&gt;If k is too large, noise is promoted to a factor and the system trades a structure that may not exist.
The implementation uses a Marchenko–Pastur upper-edge rule as a practical factor-count heuristic. Under its random-matrix assumptions, eigenvalues above the noise edge are treated as candidate common factors:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def mp_factor_count(returns):
    T, N = returns.shape
    q = float(N) / float(T)
    upper = (1.0 + math.sqrt(q)) ** 2    # upper edge under the noise model
    ev = np.linalg.eigvalsh(C)
    return int(np.sum(ev &amp;gt; upper))       # count eigenvalues above the edge

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule automatically responds to sample size through q = N/T. A shorter training window or a larger universe raises the noise edge and makes the filter stricter.&lt;/p&gt;

&lt;p&gt;That is directionally sensible: when data are scarce, the model should be less willing to believe that an apparent structure is real.&lt;/p&gt;

&lt;p&gt;In one run, the result was k = 1.&lt;/p&gt;

&lt;p&gt;In other words, the basket was largely driven by one common movement, with the remaining dimensions representing relative deviations.&lt;/p&gt;

&lt;p&gt;The Marchenko–Pastur edge should still be treated as a heuristic rather than a proof. Financial returns are not independent Gaussian noise, and temporal dependence, heavy tails, and volatility clustering can change the empirical eigenvalue spectrum.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Residual Subspace: Several Distinct Relative-Value Directions at Once
&lt;/h2&gt;

&lt;p&gt;This is where the approach begins to differ from ordinary pair trading.&lt;/p&gt;

&lt;p&gt;If N = 10 and the estimated number of common trends is k = 1, the remaining N - k = 9 dimensions lie outside the leading common direction.&lt;/p&gt;

&lt;p&gt;Under a valid common-trend and cointegration structure, portfolios formed in this residual subspace can suppress much of the shared market movement and leave mainly relative deviations.&lt;/p&gt;

&lt;p&gt;If the entire semiconductor group rises 5%, a well-constructed residual portfolio should move much less. If the group falls 8%, the same should be true. What remains is the question of which member moved away from the others.&lt;/p&gt;

&lt;p&gt;The earlier EWY trade can be understood as a manually discovered direction in such a space. A human chose weights intended to cancel the broad South Korea exposure and isolate a relative mispricing.&lt;/p&gt;

&lt;p&gt;SVD provides multiple candidate directions at once.&lt;/p&gt;

&lt;p&gt;However, one distinction matters:&lt;/p&gt;

&lt;p&gt;Orthogonal directions are not automatically statistically independent bets.&lt;/p&gt;

&lt;p&gt;They are geometrically distinct coordinates in weight space. Their realized residual time series may still be correlated, especially outside a Gaussian model. Covariance whitening and out-of-sample validation are therefore still necessary.&lt;/p&gt;

&lt;p&gt;The practical change from pair trading to matrix trading is not merely that more instruments are involved. It is that the strategy monitors a multidimensional residual structure rather than a single spread.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3w80jgyi9qo865crvqrh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3w80jgyi9qo865crvqrh.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Figure 1 | Matrix arbitrage increases the number of relative-deviation dimensions that can be monitored outside the dominant common trend.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Whether the Space Is Usable Depends on Spectral Separation
&lt;/h2&gt;

&lt;p&gt;The output of SVD should not be accepted without a stability check.&lt;/p&gt;

&lt;p&gt;SVD orders directions by singular value. The first k directions are assigned to the common-trend space and the remainder to the residual space. The question is whether the boundary between the two is stable.&lt;/p&gt;

&lt;p&gt;Suppose the k-th singular value is 100 and the next is 20. The separation is large, so a moderate perturbation is unlikely to change the subspace dramatically.&lt;/p&gt;

&lt;p&gt;If the values are 51 and 49, the boundary is fragile. On the next rebuild, a direction may rotate from the residual space into the factor space. A carefully calculated portfolio from yesterday may then represent a substantially different exposure today.&lt;/p&gt;

&lt;p&gt;Perturbation results such as the Davis–Kahan and Wedin sin Θ theorems can be summarized schematically as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subspace rotation  ≤  perturbation size / absolute spectral separation

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The denominator is the separation between the relevant singular-value groups. It is a quantitative stability concept, not a metaphor.&lt;/p&gt;

&lt;p&gt;The implementation uses a singular-value &lt;strong&gt;ratio&lt;/strong&gt; as a convenient scale-free proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gap = float(S[k - 1] / S[k])

if gap &amp;lt; MIN_GAP:                 # default: 1.30
    return None, "insufficient singular-value separation {:.2f}&amp;lt;{:.2f}".format(
        gap, MIN_GAP
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strictly speaking, the perturbation theorem depends on absolute separation relative to perturbation magnitude, not on the ratio alone. The ratio is therefore an engineering filter, not a theorem-level guarantee.&lt;/p&gt;

&lt;p&gt;The theoretical screen is followed by an empirical one. The training window is split into two halves, the residual subspace is estimated separately in each half, and the principal angle between the estimates is measured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;half = eff // 2
angles = []

for seg in (train[:half], train[half:]):
    Sc = seg - seg.mean(axis=0)
    _, _, vt = np.linalg.svd(Sc, full_matrices=False)
    angles.append(principal_angles_deg(Vn, vt.T[:, k:]))

if max(angles) &amp;gt; MAX_ANGLE_DEG:   # default: 20°
    return None, "subspace rotation {:.1f}°&amp;gt;{:.1f}°".format(
        max_angle, MAX_ANGLE_DEG
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test asks a sharp question:&lt;/p&gt;

&lt;p&gt;Do the first and second halves of the sample point toward approximately the same relative-value space?&lt;/p&gt;

&lt;p&gt;A small angle suggests temporal consistency. A large angle suggests that the apparent structure may be specific to one section of the sample.&lt;/p&gt;

&lt;p&gt;Examples rejected by this filter included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6   BMNR,COINX,CRCLX,HOODX,MSTRX,ORCLX     subspace rotation 49.6° &amp;gt; 20.0°
6   AMZNX,GOOGLX,IBM,METAX,MSFT,PLTRX      subspace rotation 87.0° &amp;gt; 20.0°
13  AAPLX,AMZNX,BABA,CXMT,FUTUON,GOOGLX    subspace rotation 80.0° &amp;gt; 20.0°

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An angle of 87 degrees means the two estimated spaces are almost orthogonal.&lt;/p&gt;

&lt;p&gt;This filter often rejects the baskets that look most convincing to the eye. Large technology stocks may be highly correlated, but high correlation is not the same as a stable tradable relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftznsw7sh5qn5r5ze8ymm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftznsw7sh5qn5r5ze8ymm.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2 | Singular-value separation tests the boundary between factor and residual spaces; principal angles test whether the estimated residual structure rotates over time.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Two Statistics: One Finds Misalignment, the Other Avoids Catching a Falling Knife
&lt;/h2&gt;

&lt;p&gt;Once a basket is admitted, it enters real-time monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two statistics are used, and they serve different purposes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Q-score: How Deep Is the Structural Misalignment?&lt;br&gt;
Project the current log-price vector into the residual subspace and measure its Mahalanobis distance using the residual covariance estimated in training:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rvec = xc @ Vn
q_raw = float(rvec @ np.array(model["resid_inv"]) @ rvec)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why use Mahalanobis distance rather than Euclidean distance?&lt;/p&gt;

&lt;p&gt;Because residual directions naturally have different volatility scales. One may be highly active while another remains close to zero most of the time. A single absolute threshold would mainly monitor the largest-variance directions and largely ignore the rest.&lt;/p&gt;

&lt;p&gt;Mahalanobis distance normalizes the directions by their covariance and places them on a common scale.&lt;/p&gt;

&lt;p&gt;If the residual vector is approximately multivariate Gaussian and the covariance matrix is known—or estimated accurately from a much larger sample—the squared Mahalanobis distance is approximately distributed as χ²(r).&lt;/p&gt;

&lt;p&gt;This gives distribution-based reference levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"q_entry": float(sps.chi2.ppf(0.995, r)),   # 99.5th percentile: alert
"q_exit":  float(sps.chi2.ppf(0.80,  r)),   # 80th percentile: exit

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful because every parameter that can be grounded in a distribution leaves less room for manual optimization.&lt;/p&gt;

&lt;p&gt;The qualification matters: cointegration by itself does not imply Gaussian residuals, and using an estimated covariance matrix, autocorrelated observations, heavy tails, or seasonal rescaling makes the exact χ² calibration only approximate. In production, empirical quantiles and forward false-alarm rates should be compared with the theoretical thresholds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F-shock: Is the Entire Factor Space Being Repriced?&lt;/strong&gt;&lt;br&gt;
The purpose of this statistic is to prevent the system from taking the right action at the wrong time.&lt;/p&gt;

&lt;p&gt;Suppose the Q-score triggers and the system prepares to trade mean reversion. But the Federal Reserve has just released an unexpected statement and the entire semiconductor sector is being repriced.&lt;/p&gt;

&lt;p&gt;The prices may not return, because the new level may be economically justified.&lt;/p&gt;

&lt;p&gt;The strategy therefore monitors the speed of movement in the common factor itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;f = xc @ Vf                          # factor score
d = f - np.array(prev["f"])          # first difference
fshock = float(d @ np.array(model["f_inv"]) @ d)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An entry requires both conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"alarm":   q &amp;gt; model["q_entry"],      # residual structure is displaced
"blocked": fshock &amp;gt; model["f_limit"], # factor is moving violently: do not enter

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two statistics describe two sides of the same question:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Q, low F&lt;/strong&gt;: the systematic environment is relatively calm, but the relative positions are displaced. This is the intended setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Q, high F&lt;/strong&gt;: the systematic environment itself is changing. This is more likely repricing than temporary misalignment.
Classical multivariate statistical process control often assumes stationary variables. Log prices are generally modeled as integrated processes, so the classical formulation cannot simply be copied.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, Q is constructed from residual directions that have passed stationarity screening, while F is constructed from first differences of factor scores. Both statistics are moved onto approximately stationary inputs before distance thresholds are applied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwnk825yb9cta6c3iv6bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwnk825yb9cta6c3iv6bi.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3 | The preferred region has a large structural displacement but a calm common factor. A simultaneous factor shock is more consistent with event-driven repricing.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Attribution: A Residual Belongs to the Basket, Not to One Instrument
&lt;/h2&gt;

&lt;p&gt;A Q-score alert only says that the basket is displaced. It does not identify which instrument caused the displacement.&lt;/p&gt;

&lt;p&gt;The residual is created by projection, so it is a property of the portfolio as a whole.&lt;/p&gt;

&lt;p&gt;The implementation uses leave-one-out regression for attribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for i in range(N):
    others = [j for j in range(N) if j != i]

    A = np.column_stack([np.ones(eff), train[:, others]])
    b, _, _, _ = np.linalg.lstsq(A, train[:, i], rcond=None)

    res = train[:, i] - A @ b

    loo.append({
        "i": i,
        "others": others,
        "beta": b.tolist(),
        "sd": float(res.std(ddof=1))
    })

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is to reconstruct instrument i using the other N - 1 instruments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If reconstruction is accurate, instrument i has little idiosyncratic displacement.&lt;/li&gt;
&lt;li&gt;If its reconstruction residual becomes unusually large, the displacement is attributed to i.
At run time, each instrument receives a z-score:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pred = b[0] + float(b[1:] @ x[rec["others"]])
z = (x[rec["i"]] - pred) / rec["sd"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One live output looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ARM:-5.604   NVDAX:-3.099   INTC:2.548   ASML:1.564

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ARM was 5.6 standard deviations below the price implied by the other instruments.&lt;/p&gt;

&lt;p&gt;The proposed position was therefore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;long ARM;&lt;/li&gt;
&lt;li&gt;hedge with the remaining legs using the regression coefficients.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sgn = 1.0 if top["z"] &amp;gt; 0 else -1.0

w = {top["symbol"]: -sgn}

for pos_j, j in enumerate(rec["others"]):
    w[model["members"][j]] = sgn * float(b[1 + pos_j])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OLS normal equations guarantee that the &lt;strong&gt;in-sample regression residual&lt;/strong&gt; is orthogonal to the regressor columns. That is useful, but it should not be overstated.&lt;/p&gt;

&lt;p&gt;It does not by itself guarantee that the live portfolio is neutral to every latent market factor out of sample. A stronger implementation should verify the final weights against the estimated factor loadings or explicitly project the executable portfolio back into the residual subspace.&lt;/p&gt;

&lt;p&gt;The leave-one-out residual should also receive its own stationarity check. A basket-level stationary residual space does not automatically imply that every leave-one-out regression residual is stationary.&lt;/p&gt;

&lt;p&gt;This is the automated analogue of the EWY triangle—except that it may contain many legs and is rebuilt repeatedly from data.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Three Traps: Perfect In Sample, Persistent Losses Out of Sample
&lt;/h2&gt;

&lt;p&gt;The previous sections describe the method. The following traps are more important because each can produce an attractive backtest and a slow live loss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 1: Path Dependence in Leveraged ETFs&lt;/strong&gt;&lt;br&gt;
SOXL targets three times the daily return of a semiconductor index. TQQQ targets three times the daily return of the Nasdaq-100.&lt;/p&gt;

&lt;p&gt;Their short-horizon correlation with the underlying market can approach 0.99, which makes them appear to be ideal relative-value partners.&lt;/p&gt;

&lt;p&gt;But daily rebalancing means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ln(3× leveraged ETF)  ≠  3 × ln(underlying)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is path dependent and is influenced by volatility, return autocorrelation, financing costs, fees, and the daily reset mechanism.&lt;/p&gt;

&lt;p&gt;In volatile or mean-reverting markets, this often appears as volatility drag. In strongly trending markets, a leveraged ETF may instead outperform a simple long-horizon multiple.&lt;/p&gt;

&lt;p&gt;The key point is not that the drift is always deterministically negative. The key point is that &lt;strong&gt;high correlation does not imply a stable cointegrating relationship.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One practical screen marks a near-duplicate with much higher volatility as a potential leveraged version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if abs(C[i, j]) &amp;gt;= DEDUP_RHO:                    # ρ &amp;gt; 0.97
    hi, lo = (i, j) if vol[i] &amp;gt; vol[j] else (j, i)

    if vol[hi] / vol[lo] &amp;gt;= DEDUP_VOL_RATIO:     # volatility ratio &amp;gt; 1.40
        drop_idx.add(hi)

# Drop SNXX, keep SNDK:
# correlation 0.9813, volatility ratio 2.15×

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only catches pairwise near-duplicates.&lt;/p&gt;

&lt;p&gt;SOXL may not reach a correlation of 0.97 with any single constituent, so a second basket-level filter is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;med_vol = float(np.median(vols))
out = [
    i for i in range(len(kept))
    if vols[i] &amp;gt; VOL_OUTLIER_MULT * med_vol
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One log entry showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6  AMD,ARM,INTC,MRVL,SOXL,TSM
   basket too small after removing abnormal-volatility member ['SOXL']

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire basket was rejected.&lt;/p&gt;

&lt;p&gt;That is a reasonable trade-off. The expected damage from a structurally contaminated basket can be larger than the opportunity cost of losing one candidate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 2: Earnings Gaps Look Exactly Like “Misalignment”&lt;/strong&gt;&lt;br&gt;
U.S. companies often release earnings after the cash market closes, while these perpetual contracts trade around the clock.&lt;/p&gt;

&lt;p&gt;At that moment, the rest of a sector may remain nearly unchanged while one instrument jumps 8%.&lt;/p&gt;

&lt;p&gt;To the Q-score and leave-one-out attribution, this can look identical to a textbook relative-value opportunity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;basket residual surges;&lt;/li&gt;
&lt;li&gt;one instrument's z-score exceeds 6;&lt;/li&gt;
&lt;li&gt;the remaining legs barely move.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the move may represent permanent repricing. The price does not need to return.&lt;/p&gt;

&lt;p&gt;The distinction is not necessarily the size of the move, but its speed. A liquidity imbalance may widen progressively, whereas an event-driven repricing may occur in one step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if q - prev["q"] &amp;gt; model["q_entry"] * JUMP_MULT:
    # one-cycle jump exceeds 60% of the alert threshold
    jump_until = now + JUMP_COOL_S    # four-hour cooldown

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This filter is imperfect. It will reject some genuine fast dislocations.&lt;/p&gt;

&lt;p&gt;But the cost of missing one opportunity is not comparable to the cost of repeatedly fading a permanent earnings repricing. Under that asymmetry, a conservative filter is rational.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 3: Intraday Seasonality Requires More Than One Scale&lt;/strong&gt;&lt;br&gt;
The perpetual contracts trade 24 hours a day, but the underlying U.S. equities have a defined cash session.&lt;/p&gt;

&lt;p&gt;Residual volatility during the U.S. trading session can differ by an order of magnitude from volatility during quiet Asian hours.&lt;/p&gt;

&lt;p&gt;If one threshold is estimated from the combined sample:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it may be too loose during the active session and miss genuine dislocations;&lt;/li&gt;
&lt;li&gt;it may be too tight overnight and create a large number of false alerts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simply discarding off-hours data is tempting, but it removes some of the most informative observations. Earnings releases often occur outside the cash session, producing the pure pattern of “one leg jumps while the rest remain still.”&lt;/p&gt;

&lt;p&gt;The implementation gives each intraday slot its own scale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q_train = np.einsum("ij,jk,ik-&amp;gt;i", Rn, resid_inv, Rn)
slots = ((ts[-eff:] // 3600000) % VOL_SLOTS).astype(int)

for h in range(VOL_SLOTS):
    sel = Q_train[slots == h]

    if len(sel) &amp;gt;= MIN_SLOT_BARS:
        slot_scale[str(h)] = float(np.median(sel)) / q_med_all

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The effect appears directly in the monitoring panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q (seasonally normalized)  10.17
Q (raw)                    14.78
slot factor                 1.453

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw score was 14.78, but baseline volatility for that time slot was already about 45% higher. After normalization, the score fell to 10.17 and remained below the 18.55 alert line.&lt;/p&gt;

&lt;p&gt;Without this adjustment, the same false signal could recur every day.&lt;/p&gt;

&lt;p&gt;This median rescaling improves comparability, but it does not preserve an exact chi-square distribution. The final threshold should therefore be validated against empirical false-alarm frequencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. In-Sample Mean Reversion Can Contain Almost No Information
&lt;/h2&gt;

&lt;p&gt;This point deserves to be stated directly:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A beautiful in-sample mean-reversion chart is not evidence that the relationship will mean-revert in the future.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When a residual or z-score is centered and standardized using the same sample, its in-sample mean is forced to zero by construction. That is an algebraic property, not a discovered trading edge.&lt;/p&gt;

&lt;p&gt;Even unrelated integrated series can produce attractive fitted residual charts over a selected window.&lt;/p&gt;

&lt;p&gt;The only informative test is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Freeze the parameters estimated from the training window, then observe whether future dislocations actually return.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For that reason, the system places forward validation statistics at the top of the dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def validation_stats(store):
    done = [
        a for a in store["alerts"]
        if a.get("resolved") is not None
    ]
    ok = [a for a in done if a["resolved"]]

    return {
        "rate": len(ok) / len(done),  # did Q actually return after the alert?
        "median_min": times[len(times) // 2],
        "excursion": np.mean([
            a["peak_q"] / a["q0"] for a in done
        ]),
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current implementation calls the final field mae, but it computes the mean peak-Q multiple across completed alerts, not the maximum adverse excursion. The label should be changed or the calculation should be changed.&lt;/p&gt;

&lt;p&gt;The three statistics answer different questions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F75sd2mr4ey9vybm4gpje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F75sd2mr4ey9vybm4gpje.png" alt=" " width="800" height="131"&gt;&lt;/a&gt;&lt;br&gt;
The last metric is easy to overlook.&lt;/p&gt;

&lt;p&gt;A strategy may show an 80% reversion rate but still experience excursions of three times the entry displacement. With excessive leverage, it can fail before convergence even when the long-run direction is correct.&lt;/p&gt;

&lt;p&gt;Being right and surviving are separate problems.&lt;/p&gt;

&lt;p&gt;No singular-value gap, ADF statistic, or attractive chart can override the forward table. Those are admission filters, not conclusions.&lt;/p&gt;

&lt;p&gt;Because the system screens many baskets and many residual directions, multiple-testing and selection bias also matter. Forward validation must use genuinely future observations, and the reported reversion rate should be accompanied by sample size and uncertainty intervals rather than treated as certain after only a small number of alerts.&lt;/p&gt;
&lt;h2&gt;
  
  
  10. The Last Mile: A Correct Signal Can Produce the Wrong Orders
&lt;/h2&gt;

&lt;p&gt;One non-mathematical detail can destroy the entire construction.&lt;/p&gt;

&lt;p&gt;Contracts trade in integer quantities.&lt;/p&gt;

&lt;p&gt;Suppose the target for one leg is 3.7 contracts. Applying floor produces 3 contracts, an error of about 19%. Repeat that across ten legs and the executable portfolio may no longer resemble the intended neutral portfolio.&lt;/p&gt;

&lt;p&gt;The consequence is worse than simply earning slightly less.&lt;/p&gt;

&lt;p&gt;Once the hedge ratios are distorted, directional market exposure remains. The strategy may appear to be betting that ARM will converge toward the basket while actually carrying a large semiconductor-sector bet.&lt;/p&gt;

&lt;p&gt;Before entry, the implementation measures the cosine similarity between target and executable signed notionals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tgt = np.array([w.get(sy, 0.0) for sy in w])

act = np.array([
    legs[sy]["notional"] * (1 if long else -1)
    ...
])

fidelity = float(
    tgt @ act / (norm(tgt) * norm(act))
)

if fidelity &amp;lt; MIN_HEDGE_FID:      # default: 0.97
    return                        # cannot match the hedge: do not enter

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This constraint can be inverted to estimate the minimum gross exposure required for the basket to be represented with sufficient accuracy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;needs.append(float(
    np.max(
        FID_TARGET_CT * gran[nz] * aw.sum() / aw[nz]
    )
))

min_gross = float(np.median(needs))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard can then show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BB136E8   minimum gross exposure $11,938   currently tradable ✅
B90094A   minimum gross exposure  $4,597   currently tradable ✅

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the account is too small, the basket should not be traded.&lt;/p&gt;

&lt;p&gt;Forcing the trade does not create “a smaller arbitrage.” It creates a directional position dominated by rounding error.&lt;/p&gt;

&lt;p&gt;Funding is another frequently omitted cost.&lt;/p&gt;

&lt;p&gt;Carry does not automatically cancel across long and short legs because the contracts may have different funding rates. TradFi perpetuals cannot necessarily be arbitraged directly against the underlying shares, so their basis may contain persistent structural differences.&lt;/p&gt;

&lt;p&gt;Funding is therefore a deterministic component of realized spread P&amp;amp;L and must be booked explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for sym, leg in pos["legs"].items():
    rate = (
        mk["fundingRate"]
        * dt
        / mk["fundingInterval"]
    )

    acc += (
        notional * rate
        if leg["side"] == "long"
        else -notional * rate
    )

pos["funding_paid"] += acc

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  11. The Complete Pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb1nmybs2a0o2rkik7ouo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb1nmybs2a0o2rkik7ouo.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4 | Screening, validation, monitoring, execution, and forward statistics form a closed loop. Every earlier filter is ultimately judged by out-of-sample results.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hundreds of contracts across the market
      ↓  Liquidity gate: open interest and long/short user count
      ↓  Exclude pre-IPO synthetic products
         (no external price anchor, so convergence has no firm basis)

Approximately 120 candidate instruments
      ↓  Hierarchical clustering
         distance = √(2(1−ρ))
         no manually supplied industry labels

Approximately 150 candidate baskets
      ↓  Remove potential leveraged duplicates
      ↓  Estimate factor count k with the MP rule
      ↓  Singular-value ratio ≥ 1.30
      ↓  First-half/second-half principal angle ≤ 20°
      ↓  ADF screening + OU half-life

Approximately 2–6 admitted baskets
      ↓  Real-time Q-score / F-shock monitoring
      ↓  Event-jump filter and intraday seasonal normalization
      ↓  Leave-one-out attribution
      ↓  Integer-contract rounding + hedge-fidelity check

Order construction
      ↓
Forward validation statistics  ← final judge

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A sample live output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID       N   k factors   r residual dirs   σ ratio   subspace angle
BB136E8  10  1           6                 4.476     11.96°

best ADF t   half-life   stability score
-5.704       17.8 bars   0.807

AMD, ARM, ASML, INTC, MRVL, NVDA, QQQ, SPY, TSM, ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The row means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the ten instruments were modeled as being driven largely by one common factor;&lt;/li&gt;
&lt;li&gt;six retained residual directions passed the implementation's stationarity screen;&lt;/li&gt;
&lt;li&gt;the singular-value ratio was about 4.5;&lt;/li&gt;
&lt;li&gt;the first-half/second-half residual spaces differed by about 12 degrees;&lt;/li&gt;
&lt;li&gt;the estimated displacement half-life was about 18 bars, or 4.5 hours on 15-minute data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the machine-generated analogue of the EWY triangle: a multi-leg relative-value basket with several residual directions, discovered without manually specifying the economic relationship in advance.&lt;/p&gt;

&lt;p&gt;Human choices still remain in universe construction, thresholds, statistical assumptions, execution constraints, and risk limits. The machine automates the search; it does not remove model risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Four Things That Must Be Stated Clearly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;First: There Is No Forced Convergence Mechanism&lt;/strong&gt;&lt;br&gt;
True arbitrage often has a physical or contractual convergence channel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an ETF can be created or redeemed;&lt;/li&gt;
&lt;li&gt;a futures contract expires or settles;&lt;/li&gt;
&lt;li&gt;a convertible claim has enforceable cash flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These TradFi perpetual contracts cannot necessarily be exchanged for the underlying U.S. shares.&lt;/p&gt;

&lt;p&gt;Their relative prices are constrained mainly by funding rates, market-maker inventory, and quoting conventions. That mechanism is much weaker than physical arbitrage.&lt;/p&gt;

&lt;p&gt;This is therefore statistical arbitrage. Convergence is probabilistic, not guaranteed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: Sample Length Is the Largest Weakness&lt;/strong&gt;&lt;br&gt;
Many of the contracts have short histories and may not have passed through a full earnings cycle.&lt;/p&gt;

&lt;p&gt;The current sample cannot establish whether the estimated relationships remain stable across earnings seasons, index rebalances, corporate actions, or changing liquidity regimes.&lt;/p&gt;

&lt;p&gt;Forward statistics must continue to accumulate. A few days of paper results are not enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third: Every Theorem and Filter Only Raises the Probability of Forward Validity&lt;/strong&gt;&lt;br&gt;
The singular-value separation, principal-angle screen, ADF test, OU half-life, and Marchenko–Pastur rule all serve one purpose: improve the chance that the structure survives out of sample.&lt;/p&gt;

&lt;p&gt;None provides a guarantee.&lt;/p&gt;

&lt;p&gt;They are necessary engineering filters, not sufficient evidence of profitability.&lt;/p&gt;

&lt;p&gt;Fourth: Large-Scale Screening Creates Multiple-Testing Risk&lt;br&gt;
The process evaluates many clusters, baskets, directions, thresholds, and attribution candidates.&lt;/p&gt;

&lt;p&gt;Even if every individual test uses a 5% significance level, the best-looking survivors can appear significant purely because so many alternatives were tried.&lt;/p&gt;

&lt;p&gt;The forward period must remain untouched, rolling evaluation must avoid leakage, and performance claims should include the number of candidates searched and the uncertainty around the observed reversion rate.&lt;/p&gt;

&lt;p&gt;The system ultimately reduces to one table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔬 Forward Validation

alerts   completed   reversion rate   median reversion time
1        0           —                —

peak adverse excursion   conclusion
—                        insufficient sample; keep collecting

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An operating rule might be:&lt;/p&gt;

&lt;p&gt;if the reversion rate remains above 60% with a sufficiently large sample and acceptable adverse excursion, the premise is worth further risk-budget discussion;&lt;br&gt;
if it remains below 50%, stop the strategy rather than continuously retuning parameters.&lt;br&gt;
The thresholds themselves are not universal truths. They should be interpreted together with sample size, confidence intervals, transaction costs, and drawdown.&lt;/p&gt;

&lt;p&gt;Using parameter changes to manufacture a better historical reversion rate is the definition of overfitting.&lt;/p&gt;

&lt;p&gt;Strategy Source&lt;br&gt;
TradFi Matrix Statistical Arbitrage&lt;/p&gt;

&lt;p&gt;The strategy is configured for Gate.io by default. Other exchanges may require symbol, metadata, fee, funding, and contract-quantity adaptations.&lt;/p&gt;

&lt;p&gt;The default mode is paper, so it does not place real orders.&lt;/p&gt;

&lt;p&gt;Accumulate enough out-of-sample evidence before discussing live capital.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Review Notes Before Live Use
&lt;/h2&gt;

&lt;p&gt;The published source should be corrected or strengthened in several places before live deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Recompute half-life only from the residual directions retained after ADF screening.&lt;/li&gt;
&lt;li&gt;Calculate the ADF pass ratio before overwriting r; otherwise pass_adf / r becomes 1 whenever any directions are retained.&lt;/li&gt;
&lt;li&gt;Rename the current mae field or calculate a true maximum adverse excursion; the code currently averages peak_q / q0.&lt;/li&gt;
&lt;li&gt;Test each leave-one-out trading residual for stationarity and verify executable factor exposure; OLS residual orthogonality alone does not ensure live factor neutrality.&lt;/li&gt;
&lt;li&gt;Treat chi-square thresholds as approximate and compare them with empirical forward false-alarm rates after covariance estimation and seasonal normalization.&lt;/li&gt;
&lt;li&gt;Correct the universe-size/member-list inconsistency in the example output.&lt;/li&gt;
&lt;li&gt;Apply multiple-testing controls or nested forward validation when selecting among many baskets and residual directions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article is for quantitative research and software-design discussion only. It is not investment advice.&lt;/p&gt;

</description>
      <category>statisticalarbitrage</category>
      <category>cryptoquant</category>
      <category>overfitting</category>
      <category>quanttrading</category>
    </item>
    <item>
      <title>Implementing the D-Man V3 Bollinger Band Reversal DCA Strategy in Rust on FMZ</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 26 Aug 2026 06:31:10 +0000</pubDate>
      <link>https://dev.to/quant001/implementing-the-d-man-v3-bollinger-band-reversal-dca-strategy-in-rust-on-fmz-5062</link>
      <guid>https://dev.to/quant001/implementing-the-d-man-v3-bollinger-band-reversal-dca-strategy-in-rust-on-fmz-5062</guid>
      <description>&lt;p&gt;This strategy is intended solely for quantitative research and software design discussion. It does not constitute investment advice. DCA can accumulate exposure during one-way markets, and stop-loss orders cannot eliminate the risks of gaps, liquidation, poor liquidity, or exchange failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Three Problems with Fixed Grids in Live Trading
&lt;/h2&gt;

&lt;p&gt;A fixed grid looks intuitive: add another layer whenever price falls by a fixed percentage, then close all layers together after a rebound. The problem is that market volatility is constantly changing, so the same spacing rarely remains effective for long.&lt;/p&gt;

&lt;p&gt;The first problem is distorted grid spacing. During a low-volatility phase, ETH may fail to move even 1% for several days, so a grid that is too wide may barely trade. During an event-driven market, however, price can move several percentage points within minutes, making the same grid far too dense and causing every layer to fill in a very short time.&lt;/p&gt;

&lt;p&gt;The second problem is that the “averaging rule” often has no explicit total budget. Some implementations define only how much larger each layer should be than the previous one, without locking in the maximum capital commitment before the cycle starts. During a continuous decline, the strategy may appear to be lowering its average entry price, while in reality it is continuously expanding an unaffordable tail-risk exposure.&lt;/p&gt;

&lt;p&gt;The third problem is that orders and positions can become disconnected. Partial fills, unconfirmed cancellations, process restarts, or manual intervention can leave old orders active on the exchange. If the program simply creates a new grid from the latest signal, it may produce ghost orders, duplicate exits, or accidentally take over an external position.&lt;/p&gt;

&lt;p&gt;The value of D-Man V3 is that it places all three problems inside a single cycle-management framework: Bollinger Bands define the current volatility scale, the DCA budget is fixed before entry, and all filled layers exit as one basket. The engineering implementation then uses order ownership and a persistent state machine to handle abnormal paths. This article presents a practical Rust implementation for FMZ.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Exactly Is Dynamic About D-Man V3?
&lt;/h2&gt;

&lt;p&gt;This implementation is based on Hummingbot’s publicly available directional_trading.dman_v3 framework. It is not simply a “buy more when price falls and sell when it rises” strategy. Instead, Bollinger Bands perform two separate tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bollinger Band percentage identifies whether price has entered a statistical extreme region.&lt;/li&gt;
&lt;li&gt;Bollinger Band width determines how far each DCA level should be placed from the anchor price.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means that grid spacing is no longer fixed. The grid contracts automatically in low-volatility conditions and expands in high-volatility conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Calculating the Bollinger Reversal Signal from Closed Candles Only
&lt;/h2&gt;

&lt;p&gt;Let the Bollinger middle, upper, and lower bands be Middle, Upper, and Lower:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BB% = (Close - Lower) / (Upper - Lower)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meaning of BB% is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 corresponds to the lower band.&lt;/li&gt;
&lt;li&gt;0.5 corresponds to the middle band.&lt;/li&gt;
&lt;li&gt;1 corresponds to the upper band.&lt;/li&gt;
&lt;li&gt;A value below 0 means the candle closed below the lower band.&lt;/li&gt;
&lt;li&gt;A value above 1 means the candle closed above the upper band.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical version exposes only BBLength in the interface. The remaining signal rules are fixed as tested internal constants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Interface parameter: BBLength = 100
Internal rules: BB_STD = 2.0, LONG_THRESHOLD = 0, SHORT_THRESHOLD = 1
Internal rule: REQUIRE_CROSS = true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FMZ’s GetRecords(symbol, period, limit) function can request candlestick data directly for a specified contract and timeframe. The Rust implementation uses the second-to-last candle as the current closed candle; the last candle, which is still forming, does not participate in signal generation.&lt;/p&gt;

&lt;p&gt;The signal function itself is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn detect_signal(
    current: &amp;amp;Bands,
    previous: &amp;amp;Bands,
    long_threshold: f64,
    short_threshold: f64,
    require_cross: bool,
) -&amp;gt; i32 {
    let mut long_now = current.bbp &amp;lt;= long_threshold;
    let mut short_now = current.bbp &amp;gt;= short_threshold;

    if require_cross {
        long_now = long_now &amp;amp;&amp;amp; previous.bbp &amp;gt; long_threshold;
        short_now = short_now &amp;amp;&amp;amp; previous.bbp &amp;lt; short_threshold;
    }

    if long_now &amp;amp;&amp;amp; !short_now {
        1
    } else if short_now &amp;amp;&amp;amp; !long_now {
        -1
    } else {
        0
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The “first crossing” rule is important. If price remains outside the lower band for several consecutive candles, a mean-reversion strategy should not start a new cycle on every candle. This filter cannot determine when a trend will end, but it can prevent repeated exposure from growing without limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Bollinger Half-Width Defines the DCA Grid Scale
&lt;/h2&gt;

&lt;p&gt;First calculate the raw Bollinger half-width as a percentage of the middle band, then clamp it to the dynamic scale actually used by the strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HalfWidthRatio = (Upper - Lower) / (2 × Middle)
ScaleRatio = clamp(HalfWidthRatio, 0.2%, 8%)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the latest traded price when the signal appears as the anchor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LongPrice[i]  = AnchorPrice × (1 - Factor[i] × ScaleRatio)
ShortPrice[i] = AnchorPrice × (1 + Factor[i] × ScaleRatio)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The baseline uses four distance factors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.05, 0.35, 0.75, 1.25

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose ETH is trading at 3,000 USDT and the Bollinger half-width is 2% when the signal appears. The approximate long-side plan is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7akoyvocloso5q6rz2mc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7akoyvocloso5q6rz2mc.png" alt=" " width="457" height="182"&gt;&lt;/a&gt;&lt;br&gt;
ScaleRatio corresponds to scale_ratio in the Rust source code. The upper and lower bounds serve two purposes. In a very narrow-band environment, several theoretical prices may collapse to the same value after tick-size quantization. In an extreme-volatility environment, the grid may otherwise expand so far that it loses practical execution value.&lt;/p&gt;

&lt;p&gt;Price quantization is not ordinary rounding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn round_price(runtime: &amp;amp;Runtime, price: f64, buy: bool) -&amp;gt; f64 {
    round_decimals(
        quantize(
            price,
            runtime.market.price_step,
            !buy, // round buy orders down and sell orders up
        ),
        decimal_places(
            runtime.market.price_step,
            runtime.market.price_precision,
        ),
    )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buy orders are rounded down and sell orders are rounded up, preventing quantization from pushing orders to a more aggressive price. Prices across all configured layers must remain strictly monotonic. If any two layers quantize to the same price, the entire cycle is rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A Budget Cap Matters More Than the “Averaging Multiplier”
&lt;/h2&gt;

&lt;p&gt;Dynamic DCA is often misunderstood as unlimited averaging. In this implementation, the maximum budget is fixed when the cycle is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CycleBudget = min(
    TotalCycleQuote,
    AccountEquity × MaxCycleEquityPct
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default capital weights are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1, 1, 1.5, 2.5

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four default layers therefore receive approximately 16.67%, 16.67%, 25%, and 41.66% of the cycle budget. Deeper levels receive more capital, but the strategy never adds temporary layers beyond the configured set, nor does it expand the total budget after a loss.&lt;/p&gt;

&lt;p&gt;For FMZ futures interfaces, amount usually represents contract quantity rather than a USDT notional value. A quote-currency budget therefore cannot be passed directly to the order function. The Rust implementation reads the following fields from GetMarkets():&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TickSize / AmountSize&lt;/li&gt;
&lt;li&gt;PricePrecision / AmountPrecision&lt;/li&gt;
&lt;li&gt;MinQty / MaxQty&lt;/li&gt;
&lt;li&gt;MinNotional / MaxNotional&lt;/li&gt;
&lt;li&gt;CtVal / CtValCcy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It then converts the quote-currency budget into a contract quantity.&lt;/p&gt;

&lt;p&gt;The core planning logic can be simplified as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let scale_ratio = clamp(
    bands.half_width_ratio,
    DCA_SCALE_MIN_PCT / 100.0,
    DCA_SCALE_MAX_PCT / 100.0,
);

let budget = TotalCycleQuote.min(
    equity * MaxCycleEquityPct / 100.0,
);

for (spread, weight) in spreads.iter().zip(weights.iter()) {
    let raw_price = if signal == 1 {
        anchor_price * (1.0 - spread * scale_ratio)
    } else {
        anchor_price * (1.0 + spread * scale_ratio)
    };

    let quote = budget * weight / weight_total;
    let amount = amount_from_quote(runtime, quote, raw_price)?;
    // Then validate price, quantity, and notional constraints.
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any single layer falls below the minimum quantity, exceeds the maximum quantity, or violates a notional constraint, the program rejects the entire cycle rather than leaving behind an incomplete plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Exit All Filled Layers as One Basket
&lt;/h2&gt;

&lt;p&gt;Not every DCA layer will necessarily fill. Managing take-profit separately for each layer can easily leave fragmented positions and isolated orders. This implementation instead uses the average position price returned by the exchange and merges all filled layers into a single basket.&lt;/p&gt;

&lt;p&gt;The dynamic exit distances use the same ScaleRatio fixed when the signal appeared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stop-loss distance          = 3.00 × ScaleRatio
Take-profit distance        = 1.00 × ScaleRatio
Trailing activation level   = 0.80 × ScaleRatio
Trailing pullback allowance = 0.25 × ScaleRatio

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy also supports the following exit conditions:&lt;/p&gt;

&lt;p&gt;Exit when price returns to the middle band and basket profit is no lower &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;than the internal constant MIN_PROFIT_PCT=0.15%.&lt;/li&gt;
&lt;li&gt;Exit after holding for more than MaxHoldingBars.&lt;/li&gt;
&lt;li&gt;After the internal trailing-profit activation level is reached, calculate the permitted pullback using ScaleRatio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Returns for long and short positions must be calculated separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn position_return(position: &amp;amp;PositionView, price: f64) -&amp;gt; f64 {
    if position.price &amp;lt;= 0.0 {
        0.0
    } else if position.side == 1 {
        price / position.price - 1.0
    } else {
        1.0 - price / position.price
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a short position also uses price / entry - 1, a price increase would incorrectly appear as a profit. Directional mistakes of this kind are well suited to being locked down with Rust unit tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. From Signal to Exit: Treat Each Trade as a Complete Cycle
&lt;/h2&gt;

&lt;p&gt;A reliable DCA strategy cannot treat “order submitted successfully” as its only state. Orders may be partially filled, requests may time out, and the strategy process may terminate after an order has already reached the exchange. The Rust implementation therefore models each reversal trade as a complete cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDLE
  -&amp;gt; PLACING
  -&amp;gt; ACTIVE
  -&amp;gt; CANCELING_FOR_EXIT
  -&amp;gt; SUBMITTING_EXIT
  -&amp;gt; EXITING
  -&amp;gt; IDLE

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before entry, the strategy first persists the PLACING state and the layer currently waiting to be submitted. Only after receiving a valid order ID is the order formally attached to the cycle. If a request has been sent but no order ID is returned, the strategy pauses instead of assuming failure and submitting the order again.&lt;/p&gt;

&lt;p&gt;The exit path also follows a strict sequence: stop increasing exposure, cancel the remaining DCA entry orders, confirm that cancellation has completed, and then submit one basket-closing order based on the actual position size returned by the exchange. The cycle ends only after the position has been confirmed as zero twice in succession.&lt;/p&gt;

&lt;p&gt;Order IDs are persisted together with the cycle. After a restart, the strategy restores order ownership before reconciling open orders and positions. If it finds an order that cannot be proven to belong to the current cycle, or if the actual position direction does not match the cycle direction, it enters HALT and waits for manual intervention. This conservative design sacrifices some automatic recovery capability in exchange for avoiding blind order resubmission.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The Core Parameters That Are Actually Worth Adjusting
&lt;/h2&gt;

&lt;p&gt;More parameters do not automatically make a strategy more professional. For this strategy, the settings that truly affect behavior can be grouped into four categories: signal speed, cycle budget, DCA shape, and exit scale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2igqppv7v8sjb2yddomx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2igqppv7v8sjb2yddomx.png" alt=" " width="798" height="173"&gt;&lt;/a&gt;&lt;br&gt;
Parameter tuning should respect these dependencies. SignalPeriodMinutes and BBLength jointly determine the real-time span covered by the statistical window. DCA distances and capital weights should be adjusted together. The fixed quote budget and equity percentage jointly determine the actual capital cap. Take-profit and stop-loss multipliers are both based on the same dynamic volatility scale.&lt;/p&gt;

&lt;p&gt;Rules such as the Bollinger standard deviation, first-crossing requirement, bandwidth filter, upper and lower DCA scale limits, order lifetime, cooldown period, and trailing-profit logic remain fixed. This removes a large number of difficult-to-explain parameter combinations. A rule should be exposed separately only when it is the explicit subject of a research experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Which Markets Suit It, and Which Markets Are Dangerous?
&lt;/h2&gt;

&lt;p&gt;D-Man V3 remains, at its core, a mean-reversion strategy. It is best suited to liquid markets where volatility repeatedly expands and contracts and price often returns from outside the bands toward the middle band. A dynamic grid can adapt to changes in volatility scale, but it cannot determine whether a breakout is false or the beginning of a new trend.&lt;/p&gt;

&lt;p&gt;The most dangerous environment is a persistent one-way market. As price moves farther away, deeper DCA layers bring the average entry closer to the market, but they also concentrate notional exposure at the point of greatest risk. A budget cap can limit the amount committed, but it cannot turn a negative-expectancy countertrend trade into a safe trade.&lt;/p&gt;

&lt;p&gt;The second source of risk is a sudden volatility regime shift. Bollinger half-width is calculated from the historical window around the signal, while an event-driven market may instantly move future volatility into an entirely different range. Even with a stop-loss, actual execution may deviate significantly because of slippage, liquidity, or exchange constraints.&lt;/p&gt;

&lt;p&gt;The third source of risk is cost erosion. Four entry layers, cancellations, and a basket exit all incur fees. Perpetual contracts may also incur funding costs. If the target profit per cycle is too small, execution costs can easily consume the gross profit.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. What Should a Backtest Actually Answer?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7lkrvh1cdcidrfx2jmes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7lkrvh1cdcidrfx2jmes.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
For a DCA strategy, final net profit alone is far from sufficient. A more useful approach is to split the backtest into independent cycles and observe how the strategy uses capital under different market regimes.&lt;/p&gt;

&lt;p&gt;At minimum, the following metrics should be recorded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximum capital committed and maximum margin usage per cycle.&lt;/li&gt;
&lt;li&gt;Trigger rate and fill contribution of each DCA layer.&lt;/li&gt;
&lt;li&gt;Maximum adverse excursion and final profit or loss for each cycle.&lt;/li&gt;
&lt;li&gt;Longest holding period and the proportion of time-based exits.&lt;/li&gt;
&lt;li&gt;Number of consecutive losing cycles.&lt;/li&gt;
&lt;li&gt;Fees, slippage, and funding costs as a percentage of gross profit.&lt;/li&gt;
&lt;li&gt;The contribution of stop-loss, take-profit, trailing-profit, middle-band, and time-based exits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sample should also be divided by market regime instead of testing only one long period:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6nkl7nq8h2oxlufefq2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6nkl7nq8h2oxlufefq2n.png" alt=" " width="673" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdekms3v9ldq5p7ga00ud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdekms3v9ldq5p7ga00ud.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
Parameter sensitivity analysis should come last. If only one highly specific parameter set is profitable while nearby values all fail, the result usually depends on sample coincidence rather than a stable mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Engineering Reliability Is Part of the Strategy
&lt;/h2&gt;

&lt;p&gt;The mean-reversion formula is short, but the live-trading path is long. Partial fills, failed order queries, cancellation delays, process restarts, and manual orders can all change the real risk. This strategy therefore enforces several boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage only one active cycle per symbol at a time.&lt;/li&gt;
&lt;li&gt;Do not automatically take over positions or open orders whose ownership cannot be proven.&lt;/li&gt;
&lt;li&gt;Do not blindly resend a request when its outcome is uncertain.&lt;/li&gt;
&lt;li&gt;Cancel all remaining entry orders before exiting.&lt;/li&gt;
&lt;li&gt;Close the actual position size rather than the planned quantity.&lt;/li&gt;
&lt;li&gt;Leverage and position mode must be defined in advance and should not be treated as strategy parameters to optimize.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These rules do not improve the signal win rate, but they reduce the gap between “the backtest formula is correct” and “live orders are out of control.” For a strategy that increases exposure layer by layer, this reliability is itself part of risk management.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Conclusion
&lt;/h2&gt;

&lt;p&gt;Strategy Source Code&lt;/p&gt;

&lt;p&gt;The core of D-Man V3 can be summarized in three sentences: BB% identifies statistical extremes, Bollinger half-width defines the DCA scale, and all filled layers exit as one basket based on the average position price.&lt;/p&gt;

&lt;p&gt;Compared with a fixed grid, it adapts better to changes in volatility and uses a cycle budget to prevent unlimited averaging. However, it still cannot eliminate the tail risk of mean reversion during a persistent trend. Whether the strategy can remain usable over the long term depends not only on the entry formula, but also on budget constraints, execution costs, state recovery, and conservative handling of abnormal orders.&lt;/p&gt;

&lt;p&gt;When evaluating this type of strategy, “how much it earned” should come only after “how much capital it used,” “how much adverse movement it endured,” and “under which market conditions it failed.” The value of dynamic DCA is not that it predicts every reversal, but that it makes the risk path as clear as possible both when the reversal hypothesis succeeds and when it fails.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F558ap8xnuzz7u58xt1ko.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F558ap8xnuzz7u58xt1ko.jpg" alt=" " width="800" height="465"&gt;&lt;/a&gt;&lt;br&gt;
Thank you for reading.&lt;/p&gt;

</description>
      <category>dmanv3</category>
      <category>gridtrading</category>
      <category>fmzquant</category>
      <category>cryptotradingbots</category>
    </item>
  </channel>
</rss>
