<?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>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>
    <item>
      <title>Updating the Model on Every Bar: Is It Adapting to the Market, or Chasing Noise? An FMZ Rust Online Learning Comparison</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 26 Aug 2026 06:01:02 +0000</pubDate>
      <link>https://dev.to/quant001/updating-the-model-on-every-bar-is-it-adapting-to-the-market-or-chasing-noise-an-fmz-rust-online-38ed</link>
      <guid>https://dev.to/quant001/updating-the-model-on-every-bar-is-it-adapting-to-the-market-or-chasing-noise-an-fmz-rust-online-38ed</guid>
      <description>&lt;h2&gt;
  
  
  A Comparative Online Learning Experiment Based on FMZ Rust
&lt;/h2&gt;

&lt;p&gt;Online learning is easily presented as a natural advantage in quantitative research: markets are non-stationary, so models should continuously absorb new data.&lt;/p&gt;

&lt;p&gt;That line of reasoning is only half complete.&lt;/p&gt;

&lt;p&gt;Faster model updates may indeed help a model move away from historical relationships that have already broken down. But the same update mechanism can also write short-term random fluctuations into the parameters more quickly. For a trading strategy, the latter does not merely appear as unstable predictions. It also turns into higher turnover, greater sensitivity to costs, and stronger path dependence.&lt;/p&gt;

&lt;p&gt;This article therefore does not attempt to prove that “online models are better than fixed models.” The experiment asks a more specific question:&lt;/p&gt;

&lt;p&gt;When the model structure, features, trading rules, and cost assumptions are held completely constant, how does changing the speed at which the model absorbs new samples affect out-of-sample loss, parameter stability, turnover, and simulated equity?&lt;/p&gt;

&lt;p&gt;To avoid mixing model differences with differences in the backtesting environment, four models run in parallel inside the same Rust strategy process. They read the same bars and use the same features and simulated execution rules. The strategy does not send orders to an exchange; it maintains four shadow accounts only.&lt;/p&gt;

&lt;p&gt;This article includes one actual backtest to verify whether the time alignment, label distribution, predictive loss, turnover, and trading costs behave as expected. However, a single backtest is not treated as a general conclusion. The main value of the strategy is still to establish a reproducible comparison framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Research Question: Update Speed Is Itself a Source of Model Risk
&lt;/h2&gt;

&lt;p&gt;Online learning usually follows the sequence “predict first, observe the label, then update.” When sample &lt;br&gt;
 arrives, the model first makes a prediction using its current parameters. After the true outcome becomes available, the loss is calculated and the parameters are updated.&lt;/p&gt;

&lt;p&gt;The advantage of this approach is that new information can be absorbed incrementally without repeatedly saving and retraining on the entire history. The problem is that every new sample immediately affects the parameters.&lt;/p&gt;

&lt;p&gt;The update mechanism can be understood as a continuous speed axis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Never update
  ── Periodic rolling retraining
  ── Update after loss deterioration
  ── Update after every bar
  ── Update after every tick

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

&lt;/div&gt;



&lt;p&gt;The farther left a model sits, the more stable its parameters are, but the slower it responds to structural change. The farther right it sits, the more flexible it becomes—and the more easily it can be pushed around by short-term noise.&lt;/p&gt;

&lt;p&gt;The experiment therefore proposes three hypotheses to test in advance, rather than assuming an answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Per-bar online updates may shorten the model’s recovery time after a regime change.&lt;/li&gt;
&lt;li&gt;The same mechanism may also amplify parameter fluctuations, signal reversals, and trading costs.&lt;/li&gt;
&lt;li&gt;Model performance may not improve monotonically with update frequency; an intermediate speed may be more stable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Experimental Design: Change Only How the Model Absorbs New Samples
&lt;/h2&gt;

&lt;p&gt;All four models share the same initialized parameters.&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%2Fwtppkliomfefojr26uvi.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%2Fwtppkliomfefojr26uvi.png" alt=" " width="799" height="122"&gt;&lt;/a&gt;&lt;br&gt;
The fourth group can only be called &lt;strong&gt;an error-gated update&lt;/strong&gt; mechanism. It cannot be described as a strict concept-drift detector.&lt;/p&gt;

&lt;p&gt;An increase in predictive loss may result from a change in the conditional distribution, but it may also come from higher volatility, outliers, label randomness, or poor model calibration. This article uses loss deterioration only as an engineering trigger. It does not infer from that trigger alone that concept drift has occurred in the market.&lt;/p&gt;

&lt;p&gt;To keep the comparison as clean as possible, all four models use exactly the same:&lt;/p&gt;

&lt;p&gt;initial training samples;&lt;br&gt;
features and standardization parameters;&lt;br&gt;
logistic-regression structure;&lt;br&gt;
long and short probability thresholds;&lt;br&gt;
return calculation method;&lt;br&gt;
one-way turnover-cost assumption.&lt;br&gt;
The four simulated accounts are also settled synchronously on the same bar. As a result, differences in the outcomes mainly come from the update mechanism rather than from data segmentation, API timing, or backtest-matching differences.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Time Alignment: Resolve Look-Ahead Bias and Execution Assumptions First
&lt;/h2&gt;

&lt;p&gt;The easiest place to make a mistake in an online model is not the gradient formula, but sample timing.&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%2Fkdvbmuq9f7nz8dm4ylc7.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%2Fkdvbmuq9f7nz8dm4ylc7.png" alt=" " width="800" height="221"&gt;&lt;/a&gt;&lt;br&gt;
Strictly speaking, class 0 means non-up, because a bar with an opening price equal to its closing price is also encoded as 0.&lt;/p&gt;

&lt;p&gt;The label function 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 make_label(
    bars: &amp;amp;[Bar],
    feature_index: usize,
) -&amp;gt; f64 {
    let next_bar =
        bars[feature_index + 1];

    if next_bar.close &amp;gt; next_bar.open {
        1.0
    } else {
        0.0
    }
}

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

&lt;/div&gt;



&lt;p&gt;The 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;Bar t closes
        ↓
Calculate x_t and predict the direction of bar t+1
        ↓
Assume a position is established near the open of bar t+1
        ↓
Bar t+1 closes, producing the label and open-to-close return
        ↓
Evaluate the original prediction first, then allow the model to update

&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%2Fgq2y2j8p38pfyjzsn1za.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%2Fgq2y2j8p38pfyjzsn1za.png" alt=" " width="800" height="222"&gt;&lt;/a&gt;&lt;br&gt;
This is still a simplified model. A real order may not fill at the opening price of the next bar, and order-book impact, latency, and slippage will not remain fixed. The cost in the code is only a standardized stress-test assumption and does not represent the actual fee schedule of any exchange.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.1 Identifying Completed Bars Separately in Backtesting and Live Trading&lt;/strong&gt;&lt;br&gt;
The first version of the strategy used the current time in every environment to determine whether a bar had finished. In the actual backtest, this caused a still-forming bar to be treated prematurely as a completed sample. A large number of bars therefore appeared with Open == Close, and model accuracy became abnormally close to 100%.&lt;/p&gt;

&lt;p&gt;The corrected version uses IsVirtual() to distinguish between runtime environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In backtesting, it conservatively uses the second-to-last bar in the market-data array.&lt;/li&gt;
&lt;li&gt;In live trading, it checks whether the start time of the last bar plus the bar duration is no later than the current time.&lt;/li&gt;
&lt;li&gt;If the final bar returned by the live API has not finished, the function falls back to the second-to-last bar.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn latest_closed_index(
    bars: &amp;amp;[Bar],
    is_backtest: bool,
    bar_seconds: i64,
) -&amp;gt; Option&amp;lt;usize&amp;gt; {
    if bars.len() &amp;lt; 2 {
        return None;
    }

    if is_backtest {
        return Some(bars.len() - 2);
    }

    let last_index = bars.len() - 1;
    let period_ms = bar_seconds * 1000;
    let current_time_ms =
        (UnixNano() / 1_000_000) as i64;

    if bars[last_index].time + period_ms
        &amp;lt;= current_time_ms
    {
        Some(last_index)
    } else {
        Some(last_index - 1)
    }
}

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

&lt;/div&gt;


&lt;p&gt;The cost of processing one fewer bar is far smaller than the cost of mistakenly treating incomplete data as a label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.2 Test-Then-Train&lt;/strong&gt;&lt;br&gt;
Whenever a new label appears, the processing order is strictly maintained as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the previously stored prediction to calculate accuracy and Log Loss.&lt;/li&gt;
&lt;li&gt;Use the previously determined position to calculate the return on the current bar.&lt;/li&gt;
&lt;li&gt;Add the current sample to the training data.&lt;/li&gt;
&lt;li&gt;Update each model according to its own rule.&lt;/li&gt;
&lt;li&gt;Use the current completed bar to generate the prediction for the next bar.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In other words, test first and train afterward. The current sample is not fed into the model before the same sample is used to evaluate it.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Model: Simple Logistic Regression Is Enough for This Experiment
&lt;/h2&gt;

&lt;p&gt;This article does not use a neural network or a tree model. It uses logistic regression with L2 regularization:&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%2Fg1akc54xzzu13lbvr38o.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%2Fg1akc54xzzu13lbvr38o.png" alt=" " width="708" height="249"&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%2Farypa2bhqt6ovs1pe4v4.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%2Farypa2bhqt6ovs1pe4v4.png" alt=" " width="800" height="265"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn predict(
    &amp;amp;self,
    features: &amp;amp;[f64; N_FEATURES],
) -&amp;gt; f64 {
    let mut linear_value = self.bias;

    for j in 0..N_FEATURES {
        linear_value +=
            self.weights[j] * features[j];
    }

    Self::sigmoid(linear_value)
}

fn update(
    &amp;amp;mut self,
    features: &amp;amp;[f64; N_FEATURES],
    label: f64,
    learning_rate_scale: f64,
) {
    let probability = self.predict(features);
    let error = probability - label;
    let step =
        self.learning_rate
            * learning_rate_scale;

    for j in 0..N_FEATURES {
        let gradient = clip(
            error * features[j]
                + self.l2_penalty
                    * self.weights[j],
            -5.0,
            5.0,
        );
        self.weights[j] -= step * gradient;
    }

    self.bias -=
        step * clip(error, -1.0, 1.0);

    self.updates += 1;
}

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

&lt;/div&gt;



&lt;p&gt;Choosing a simple model does not imply that logistic regression is sufficient to describe the market. The purpose is to reduce confounding factors in the experiment.&lt;/p&gt;

&lt;p&gt;If the network structure, training epochs, number of features, and update frequency are all changed at the same time, it becomes difficult to determine where the difference in returns comes from. Parameter changes in a linear model can be measured directly, making it easier to observe whether “model adaptation” is accompanied by excessive weight drift.&lt;/p&gt;

&lt;p&gt;The code also clips individual gradients and standardized features. The purpose is not to improve returns, but to prevent an extreme bar from pushing the parameters too far in a single update.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Features: Retain Only Basic Price-Structure and Volume Information
&lt;/h2&gt;

&lt;p&gt;The model uses eight features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;one-bar log return;&lt;/li&gt;
&lt;li&gt;log return of the candle body;&lt;/li&gt;
&lt;li&gt;log range between the high and low;&lt;/li&gt;
&lt;li&gt;relative upper-wick length;&lt;/li&gt;
&lt;li&gt;relative lower-wick length;&lt;/li&gt;
&lt;li&gt;log change in volume;&lt;/li&gt;
&lt;li&gt;three-bar momentum;&lt;/li&gt;
&lt;li&gt;realized volatility of returns over the most recent five bars.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No derived indicators such as RSI, MACD, or moving-average crossovers are included, and the model does not use dozens of lagged terms.&lt;/p&gt;

&lt;p&gt;The main part of the feature calculation is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn raw_features(
    bars: &amp;amp;[Bar],
    index: usize,
) -&amp;gt; [f64; N_FEATURES] {
    let current = bars[index];
    let previous = bars[index - 1];
    let epsilon = 1e-12;

    let log_return_1 =
        (current.close.max(epsilon)
            / previous.close.max(epsilon))
            .ln();

    let candle_body =
        (current.close.max(epsilon)
            / current.open.max(epsilon))
            .ln();

    let range =
        (current.high.max(epsilon)
            / current.low.max(epsilon))
            .ln();

    let upper_wick =
        (current.high
            - current.open.max(current.close))
            .max(0.0)
            / current.open.max(epsilon);

    let lower_wick =
        (current.open.min(current.close)
            - current.low)
            .max(0.0)
            / current.open.max(epsilon);

    let volume_change =
        ((current.volume + epsilon)
            / (previous.volume + epsilon))
            .ln();

    let momentum_3 =
        (current.close.max(epsilon)
            / bars[index - 3]
                .close
                .max(epsilon))
            .ln();

    // Realized volatility of returns over the most recent five bars
    // The full implementation continues the calculation over the same window.

    [
        log_return_1,
        candle_body,
        range,
        upper_wick,
        lower_wick,
        volume_change,
        momentum_3,
        realized_volatility,
    ]
}

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

&lt;/div&gt;



&lt;p&gt;The initial samples are divided chronologically into the first 80% and the final 20%. The standardizer fits its means and standard deviations using only the first 80% of the features and then remains fixed. All four models always use the same standardizer.&lt;/p&gt;

&lt;p&gt;The standardizer is deliberately not updated online. Otherwise, the per-bar model would receive not only parameter updates but also an additional layer of feature-distribution adaptation, and the comparison would no longer isolate a single variable.&lt;/p&gt;

&lt;p&gt;A fixed standardizer helps keep the four models comparable, but it may also produce scale mismatch when the feature distribution changes over a long period. Therefore, the results of this experiment contain the combined effects of model-relationship failure and fixed-standardization scale mismatch. The two effects are not separately identified.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Initialization: All Four Models Must Start from the Same Line
&lt;/h2&gt;

&lt;p&gt;By default, the code prepares 320 initial supervised samples.&lt;/p&gt;

&lt;p&gt;Initialization is performed in two stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run six epochs of batch training on the first 80% of samples.&lt;/li&gt;
&lt;li&gt;Process the final 20% one by one using “predict—record loss—update.”
The average pre-update Log Loss produced by the second stage is used as the reference loss for the error-gated model. Only after this segment has been processed is the resulting model used as the shared initial parameter state for all four models.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn fit_initial_model(
    samples: &amp;amp;[Sample],
    split_index: usize,
    config: &amp;amp;Config,
) -&amp;gt; (LogisticModel, f64) {
    let mut model =
        train_model(
            &amp;amp;samples[..split_index],
            config.initial_epochs,
            config,
        );

    let mut loss_sum = 0.0;
    let mut count = 0usize;

    for sample in &amp;amp;samples[split_index..] {
        let probability =
            model.predict(&amp;amp;sample.x);

        loss_sum +=
            log_loss(
                probability,
                sample.y,
            );

        count += 1;

        model.update(
            &amp;amp;sample.x,
            sample.y,
            1.0,
        );
    }

    let baseline_loss =
        if count == 0 {
            0.69314718056
        } else {
            loss_sum / count as f64
        };

    (model, baseline_loss)
}

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

&lt;/div&gt;



&lt;p&gt;This treatment resolves two problems.&lt;/p&gt;

&lt;p&gt;First, the reference loss is not an in-sample retrospective loss calculated on data the model has already trained on. It is a pre-update loss obtained in chronological order.&lt;/p&gt;

&lt;p&gt;Second, all four models begin from the same parameter state, which has already absorbed all initialization samples. This prevents a situation in which the fixed and online models use different initial training data while later differences are still attributed to the update method.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Four Update Mechanisms
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;7.1 Fixed Model&lt;/strong&gt;&lt;br&gt;
The fixed model no longer modifies its weights after initialization.&lt;/p&gt;

&lt;p&gt;It provides the benchmark. If subsequent performance continues to deteriorate, the relationship learned during initialization cannot be stably extrapolated. If the fixed model is instead the most stable, the additional updates have not provided enough incremental information.&lt;/p&gt;

&lt;p&gt;It is important to note that a fixed model only means its weights and bias are no longer updated. It does not mean the predicted probability or position remains unchanged. Input features change on every bar, so fixed parameters can still generate frequent turnover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.2 Per-Bar Online Model&lt;/strong&gt;&lt;br&gt;
One gradient update is performed after each label becomes available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;online_model.update(
    &amp;amp;previous_features,
    label,
    1.0,
);

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

&lt;/div&gt;



&lt;p&gt;This model reacts fastest to a new environment, but it is also the most likely to interpret a short run of accidental samples as a structural change.&lt;/p&gt;

&lt;p&gt;The learning rate is the most important risk parameter for this model. The larger the learning rate, the stronger the influence of a new sample on the parameters. This article uses the interface default of 0.02 and performs no in-sample optimization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.3 Rolling-Retraining Model&lt;/strong&gt;&lt;br&gt;
Under the default settings, the rolling model stores the most recent 240 samples. After every 32 new samples, its parameters are reset and the model is retrained from scratch on the samples in the window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if processed_bars
    % config.retrain_every
    == 0
{
    periodic_model =
        retrain_from_window(
            &amp;amp;rolling_samples,
            &amp;amp;config,
        );

    periodic_retrains += 1;
}

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

&lt;/div&gt;



&lt;p&gt;This method has two characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Old samples are explicitly removed from the training set.&lt;/li&gt;
&lt;li&gt;A single new sample does not immediately change the model. Instead, it takes effect together with a batch of recent samples at the next retraining point.
Its adaptation is delayed, but parameter changes are usually not driven entirely by the final observation in the same way as per-bar updates. Both the rolling window and retraining interval can be adjusted in the strategy interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7.4 Error-Gated Model&lt;/strong&gt;&lt;br&gt;
The gated model maintains an exponential moving average of its own predictive Log Loss:&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%2Fllofxzn4c6iofvdxaip1.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%2Fllofxzn4c6iofvdxaip1.png" alt=" " width="800" height="247"&gt;&lt;/a&gt;&lt;br&gt;
Only when recent loss exceeds the initialization reference loss by a specified proportion, and the model is not in a cooldown period, does it update at a low learning rate using a small batch of the most recent samples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let current_gated_loss =
    log_loss(
        gated_old_probability,
        label,
    );

gated_loss_ema =
    (1.0 - config.loss_ema_alpha)
        * gated_loss_ema
        + config.loss_ema_alpha
            * current_gated_loss;

let gate_threshold =
    baseline_loss
        * config.gate_loss_multiplier;

if gate_cooldown_left == 0
    &amp;amp;&amp;amp; gated_loss_ema &amp;gt; gate_threshold
{
    let batch_start =
        rolling_samples
            .len()
            .saturating_sub(
                config.gate_batch
            );

    for (sample_index, sample)
        in rolling_samples
            .iter()
            .enumerate()
    {
        if sample_index &amp;gt;= batch_start {
            gated_model.update(
                &amp;amp;sample.x,
                sample.y,
                config.gated_lr_scale,
            );
        }
    }

    gate_events += 1;
    gate_cooldown_left =
        config.gate_cooldown;
}

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

&lt;/div&gt;



&lt;p&gt;The interface defaults are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loss threshold: 1.10 times the reference loss.&lt;/li&gt;
&lt;li&gt;Update batch: the most recent 32 samples.&lt;/li&gt;
&lt;li&gt;Learning-rate scale: 0.25 times the base learning rate.&lt;/li&gt;
&lt;li&gt;Post-update cooldown: 32 bars.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These values are only unoptimized experimental defaults. Their purpose is to define a repeatable gating rule, not to represent statistically optimal thresholds.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Trading Mapping and Costs
&lt;/h2&gt;

&lt;p&gt;Model probabilities are mapped into three positions:&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_from_probability(
    probability: f64,
    config: &amp;amp;Config,
) -&amp;gt; i32 {
    if probability &amp;gt;= config.long_threshold {
        1
    } else if probability
        &amp;lt;= config.short_threshold
    {
        -1
    } else {
        0
    }
}

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

&lt;/div&gt;



&lt;p&gt;The default rule is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p &amp;gt;= 0.55: long
p &amp;lt;= 0.45: short
otherwise: flat

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

&lt;/div&gt;



&lt;p&gt;The neutral range from 0.45 to 0.55 is intended to reduce the conversion of small probability fluctuations around 0.5 directly into trades.&lt;/p&gt;

&lt;p&gt;The position-change amount is defined 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%2F198034zq4jqf7i94kfn0.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%2F198034zq4jqf7i94kfn0.png" alt=" " width="799" height="160"&gt;&lt;/a&gt;&lt;br&gt;
Therefore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flat to long has turnover of 1;&lt;/li&gt;
&lt;li&gt;long to flat has turnover of 1;&lt;/li&gt;
&lt;li&gt;a direct flip from long to short has turnover of 2.
The cost is configured as an interface parameter rather than a hard-coded constant:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let cost_bps = float_parameter(
    "CostBps",
    CostBps,
    0.0,
    100.0,
    true,
)?;

cost_per_turnover:
    cost_bps / 10_000.0,

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

&lt;/div&gt;


&lt;p&gt;CostBps defaults to 5, meaning that 5 basis points are deducted for each unit of one-way turnover. This is an experimental parameter that can be adjusted in the strategy interface. It does not represent the actual fee rate of any specific exchange.&lt;/p&gt;
&lt;h2&gt;
  
  
  9. Evaluation Metrics: Do Not Look Only at Accuracy
&lt;/h2&gt;

&lt;p&gt;All four models record the following metrics:&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%2Ft5nqeo17tl0paic2efy2.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%2Ft5nqeo17tl0paic2efy2.png" alt=" " width="800" height="309"&gt;&lt;/a&gt;&lt;br&gt;
Parameter displacement is defined as the Euclidean distance of the differences in the weights and bias. Because the features use the same fixed standardizer, the four models can be compared on a relative basis.&lt;/p&gt;

&lt;p&gt;A large parameter displacement is not an error by itself. If the market structure truly changes, the model needs to move. The combination that should raise concern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parameter displacement rises rapidly
+ Turnover increases materially
+ Log Loss does not improve
+ Net equity after costs deteriorates

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

&lt;/div&gt;



&lt;p&gt;This looks more like the model is chasing recent samples than acquiring stable adaptive ability.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. One Actual Backtest Under the Default Parameters
&lt;/h2&gt;

&lt;p&gt;After correcting the completed-bar logic, a baseline backtest was run under the following conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instrument: ETH_USDT.swap
Timeframe: 15 minutes
Backtest period: 2025-07-09 to 2026-06-21
Initialization samples: 320
Out-of-sample bars: 33333
Base learning rate: 0.02
Rolling window: 240
One-way turnover cost: 5 bps

&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%2Fuuo723721g041w1flvbi.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%2Fuuo723721g041w1flvbi.png" alt=" " width="799" height="266"&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%2Faceaz8q8oxmo9a72sa2x.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%2Faceaz8q8oxmo9a72sa2x.png" alt=" " width="799" height="266"&gt;&lt;/a&gt;&lt;br&gt;
In the out-of-sample statistics, there were 16,644 up labels and 16,689 non-up labels. Among them, 36 bars had identical opening and closing prices. The status panel currently displays the latter category as “down,” but the actual code definition is Close &amp;lt;= Open; the remainder of this article therefore uses the term non-up.&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%2F88u2i5v2mgxnjbofge0c.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%2F88u2i5v2mgxnjbofge0c.png" alt=" " width="800" height="143"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;10.1 The Time-Alignment Problem Has Been Corrected&lt;/strong&gt;&lt;br&gt;
The up and non-up labels are nearly balanced, and model accuracy has returned to 49%–52%. The initialization reference Log Loss is 0.6988. The earlier accuracy close to 100% did indeed come from processing incomplete bars prematurely, not from the model suddenly acquiring extremely strong predictive ability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10.2 More Frequent Updates Did Not Improve Out-of-Sample Probability Forecasts&lt;/strong&gt;&lt;br&gt;
For a naive probability model that always outputs 0.5, the Log Loss on every sample is approximately 0.6931.&lt;/p&gt;

&lt;p&gt;In this backtest, the average Log Loss of all four models was above that benchmark. The error-gated model was closest to 0.6931, but it still cannot be said that the model had acquired effective predictive ability.&lt;/p&gt;

&lt;p&gt;Both per-bar online updating and rolling retraining produced clear parameter displacement, but their Log Loss rose to 0.7203 and 0.7301 respectively, both worse than the fixed model. At least for the current instrument, timeframe, features, and default parameters, more frequent model updates did not improve out-of-sample probability forecasts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10.3 The Gating Mechanism Reduced Noise Reactions but Did Not Create a Trading Edge&lt;/strong&gt;&lt;br&gt;
The error-gated model triggered only eight updates. Its cumulative turnover fell to 6,190, and it recorded only 142 direct long-short flips, both markedly lower than the other three models.&lt;/p&gt;

&lt;p&gt;This shows that the gating mechanism at least achieved its engineering objective: reducing the frequency with which the model changed its mind because of short-term errors.&lt;/p&gt;

&lt;p&gt;However, its post-cost equity still fell by approximately 98%. The correct conclusion is not that “error gating has already become effective,” but rather:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Controlled updating is more stable than unconditional updating, but stability itself has not yet translated into a trading edge.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The final status display of “update threshold not reached” only means that the loss EMA was below the threshold at the end of the backtest. It does not contradict the fact that eight updates were triggered earlier in the historical path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10.4 A Fixed Model Can Still Generate Frequent Turnover Without Updating Its Parameters&lt;/strong&gt;&lt;br&gt;
The fixed model’s parameter displacement is 0, yet its cumulative turnover is the highest of all four models.&lt;/p&gt;

&lt;p&gt;There is no contradiction. A fixed model only means that the weights and bias no longer change. The input features &amp;nbsp;x_t are still different on every bar. Fixed parameters combined with changing inputs can still produce changing probabilities and positions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10.5 Once Equity Approaches Zero, Return Metrics Become Saturated&lt;/strong&gt;&lt;br&gt;
All four models suffered severe losses under a 5 bps one-way cost. The primary problem was not a single large loss, but the accumulation of substantial turnover across more than 30,000 bars.&lt;/p&gt;

&lt;p&gt;Once equity approaches zero, net return and maximum drawdown both approach their limiting values, making it difficult to continue distinguishing the models economically.&lt;/p&gt;

&lt;p&gt;A later version should at least add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gross return before costs;&lt;/li&gt;
&lt;li&gt;cumulative costs;&lt;/li&gt;
&lt;li&gt;net log return;&lt;/li&gt;
&lt;li&gt;turnover per 1,000 bars;&lt;/li&gt;
&lt;li&gt;multiple cost scenarios at 0, 2, 5, and 10 bps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These results cannot prove that online learning is ineffective in other markets. They only show that, under the conditions of this experiment, updating the model more frequently did not demonstrate stronger market adaptation. Controlled updating reduced reactions to noise, but the added stability did not translate into a trading edge.&lt;/p&gt;
&lt;h2&gt;
  
  
  11. Complete Rust Implementation
&lt;/h2&gt;

&lt;p&gt;The complete strategy only reads bars and maintains four models and four simulated accounts in memory. It contains no real order-placement logic.&lt;/p&gt;

&lt;p&gt;Strategy Code: Online Bar-by-Bar Learning Comparison Experiment (Rust)&lt;/p&gt;
&lt;h2&gt;
  
  
  12. How to Tell Whether a Model Is Adapting or Chasing Noise
&lt;/h2&gt;

&lt;p&gt;The online model having the highest equity in a single backtest does not directly prove that online updating is effective. More informative evidence comes from the relationship between metrics and from whether the conclusion can be repeated across different periods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 1: Log Loss Improves Without a Material Increase in Turnover&lt;/strong&gt;&lt;br&gt;
If the per-bar model reduces Log Loss relatively quickly after a regime change, while parameter displacement stabilizes and cumulative turnover remains close to that of the fixed model, this is stronger evidence of adaptation.&lt;/p&gt;

&lt;p&gt;The important point is not that “the parameters moved,” but that predictive quality improved persistently after they moved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 2: Accuracy Improves, but Log Loss and Equity Deteriorate&lt;/strong&gt;&lt;br&gt;
Accuracy only considers whether the predicted probability crosses 0.5. It does not account for how confident the model is.&lt;/p&gt;

&lt;p&gt;A model may increase a probability from 0.51 to 0.90 while remaining directionally correct. But once the prediction is wrong, Log Loss deteriorates significantly. In trading, this kind of overconfidence is also more likely to push a signal across the long and short thresholds.&lt;/p&gt;

&lt;p&gt;Therefore, it is not contradictory for the online model to have slightly higher accuracy while showing worse Log Loss, drawdown, and post-cost returns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 3: An Advantage Exists at Zero Cost but Disappears After Costs Are Added&lt;/strong&gt;&lt;br&gt;
This usually means that online updating generated more marginal signals, but the edge per trade was not large enough to cover trading frictions.&lt;/p&gt;

&lt;p&gt;The model may indeed have captured a small amount of short-term predictability, but that predictability did not reach tradable strength. An improvement in gross return cannot be equated with an effective strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 4: Rolling Retraining or Gated Updating Is More Stable&lt;/strong&gt;&lt;br&gt;
If these two groups consistently show lower Log Loss, smaller drawdown, and lower turnover across multiple instruments and timeframes, the useful adaptation speed may lie somewhere between “never update” and “update after every bar.”&lt;/p&gt;

&lt;p&gt;It is still important to remember that the rolling window, retraining interval, and gating threshold are themselves hyperparameters. Only the best-performing setting cannot be retained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 5: The Fixed Model Performs Best&lt;/strong&gt;&lt;br&gt;
This does not mean the market is stationary. It only means that, for the current features, model, and sample period, new samples did not provide sufficiently stable information, or the online update rule did not extract that information correctly.&lt;/p&gt;

&lt;p&gt;Stable fixed-model parameters do not imply fewer signals. In this backtest, the fixed model actually recorded the highest cumulative turnover of all four models.&lt;/p&gt;
&lt;h2&gt;
  
  
  13. How the Results Should Be Recorded
&lt;/h2&gt;

&lt;p&gt;At a minimum, the following table should be saved separately by instrument, timeframe, and date range instead of retaining only one aggregate equity curve:&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%2Flf57x78wq5ohvw9gqyeo.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%2Flf57x78wq5ohvw9gqyeo.png" alt=" " width="794" height="46"&gt;&lt;/a&gt;&lt;br&gt;
Three categories of robustness checks should also be added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13.1 Cost Scenarios&lt;/strong&gt;&lt;br&gt;
At a minimum, compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 bps
2 bps / one-way turnover
5 bps / one-way turnover
10 bps / one-way turnover

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

&lt;/div&gt;



&lt;p&gt;If the online model wins only with zero costs, the conclusion should be written as “it generated more gross signals,” not “it adapted better to the market.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13.2 Neighboring Parameters&lt;/strong&gt;&lt;br&gt;
Do not perform large-scale optimization. Only check whether the conclusion is stable under neighboring parameter values, 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;Online learning rate: 0.01 / 0.02 / 0.04
Retraining interval: 16 / 32 / 64
Rolling window: 120 / 240 / 480
Gating loss multiplier: 1.05 / 1.10 / 1.20

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

&lt;/div&gt;



&lt;p&gt;If an advantage appears only at one very narrow parameter point, it is more likely to be sample fitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13.3 Regime-by-Regime Observation&lt;/strong&gt;&lt;br&gt;
Break the results down by volatility, trend strength, or natural time periods.&lt;/p&gt;

&lt;p&gt;Aggregate-sample returns may conceal two completely different behaviors: the model may recover faster during transitions, while continuously leaking performance in stable periods because of frequent updates. Only regime-by-regime observation can reveal where the benefits of adaptation and the costs of noise occur separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. Remaining Limitations of the Experiment
&lt;/h2&gt;

&lt;p&gt;First, simulated execution is not exchange matching. Entry near the next bar’s open, fixed basis-point costs, and the absence of order-book impact are all simplifying assumptions.&lt;/p&gt;

&lt;p&gt;Second, the model predicts only the open-to-close direction of the next bar. This target makes time alignment convenient, but it does not mean it is the most economically meaningful forecasting target.&lt;/p&gt;

&lt;p&gt;Third, the fixed standardizer preserves consistency across the model comparison, but it does not solve feature-distribution drift. Online standardization can be studied as another experiment, but it cannot be added only to the per-bar model.&lt;/p&gt;

&lt;p&gt;Fourth, error gating is only a heuristic rule and does not carry the statistical guarantees of a drift-detection algorithm. To study formal drift identification, methods such as ADWIN and DDM should be implemented separately, with false alarms and detection delay controlled.&lt;/p&gt;

&lt;p&gt;Fifth, a single instrument, timeframe, or backtest interval cannot answer the question “Is online learning better?” Questions of this kind can only be answered through stability across markets, regimes, and neighboring parameter settings.&lt;/p&gt;

&lt;p&gt;Sixth, conclusions from logistic regression should not be directly extrapolated to deep models. Complex models have greater expressive power and more degrees of freedom in their updates. The noise risk of online updating does not automatically disappear as a result.&lt;/p&gt;

&lt;p&gt;Seventh, the current simulated equity approaches zero rapidly under high turnover costs, causing net return and maximum drawdown to become saturated. Future research should report gross return, cumulative costs, net log return, and turnover per 1,000 bars at the same time, avoiding a situation in which different models all display returns close to -100% and can no longer be compared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Markets change, but that does not mean a model should change immediately after every bar.&lt;/p&gt;

&lt;p&gt;A fixed model bears the risk of “remaining trapped in an old relationship.” A per-bar online model bears the risk of “placing too much trust in the most recent samples.” There is no universal answer independent of the data, costs, and forecasting target.&lt;/p&gt;

&lt;p&gt;For online updating to be valuable, at least three conditions should hold simultaneously:&lt;/p&gt;

&lt;p&gt;After a new environment appears, predictive loss recovers more quickly.&lt;br&gt;
Parameter changes do not evolve persistently into higher signal reversal and turnover.&lt;br&gt;
The improvement survives costs and can be repeated across instruments, timeframes, and neighboring parameter settings.&lt;br&gt;
In this default-parameter backtest, both per-bar online updating and rolling retraining produced clear parameter movement without lowering out-of-sample Log Loss. Error gating reduced turnover and direct long-short reversals, but it still did not produce a tradable edge.&lt;/p&gt;

&lt;p&gt;If a model merely updates more frequently, moves its parameters farther, and trades more often—without persistently reducing out-of-sample loss—it has not adapted better to the market.&lt;/p&gt;

&lt;p&gt;It has only learned what happened most recently, faster.&lt;/p&gt;

</description>
      <category>quanttrading</category>
      <category>modeladaptation</category>
      <category>overfitting</category>
      <category>backtesting</category>
    </item>
    <item>
      <title>A New Arbitrage Play: After the SK Hynix Arbitrage Frenzy, Are There Still Opportunities Hidden in EWY?</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Wed, 26 Aug 2026 03:47:24 +0000</pubDate>
      <link>https://dev.to/quant001/a-new-arbitrage-play-after-the-sk-hynix-arbitrage-frenzy-are-there-still-opportunities-hidden-in-44pb</link>
      <guid>https://dev.to/quant001/a-new-arbitrage-play-after-the-sk-hynix-arbitrage-frenzy-are-there-still-opportunities-hidden-in-44pb</guid>
      <description>&lt;p&gt;SK Hynix has recently put the word “arbitrage” back in the spotlight.&lt;/p&gt;

&lt;p&gt;With stock perpetual contracts going live, instruments such as SK Hynix and Samsung Electronics—which previously traded only through the KRX order book—gained an additional USDT-margined exposure channel with continuous 24-hour quotes. Public information shows that Binance listed SKHYNIXUSDT, SAMSUNGUSDT, and HYUNDAIUSDT on June 2, 2026. An industry report published in July noted that, during certain periods, the basis between SK Hynix’s Korean cash-market closing price and the Binance contract reached 3%–5%, while contract price differences across crypto platforms also exceeded 2% at one point. Once those spreads appeared on the screen, quantitative teams naturally rushed in. But after walking through that route myself, I did not ultimately trade it. Instead, I chose a quieter combination whose three legs could all be executed in the same account: EWY, Samsung Electronics, and SK Hynix.&lt;/p&gt;

&lt;p&gt;This article explains the logic of the strategy: why I gave up on the cross-market basis trade, what makes the relationship among these three instruments potentially tradable, what the regression equation is doing, why the residual—not the price—is the actual trading object, how position ratios are determined, which data conditions must cause the strategy to refuse trading, and what is still missing before the current version can be considered suitable for live deployment. The strategy has been running in simulation on the FMZ Quant platform, and the source code is provided at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Basis Everyone Is Talking About—and the Timing Mismatch Behind It
&lt;/h2&gt;

&lt;p&gt;The first point to clarify is a common wording error: listing stock perpetual contracts did not extend the trading hours of SK Hynix’s cash shares.&lt;/p&gt;

&lt;p&gt;After the KRX closes, the underlying stock remains frozen at its final traded price, while the perpetual contract that references it continues to quote and absorb overnight macro data, movements in the Philadelphia Semiconductor Index, and exchange-rate changes. The reference asset is frozen while the derivative continues to price new information. This timing mismatch is the main source of the recent basis.&lt;/p&gt;

&lt;p&gt;For the same reason, the so-called “fair price” after the cash market closes is unobservable. A deviation in the perpetual contract may arise from two completely opposite causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It may be pricing overnight information in advance → the cash market could move toward it at the next open, in which case trading convergence means catching a falling knife against the trend.&lt;/li&gt;
&lt;li&gt;It may simply have been pushed away from its anchor by thin liquidity → the cash-market open could pull it back, in which case a convergence trade may be valid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two situations look exactly the same in the order book. If you cannot distinguish between them and place a bet anyway, you are not trading convergence—you are taking the other side of a new round of price discovery for the market.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why I Did Not Pursue Cross-Market Arbitrage
&lt;/h2&gt;

&lt;p&gt;Even if we set aside the ambiguity in pricing, building the “Korean cash equity ↔ crypto perpetual” trade requires passing through every one of the following frictions:&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%2F4y5f1afjs5gr5x67dxgm.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%2F4y5f1afjs5gr5x67dxgm.png" alt=" " width="799" height="171"&gt;&lt;/a&gt;&lt;br&gt;
A nominal 3% spread does not mean 3% will end up in your pocket. And the most visible crack is usually also the most crowded one.&lt;/p&gt;

&lt;p&gt;So I changed the approach: keep the relative-value methodology, but place every leg in the same execution environment. This cannot eliminate strategy risk, but it can first remove several layers of engineering friction—accounts, funding channels, time zones, and settlement currencies—allowing the research to focus on the statistical relationship itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. The Instruments: EWY and Its Two Heaviest Weights
&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%2Fybzazeca3orfw18qp779.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%2Fybzazeca3orfw18qp779.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Figure 1: EWY is like an asset basket containing multiple Korean companies, with Samsung Electronics and SK Hynix as its two heaviest weights. The regression attempts to use those two weights to explain the basket’s main movements, while the orange fluctuation represents the temporarily unexplained residual.&lt;/p&gt;

&lt;p&gt;EWY is an ETF that tracks the South Korean equity market. According to the holdings disclosed by iShares on July 17, 2026:&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%2Fbhw56mtsktt1wyts93vg.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%2Fbhw56mtsktt1wyts93vg.png" alt=" " width="611" height="151"&gt;&lt;/a&gt;&lt;br&gt;
Together, the two companies account for nearly 45% of the fund—enough to explain a large share of EWY’s directional movement, but far from enough to determine it completely. The remaining components, together with exchange rates, timing mismatches, capital flows, and the basis specific to each contract, create the deviations that may be traded.&lt;/p&gt;

&lt;p&gt;The reason for choosing this combination is not that “the three curves look similar.” The deviation has an economic attribution: the two stocks are genuinely core holdings of the ETF. Because the statistical relationship is supported by the portfolio structure, it makes sense to discuss “convergence.” If you simply brute-force correlations across thousands of contracts, it is easy to find attractive but economically meaningless spurious relationships. Once the market regime changes, those relationships disappear.&lt;/p&gt;

&lt;p&gt;The research question therefore becomes specific:&lt;/p&gt;

&lt;p&gt;When EWY deviates from the “implied price” given by Samsung Electronics and SK Hynix, does that deviation tend to revert?&lt;/p&gt;

&lt;p&gt;The strategy trades three USDT-margined linear contracts within the same account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var SYMBOLS = "EWY_USDT.swap,SAMSUNG_USDT.swap,SKHYNIX_USDT.swap";
var LABELS  = "EWY,Samsung Electronics,SK Hynix";   // The 1st is the target leg; the others are hedge legs

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

&lt;/div&gt;



&lt;p&gt;This must be stated clearly: it is not risk-free arbitrage. It is a relative-value trade that bets on the continuation of a statistical relationship.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Model: Explaining the Basket with Two Weights
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;4.1 The Regression Equation—and Why It Uses Log Prices&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ln(EWY) = c + β₁·ln(Samsung Electronics) + β₂·ln(SK Hynix) + ε

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

&lt;/div&gt;



&lt;p&gt;There are two practical reasons for using log prices rather than raw prices.&lt;/p&gt;

&lt;p&gt;First, β becomes an elasticity coefficient. β₁ = 0.5 means that “when Samsung rises by 1%, EWY rises by 0.5%,” regardless of the absolute price levels of the three instruments. The resulting hedge ratios can be converted directly into notional amounts without multiplying by price ratios.&lt;/p&gt;

&lt;p&gt;Second, the residual ε becomes a relative deviation. ε = 0.008 means that “EWY is approximately 0.8% more expensive than the value implied by the model.” It is a percentage. With a regression on raw prices, the residual is an absolute price difference whose units change as price levels change. It cannot be compared across time, and the standardized z-score loses its meaning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.2 Code Implementation&lt;/strong&gt;&lt;br&gt;
The mathematical core of the strategy is a single function. It returns not only β₁ and β₂, but also the full residual series for every K-line in the window—because the trading signal comes from the residual sequence, not from the coefficients themselves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function regressSpread(lnTarget, lnRefs) {
    var k = lnRefs.length, n = lnTarget.length, dim = k + 1;
    var XtX = [], Xty = new Array(dim).fill(0);
    for (var i = 0; i &amp;lt; dim; i++) XtX.push(new Array(dim).fill(0));

    function col(j, t) { return j === 0 ? 1 : lnRefs[j - 1][t]; }

    // Accumulate the normal equations XᵀX and Xᵀy
    for (var t = 0; t &amp;lt; n; t++) {
        for (var i = 0; i &amp;lt; dim; i++) {
            Xty[i] += col(i, t) * lnTarget[t];
            for (var j = 0; j &amp;lt; dim; j++) XtX[i][j] += col(i, t) * col(j, t);
        }
    }
    var beta = solveLinearSystem(XtX, Xty);
    if (!beta) return null;

    // Reconstruct the residual bar by bar: actual value − model-implied value
    var resid = [];
    for (var t = 0; t &amp;lt; n; t++) {
        var pred = beta[0];
        for (var j = 0; j &amp;lt; k; j++) pred += beta[j + 1] * lnRefs[j][t];
        resid.push(lnTarget[t] - pred);
    }
    return { c: beta[0], betas: beta.slice(1), resid: resid };
}

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

&lt;/div&gt;



&lt;p&gt;solveLinearSystem() uses Gaussian elimination with partial pivoting. If the absolute value of a pivot is below 1e-12—meaning the two hedge legs are highly collinear and the normal equations are nearly singular—it returns null directly and invalidates the signal for that cycle. This is not numerical fussiness: Samsung and SK Hynix are both part of the Korean semiconductor sector, so collinearity is naturally high. A single ill-conditioned solution can push β to absurd magnitudes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.3 The Trading Object Is ε, Not Price&lt;/strong&gt;&lt;br&gt;
ε is “the part of EWY’s price that cannot be explained by the synchronized movement of Samsung and SK Hynix.” It may come from the ETF’s other constituents, such as Hyundai Motor and financial stocks, or from exchange rates, timing mismatches, funding rates, or liquidity noise.&lt;/p&gt;

&lt;p&gt;This is the dividing line between this strategy and directional trading. We are not predicting whether the Korean stock market will rise or fall. We are betting only that “a short-term dislocation between the basket and its weights will converge.” The common movement of Samsung and SK Hynix is offset by the hedge legs, leaving only ε as the intended exposure.&lt;/p&gt;

&lt;p&gt;The strategy runs a rolling regression on the most recent 480 fifteen-minute K-lines—approximately five trading days—and standardizes the latest residual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z = (latest residual − mean residual over the window) / residual standard deviation over the window

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

&lt;/div&gt;



&lt;p&gt;There is a methodological flaw that must be acknowledged. The regression is fitted on the full sample within the window, and the latest residual participates in that fit. It is therefore naturally “pulled toward” the regression surface, systematically compressing the z-score. At the same time, the window rolls forward on every K-line, so β also changes. Strictly speaking, the residual sequence is not a set of comparable residuals generated by one fixed model. This is not fatal—rolling OLS is common in pairs trading—but it means the absolute z-score cannot be interpreted as a standard normal variable. Thresholds must be calibrated empirically; textbook conclusions such as “±2σ corresponds to 95%” cannot simply be applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Signal: A Three-State Machine
&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%2Fokxln70l64r5vfech5oj.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%2Fokxln70l64r5vfech5oj.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Figure 2: A spread position is opened when the residual enters the upper or lower extreme region and closed when it returns near the mean. The reversion arrows in the chart represent the trading hypothesis; they do not mean the residual is guaranteed to converge.&lt;/p&gt;

&lt;p&gt;The signal is a small state machine. When flat, the strategy opens a position only after the entry threshold is crossed. Once a position is open, it does not keep adding just because z remains on the same side. It waits for z to return inside the exit band or for the maximum holding period to be reached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var state = _G('bh_state') || 'FLAT';
var holdHours = state !== 'FLAT' ? (now - entryTime) / 3600000 : 0;
var forceExit = state !== 'FLAT' &amp;amp;&amp;amp; MAX_HOLD_HOURS &amp;gt; 0 &amp;amp;&amp;amp; holdHours &amp;gt; MAX_HOLD_HOURS;

var newState = state;
if (state === 'FLAT') {
    if (zLatest &amp;gt; ENTRY_Z)       newState = 'SHORT_SPREAD';  // EWY expensive → short EWY, long both hedge legs
    else if (zLatest &amp;lt; -ENTRY_Z) newState = 'LONG_SPREAD';   // EWY cheap → long EWY, short both hedge legs
} else {
    if (forceExit) { newState = 'FLAT'; Log('⏰ Maximum holding time exceeded; forcing exit'); }
    else if (Math.abs(zLatest) &amp;lt; EXIT_Z) { newState = 'FLAT'; Log('✅ z has reverted into the exit band; closing positions'); }
}

&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%2Fzft45euqy556ngqbjnoc.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%2Fzft45euqy556ngqbjnoc.png" alt=" " width="786" height="154"&gt;&lt;/a&gt;&lt;br&gt;
The asymmetric entry and exit thresholds—1.0 and 0.5—are deliberate. If they were equal, z would oscillate near the threshold and repeatedly open and close positions, allowing fees to consume the entire profit. The 0.5 buffer ensures that every trade must complete at least half a standard deviation of reversion before the gain is realized.&lt;/p&gt;

&lt;p&gt;This state machine contains two design trade-offs that need to be stated explicitly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;There is no z-based stop-loss.&lt;/strong&gt; If z continues to widen against the position—for example, z rises from 1.2 to 3 while holding SHORT_SPREAD—the code neither adds to the position nor stops out. It waits only for |z| &amp;lt; 0.5 or for the 48-hour timeout. This is the classic form of a mean-reversion strategy, and also the classic way such a strategy dies: once the relationship truly breaks, the loss has no upper bound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The strategy does not reverse.&lt;/strong&gt; If z crosses directly to −1.5 while holding SHORT_SPREAD, the else branch checks only |z| &amp;lt; 0.5 and the timeout. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither condition is met, so the position remains unchanged until the 48-hour limit. This is an explicit behavioral boundary, not a bug, but it should be reassessed before live trading.&lt;br&gt;
The purpose of MAX_HOLD_HOURS = 48 is precisely to prevent a short-term dislocation from turning into a long-term belief. In this version, it is the only hard mechanism for admitting that “the relationship may already have failed.”&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Positioning: β Determines the Ratios
&lt;/h2&gt;

&lt;p&gt;The easiest mistake in a three-leg strategy is to get the direction right but the ratio wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.1 Where the Hedge Ratios Come From&lt;/strong&gt;&lt;br&gt;
Suppose the signal says EWY is expensive and we short one unit of EWY notional. What risks is the portfolio exposed to? According to the regression equation, β₁ units of EWY’s movement come from Samsung and β₂ units come from SK Hynix. To offset those two common factors, we must simultaneously buy β₁ units of Samsung notional and β₂ units of SK Hynix notional.&lt;/p&gt;

&lt;p&gt;In the code, it is this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var wr = -reg.betas[j] * wTarget;   // Direction and size of each hedge leg are determined inversely by β

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

&lt;/div&gt;



&lt;p&gt;The notional ratio of the three legs is therefore 1 : β₁ : β₂, after which the whole portfolio is normalized to the gross exposure budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var dir = state === 'SHORT_SPREAD' ? -1 : 1;
var wTarget = dir * 1.0;
var wRefs = [], gross = Math.abs(wTarget);
for (var j = 0; j &amp;lt; reg.betas.length; j++) {
    var wr = -reg.betas[j] * wTarget;
    wRefs.push(wr); gross += Math.abs(wr);
}
var scale = gross &amp;gt; 0 ? (GROSS_EXPOSURE / gross) : 0;   // Normalize Σ|w| to 0.7
weights[0] = wTarget * scale;
for (var j = 0; j &amp;lt; wRefs.length; j++) weights[j + 1] = wRefs[j] * scale;

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

&lt;/div&gt;



&lt;p&gt;The strategy then converts weights into order quantities using notional amount = weight × LEVERAGE × equity, with leverage set to 3×.&lt;/p&gt;

&lt;p&gt;The goal of this step is not to make the portfolio incapable of losing money. It is to make the portfolio sensitive only to ε. Broad moves in Korean equities, semiconductor-sector co-movement, and USD/KRW fluctuations—the factors acting simultaneously on all three legs—are theoretically offset by the β-based ratios. What remains is the relative-value component we actually want to trade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.2 When Orders Are Actually Placed&lt;/strong&gt;&lt;br&gt;
β and the weights are recalculated in every loop, but the strategy does not rebalance every time. Rebalancing is triggered only in two situations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ① The direction of any leg flips
if ((sig.weights[i] &amp;gt;= 0) !== (lastWeights[i] &amp;gt;= 0) &amp;amp;&amp;amp; (sig.weights[i] !== 0 || lastWeights[i] !== 0)) {
    needRebal = true; break;
}
// ② The absolute weight of any leg drifts by more than 20%
var lw = Math.abs(lastWeights[i]);
if (lw &amp;gt; 1e-9 &amp;amp;&amp;amp; Math.abs(Math.abs(sig.weights[i]) - lw) / lw &amp;gt; DRIFT_THRESHOLD) {
    needRebal = true; break;
}

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

&lt;/div&gt;



&lt;p&gt;β drift is captured through condition ②: β changes → weights change → the 20% threshold is exceeded → the hedge is rebalanced. This throttling layer acknowledges that “small fluctuations in β are estimation noise, not information.” If every minor change triggered a rebalance, trading fees would drain the account before the residual had time to converge.&lt;/p&gt;

&lt;p&gt;MIN_REBAL_USDT = 3 serves two roles. It is both the threshold below which “the deviation is too small to act on” and the closing switch on exit. After the state changes to FLAT, the weights of all three legs become zero, making their target notionals fall below 3 USDT; the rebalancing logic then closes all positions.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Data Hygiene: Conditions That Must Block Trading
&lt;/h2&gt;

&lt;p&gt;Newly listed TradFi contracts have short histories, prices may freeze during market closures, and the hedge legs are highly collinear. A single abnormal price can push β and z to absurd levels, causing the system to believe it has found a once-in-a-century opportunity.&lt;/p&gt;

&lt;p&gt;For that reason, the code places several hard gates before a signal can be formed. They do not create returns. Their only job is to refuse trading when the data is clearly unreliable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ① Price-freeze detection: a hedge leg barely changes over the entire window
//    (market closed / mark price frozen). This destabilizes the regression,
//    compresses residual standard deviation toward zero, and can explode z.
if (refMax - refMin &amp;lt; MIN_REF_LOGRANGE) {          // 1e-6
    Log('⚠️ ' + LABELS[j + 1] + ' price barely changed within the window (possibly closed/frozen); skipping this cycle');
    return null;
}

// ② Collinearity detection: the normal equations are nearly singular,
//    so Gaussian elimination returns null
var reg = regressSpread(lnTarget, lnRefs);
if (!reg) { Log('⚠️ Regression matrix is singular; skipping this cycle'); return null; }

// ③ Minimum residual standard deviation: a very small value means the regression
//    is unreliable and the z denominator will amplify noise into a signal
var sd = stdev(reg.resid);
if (!(sd &amp;gt; MIN_RESID_STD)) { return null; }        // 5e-4

// ④ z-value sanity cap: an extreme value is more likely to be bad data
//    than a genuine opportunity
if (!isFinite(zLatest) || Math.abs(zLatest) &amp;gt; Z_SANITY_CAP) {   // 15
    Log('⚠️ Abnormal z value (' + zLatest + '); treating it as a calculation fault and ignoring the signal');
    return null;
}

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

&lt;/div&gt;



&lt;p&gt;Gate ① and gate ③ address opposite ends of the same risk. When Korean equities are closed, the reference contracts may remain unchanged for a long period, compressing the residual standard deviation within the window toward zero. The denominator of z then approaches zero, and any tiny numerator is magnified into an astronomical value. A seemingly “once-in-a-lifetime” z = 40 may simply be the result of dividing by a number that should never have appeared. If such signals are not blocked, the first trigger may be enough to wipe out the account.&lt;/p&gt;

&lt;p&gt;The full data chain is also aligned. alignData() takes the intersection of timestamps across the three K-line series, keeps only times for which all three instruments have closing prices, and requires at least 490 common K-lines before any calculation is allowed. If any leg contains a zero, negative, or non-numeric price, the entire cycle is invalidated.&lt;/p&gt;

&lt;p&gt;All intermediate values are logged with the [DEBUG] prefix: latest close, the log-price range of every leg within the window, regression coefficients, residual mean and standard deviation, and the raw z-score. If z becomes abnormal, the logs reveal exactly which stage caused the problem, without guesswork.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Risk Boundaries
&lt;/h2&gt;

&lt;p&gt;Apart from z-based exits and the 48-hour timeout, the only active risk control in the code is a per-leg emergency reduction. If an open leg experiences an adverse move of more than 6% between two checks, the strategy cuts that leg’s position by half.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var drop = pos.side === 'long' ? (last - cur) / last : (cur - last) / last;
if (drop &amp;gt;= EMERGENCY_DROP) {                    // 0.06
    Log('🚨 Emergency risk! [' + label + '][' + pos.side + '] adverse move:', _N(drop * 100, 2) + '%');
    // ... reduce the position by 50%
}

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

&lt;/div&gt;



&lt;p&gt;Three boundaries of this mechanism must be made clear:&lt;/p&gt;

&lt;p&gt;① It evaluates each leg individually, not the portfolio. In a hedged portfolio, one leg falling by 6% may be completely normal if another leg rises at the same time and the portfolio’s net exposure remains unchanged. In that situation, the circuit breaker damages the hedge structure. After half of a hedge leg is removed, the remaining exposure is no longer the residual; it becomes naked directional risk. This is the part of the current version that most urgently needs to be changed.&lt;/p&gt;

&lt;p&gt;② The reference price is updated conditionally. bh_lastCheckPrices is updated as a whole only when no leg triggers during the current cycle. Once a trigger occurs, the reference price remains unchanged, and the next cycle still compares against the old price. In a one-way market, this can cause repeated triggers and repeated halving. This is intentionally conservative, but it can make the reduction much more aggressive than expected.&lt;/p&gt;

&lt;p&gt;③ It is an after-the-fact defense. The three legs cannot fill in the same microsecond at the same ideal price. Once the first leg fills, the other two legs still leave the portfolio exposed to the market. That is leg risk. The faster the market moves, the more likely execution error is to consume the small residual the strategy is trying to earn. A per-leg circuit breaker reduces exposure only after a loss has already occurred. It cannot replace a genuine three-leg execution-coordination mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Parameter Summary
&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%2Fd5uahvx9e18elo803gxd.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%2Fd5uahvx9e18elo803gxd.png" alt=" " width="678" height="601"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;⚠️ In the source code, the comment for POLL_MS says “5 minutes,” but the actual value is 1 * 60 * 1000—one minute. Polling once per minute while using 15-minute K-lines means the same unfinished bar is recalculated repeatedly. As a result, DRIFT_THRESHOLD carries a heavier throttling responsibility than originally intended. The implementation and the comment should be made consistent before live trading.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Two Days of Simulation—and What It Cannot Prove
&lt;/h2&gt;

&lt;p&gt;The prototype ran in simulation on FMZ Quant with initial capital of 400 USDT. After two days of testing, the account recorded a certain amount of positive return.&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%2Fr37oyakrl8j7phsmky2e.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%2Fr37oyakrl8j7phsmky2e.png" alt=" " width="525" height="272"&gt;&lt;/a&gt;&lt;br&gt;
This is enough to justify continued research, but nowhere near enough to prove profitability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The sample is too short.&lt;/strong&gt; Two days may cover only one market regime, or perhaps just one conveniently successful convergence. With a maximum holding period of 48 hours, the sample contains very few complete trade cycles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simulated execution is friendlier than the real market&lt;/strong&gt;. Orders are filled immediately and in full at the latest price, with no fees, no slippage, and no simulation of queue priority or partial fills. Three-leg strategies are especially sensitive to exactly these issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funding rates are not modeled at all&lt;/strong&gt;. Funding rates across the three legs will not be identical. A long-short portfolio generates additional carry P&amp;amp;L that does not automatically cancel. The longer the holding period, the larger this bias becomes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leg risk is not included&lt;/strong&gt;. In the simulation, all three legs fill “simultaneously.” Real execution does not work that way.
The correct interpretation of the positive return at this stage is simply this: the workflow runs end to end, no abnormal circuit breaker was triggered, and z and β remained within reasonable ranges. Nothing more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. What the Current Version Does Not Implement
&lt;/h2&gt;

&lt;p&gt;The difference between “the formula runs” and “capital can be entrusted to it” is the following checklist. None of these items is included in the current code, and every one of them must be completed before live deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statistical Validity&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No cointegration test. The code runs rolling OLS, but “running a regression” is not the same as “establishing cointegration.” An ADF or Engle–Granger test is needed to confirm that the residual is stationary. Otherwise, the supposed mean reversion may be nothing more than a visual illusion in a short sample.&lt;/li&gt;
&lt;li&gt;No half-life estimate. The residual has not been fitted to an OU process to estimate its reversion half-life. MAX_HOLD_HOURS = 48 is currently an arbitrary choice; it should instead be derived from the half-life.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No out-of-sample validation. There is no rolling walk-forward test and no parameter-stability analysis across market regimes. Neither ENTRY_Z = 1.0 nor LOOKBACK = 480 has been calibrated out of sample.&lt;br&gt;
&lt;strong&gt;Costs and Execution&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fees and slippage are not modeled. The average convergence amplitude of the residual must be reevaluated to determine whether any safety margin remains after costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Funding-rate carry is not modeled. Funding rates for the three contracts must be tracked separately to determine whether the portfolio has a persistent carry bias toward one side.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is no three-leg execution-coordination mechanism. The system does not specify how to complete or unwind the whole portfolio within a limited time when legs fill separately, orders are rejected, execution is delayed, or fills are partial.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Order precision is not aligned. Quantities are not rounded according to each contract’s stepSize and minimum notional requirement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is no trading calendar. The system does not identify KRX holidays, earnings windows, index rebalances, or other events during which β may change abruptly.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  12. Where This Framework Can Be Extended
&lt;/h2&gt;

&lt;p&gt;The same framework is not limited to the Korean market:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Index vs. a basket of heavily weighted sectors: short-term deviations between the S&amp;amp;P 500 and several high-weight sector ETFs.&lt;/li&gt;
&lt;li&gt;Rotation among market leaders: relative dislocations between leaders and followers within the “Magnificent Seven.”&lt;/li&gt;
&lt;li&gt;Sector ETF vs. core constituents: constructing an implied value for an ETF from several of its largest holdings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model also does not have to remain a three-leg structure. The instrument pool can be expanded into “one target plus multiple explanatory factors.” But a larger portfolio is not automatically more sophisticated. Every additional leg introduces another order book, another fill process, another funding rate, and another failure point. Execution error on every leg directly erodes the small residual the strategy is trying to capture.&lt;/p&gt;

&lt;p&gt;A genuinely tradable portfolio should satisfy all three conditions at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The economic relationship makes sense—the deviation has an explanation and is not spurious correlation.&lt;/li&gt;
&lt;li&gt;The statistical relationship survives out-of-sample testing—it is not a coincidence in a short sample.&lt;/li&gt;
&lt;li&gt;Execution costs do not consume the expected return—net, not gross.
If even one is missing, the idea is research material, not a tradable opportunity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
The SK Hynix basis reminded the market that new trading vehicles create new price fractures. But arbitrage opportunities do not exist only in the most crowded fracture. Indices and constituents, ETFs and baskets of leading stocks, and leaders and followers within the same theme can all temporarily lose synchronization.&lt;/p&gt;

&lt;p&gt;Formulas describe the deviation, tools discover and execute it, and risk controls give us a chance to survive until the next deviation appears. As for the small simulated profit in front of us, the right response is not celebration. It should be treated as the starting point for the next optimization checklist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;br&gt;
Binance Academy: How to Trade SK Hynix (SKHY) on Binance&lt;br&gt;
ChainCatcher: Report on the Current Development Status of the Stock Perpetual Contract Market (July 2026)&lt;br&gt;
iShares: EWY Fund Information and Latest Holdings&lt;br&gt;
Risk Notice: The content of this article is provided solely as a record of strategy research and simulated testing. It does not constitute investment advice. Statistical arbitrage is not risk-free arbitrage. Historical relationships, simulation results, and short-term returns do not guarantee future performance. The strategy runs in simulation mode by default. Complete every validation item listed above before switching to live trading.&lt;/p&gt;

</description>
      <category>ewy</category>
      <category>quanttrading</category>
      <category>tradingstrategy</category>
      <category>marketarbitrage</category>
    </item>
  </channel>
</rss>
