<?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>The Essential Skill for Quants in the AI Era Is No Longer Coding</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 21 Jul 2026 06:17:06 +0000</pubDate>
      <link>https://dev.to/quant001/the-essential-skill-for-quants-in-the-ai-era-is-no-longer-coding-329h</link>
      <guid>https://dev.to/quant001/the-essential-skill-for-quants-in-the-ai-era-is-no-longer-coding-329h</guid>
      <description>&lt;p&gt;&lt;em&gt;From a grid strategy that nearly blew up the account to a complete rewrite built together with AI&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A grid running on the Korean equity market revealed four painful flaws in live trading. Following those four symptoms downward did not lead to another round of parameter tuning, but to an entirely new mathematical foundation. And throughout the whole process, the truly scarce ability was not “solving the problem,” but “asking the right question.”&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instrument: EWY Korea equity index exposure (contract)&lt;/li&gt;
&lt;li&gt;Structure: Dynamically shifting grid, 10× leverage&lt;/li&gt;
&lt;li&gt;Final result: General-Purpose Grid Strategy · Mathematically Adaptive Edition v4.0&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%2Ftggr82wodmt5tsydwci6.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%2Ftggr82wodmt5tsydwci6.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: The fixed grid on the left keeps accumulating risk during a trend, while the adaptive structure on the right is supported by recursive estimators and risk-control gates.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  00 · The Beginning: One Grid, Four Thorns
&lt;/h2&gt;

&lt;p&gt;The story began with a discretionary view. I was bullish on a stretch of the Korean market, so I deployed a general-purpose grid on EWY: buy low and sell high inside a range, and shift the grid in the favorable direction after a breakout. The logic was clean, and the backtest looked good.&lt;/p&gt;

&lt;p&gt;But once real money was involved, four problems began to surface, one after another. The following four sections describe what I genuinely felt while watching the strategy trade live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thorn 1 · Inefficiency&lt;/strong&gt;. Both the grid spacing and the order size per level were hard-coded constants. But equities do not fluctuate uniformly around the clock. Between the Asian close and the U.S. open, price can barely move and volatility can become extremely low. Yet the grid was still using the same wide range and the same order size as during volatile periods. Orders sat sparsely across the range for hours without getting filled, while capital remained idle. Of course, when unexpected news arrived, volatility could suddenly explode. &lt;strong&gt;The problem was obvious: during low-volatility periods, the strategy was still placing orders across a fixed, wide range, making capital usage painfully inefficient.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thorn 2 · Liquidation risk.&lt;/strong&gt; This was the most dangerous flaw. Once the market entered a &lt;strong&gt;persistent decline&lt;/strong&gt;, the long grid kept buying at lower and lower levels—buying more as price fell, accumulating a larger position, and doing so without any stop-loss. Under 10× leverage, this is the classic “catching a falling knife” pattern. Inventory keeps growing until &lt;strong&gt;liquidation becomes only a matter of time.&lt;/strong&gt; The strategy could display the liquidation price, but it merely displayed it. It never intervened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thorn 3 · Giving back profits and wasting time while waiting.&lt;/strong&gt; The grid slowly accumulated profits during sideways markets, and the equity curve looked attractive. But once there was &lt;strong&gt;a meaningful downward move, nearly all of those gains were given back.&lt;/strong&gt; What came next was even worse: I was left holding a pile of trapped long positions and could do nothing but wait for price to revert before each one could exit at its take-profit level. That wait could last a long time. Capital remained locked, and the opportunity cost kept growing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thorn 4 · Dead inventory at the top.&lt;/strong&gt; Positions opened at high prices were effectively being subsidized by repeated small profits harvested near the bottom. The strategy was slowly “grinding away” their losses through lower-range oscillations. The process was slow and passive. What I wanted was different: the grid’s &lt;strong&gt;upper boundary should be able to move down dynamically&lt;/strong&gt;, instead of endlessly carrying expensive inventory opened near the top.&lt;/p&gt;

&lt;p&gt;These four thorns looked like four separate defects. But once I dug deeper, they all turned out to be the same problem.&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%2Fi3g85p8tgllvreayqsit.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%2Fi3g85p8tgllvreayqsit.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Small profits accumulate during the oscillating phase; once the market enters a one-way decline, the risk from counter-trend inventory expands at a much faster rate.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  01 · Diagnosis: Four Symptoms, One Root Cause
&lt;/h2&gt;

&lt;p&gt;The clearest way to explain it is with Greek letters. A grid is fundamentally a short-volatility or short-gamma strategy: it earns money from repeated back-and-forth movement and therefore depends on mean reversion. But a long grid keeps catching falling knives during a decline, causing its net position—its delta—to grow larger and larger. It has no defense against a persistent trend.&lt;/p&gt;

&lt;p&gt;Decomposing the P&amp;amp;L gives the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Grid P&amp;amp;L  ≈  ( Oscillation Harvest ∝ σ²/d )  −  ( Inventory × Trend Drift μ )
              └── Always positive: earns from movement ──┘   └── Pure loss for a long book in a decline ──┘

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Oscillation term: The more frequently price crosses grid spacing d, and the larger volatility σ becomes, the more the strategy harvests. This term is always positive.&lt;/li&gt;
&lt;li&gt;Trend term: Inventory × μ. In a sustained decline, μ remains negative while inventory keeps increasing. This term can diverge and eventually consume everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The real root cause: this was a short-volatility strategy with no meaningful risk control.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Worse still, the traditional grid design of “moving only in the favorable direction” is actually &lt;strong&gt;the opposite&lt;/strong&gt; of the grid’s original purpose. When price breaks above the upper boundary, the grid moves upward and places new long orders at higher prices—buying after the rise. When price breaks below the lower boundary, it does nothing: no downward reset, no order cancellation, no stop-loss—just continued knife-catching. That design actively &lt;strong&gt;amplifies&lt;/strong&gt; the trend term.&lt;/p&gt;

&lt;p&gt;Looking back at the four thorns—inefficiency, liquidation, profit giveback, and dead inventory—they were simply different expressions of the same Inventory × μ problem at different stages. Once that became clear, the direction of the redesign was obvious: &lt;strong&gt;parameter tuning was not enough. This naked short-gamma grid needed a mathematical skeleton capable of reasoning about risk.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  02 · A Grid That Breathes
&lt;/h2&gt;

&lt;p&gt;The first problem to solve was the most intuitive one: inefficiency. The range width should not be a fixed percentage of price. It should follow the &lt;strong&gt;structure of volatility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When volatility is low, the grid should automatically contract and harvest smaller movements more densely. When volatility expands, the grid should open up so that a single long wick does not sweep through the entire structure. It should inhale and exhale like a pair of lungs.&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%2Fl6257r36hpn1px0k7hx7.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%2Fl6257r36hpn1px0k7hx7.png" alt=" " width="799" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: On the left, volatility is low, so both spacing and total range contract. On the right, volatility expands, and the grid widens with the equilibrium amplitude.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  03 · The Fog of Direction: Milliseconds Are Noise; Drift Is the Enemy
&lt;/h2&gt;

&lt;p&gt;The core of thorns two and three was &lt;strong&gt;direction detection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Millisecond-level market jumps can look frightening, but those moves are precisely the &lt;strong&gt;noise&lt;/strong&gt; that a grid is supposed to harvest. You do not need to predict them, and in practice you cannot. What matters is the &lt;strong&gt;slow variable hidden underneath the noise: drift, μ&lt;/strong&gt;. Its relevant timescale is minutes to hours, not milliseconds.&lt;/p&gt;

&lt;p&gt;Treating high-frequency price jumps as directional signals and repeatedly stopping out is the cognitive error behind many failed grid strategies. &lt;strong&gt;Making decisions from noise guarantees two failures at once: you waste the strategy’s expected return through frequent stop-outs, fees, and false positives, while still failing to avoid the real trend.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the problem became: how can the strategy continuously sense μ &lt;strong&gt;without repeatedly stopping out and without stubbornly holding through a genuine trend?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That required a “second eye.”&lt;/p&gt;
&lt;h2&gt;
  
  
  04 · The Second Eye: Confirmation, Not Prediction
&lt;/h2&gt;

&lt;p&gt;EWY represents Korean equities, and it has major constituent exposures such as Samsung Electronics and SK Hynix. The key shift in thinking was to stop betting on “who leads whom.” Lead-lag relationships are unstable and can be disrupted by geopolitics, Federal Reserve policy, and news flow.&lt;/p&gt;

&lt;p&gt;Instead, the constituents can be used to &lt;strong&gt;confirm whether a decline is real&lt;/strong&gt;. This requires &lt;strong&gt;synchronization&lt;/strong&gt;, not predictive leadership.&lt;/p&gt;

&lt;p&gt;Mathematically, each EWY return can be decomposed into two parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r_EWY  =  β · r_reference  +  ε
          └─ systematic ─┘    └─ idiosyncratic ─┘
           broad/fundamental     EWY-specific
           → genuine trend       → likely to revert

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

&lt;/div&gt;



&lt;p&gt;The decision rule then becomes straightforward:&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%2Fvsj4au827znlah0utsm3.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%2Fvsj4au827znlah0utsm3.png" alt=" " width="800" height="93"&gt;&lt;/a&gt;&lt;br&gt;
Price alone cannot reveal this difference. Every bearish candle looks the same. Adding the constituent stocks as a second eye separates trend from noise—exactly the boundary I had been looking for between “short-term deviation” and “long-term regime change.”&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%2F2uzjrc6yysa3h257ne9w.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%2F2uzjrc6yysa3h257ne9w.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4: On the left, all three price streams weaken together, so the system identifies a broad trend and blocks counter-trend entries. On the right, only the target briefly deviates, so the mean-reversion channel remains active.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  05 · Let the Parameters Converge on Their Own
&lt;/h2&gt;

&lt;p&gt;There was another, deeper dissatisfaction: why should I decide the width of the range or how long the strategy should wait for reversion by intuition?&lt;/p&gt;

&lt;p&gt;If the spread is mean-reverting, it can be modeled as an Ornstein–Uhlenbeck process. That means its &lt;strong&gt;timescale can be estimated from data instead of being entered manually as a parameter&lt;/strong&gt;. The central quantity is the &lt;strong&gt;mean-reversion half-life&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;H = ln(2) / θ        (θ is the reversion speed)

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Half-life&lt;/strong&gt; H determines the natural holding timescale. More importantly, it provides an &lt;strong&gt;exit criterion&lt;/strong&gt;: if the spread still has not reverted after k·H, the mean-reversion relationship may have deteriorated, confirming a regime shift. Exit instead of waiting forever.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Equilibrium amplitude&lt;/strong&gt; σ_eq determines the range width.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“How long is too long to wait?” changed from a subjective anxiety into a measurable number. For the first time, the wasted waiting described in thorn three had a precise gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  06 · Convergence: Three Recursive Estimators
&lt;/h2&gt;

&lt;p&gt;Every requirement eventually converged on one principle: &lt;strong&gt;recursive, online, and without a fixed lookback window.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each new candle updates the state once. Memory adapts to the signal-to-noise ratio and the reversion speed instead of looking back over a fixed number of bars. Three estimators carry the entire decision framework.&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%2Finhhrnx7l76p455usps8.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%2Finhhrnx7l76p455usps8.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Figure 5: The Kalman filter tracks local level and slope, RLS-OU estimates the reversion timescale, and cross-asset β decomposition isolates systematic drift. Together, the three outputs determine the range and the risk-control gates.&lt;/p&gt;
&lt;h2&gt;
  
  
  ① Kalman Local Level + Slope — Replacing ATR, MA, and RSI
&lt;/h2&gt;

&lt;p&gt;A two-dimensional Kalman filter—state = level + slope—tracks price online. It has &lt;strong&gt;no fixed lookback window&lt;/strong&gt;. The Kalman gain adapts to the signal-to-noise ratio: memory becomes longer in stable conditions and shortens rapidly after structural change. Observation noise R, which functions as volatility, is estimated recursively online using Robbins–Monro adaptation. This is soft memory, not a fixed window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def kf_step(st, y):
    """Kalman local level + trend; update one state dictionary point by point.
    Observation noise R adapts online with no fixed window.
    """
    st["price"] = y
    if not st["init"]:
        st["l"] = y
        st["s"] = 0.0
        st["R"] = max((y * 0.0005) ** 2, 1e-12)   # Initial volatility prior ≈ 5 bp
        st["init"] = True
        return

    R = st["R"] if st["R"] &amp;gt; 0 else 1e-12
    q_s = KF_Q_RATIO * R          # Slope process noise: responsiveness, the only soft prior
    q_l = KF_Q_RATIO * R * 0.25

    # Predict: F=[[1,1],[0,1]]
    l_pred = st["l"] + st["s"]
    s_pred = st["s"]
    pp00 = st["p00"] + st["p01"] + st["p10"] + st["p11"] + q_l
    pp01 = st["p01"] + st["p11"]
    pp10 = st["p10"] + st["p11"]
    pp11 = st["p11"] + q_s

    # Update: H=[1,0]
    e = y - l_pred                 # Innovation: current prediction error
    S = pp00 + R
    k0 = pp00 / S
    k1 = pp10 / S
    st["l"] = l_pred + k0 * e
    st["s"] = s_pred + k1 * e
    st["p00"] = (1 - k0) * pp00
    st["p01"] = (1 - k0) * pp01
    st["p10"] = pp10 - k1 * pp00
    st["p11"] = pp11 - k1 * pp01

    # Adaptive observation noise: Robbins–Monro soft memory, not a fixed window
    st["R"] = st["R"] + KF_ADAPT * (e * e - st["R"])
    st["sigma"] = math.sqrt(st["R"])                  # Instantaneous volatility
    st["z"] = st["s"] / (st["sigma"] + 1e-12)       # Trend strength = slope / volatility, signed

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

&lt;/div&gt;



&lt;p&gt;The output st["z"]—slope divided by volatility, a signed signal-to-noise measure per bar—is the trend-strength signal. Note that the state is stored in a dictionary called st: &lt;strong&gt;the same mathematical structure is reused for both the target and every reference instrument&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ② RLS-AR(1) with a Self-Consistent Forgetting Factor — Letting the Timescale Emerge
&lt;/h2&gt;

&lt;p&gt;Recursive least squares with a forgetting factor is used to fit:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;price_t = a + b·price&lt;/em&gt;{t-1}_&lt;/p&gt;

&lt;p&gt;The regression coefficient b is then converted into the OU half-life H and the equilibrium amplitude σ_eq.&lt;/p&gt;

&lt;p&gt;The key idea is that &lt;strong&gt;the forgetting factor is itself determined by the estimated half-life&lt;/strong&gt;. Memory is approximately OU_MEMORY half-lives long. In other words, the memory length converges from the data instead of being imposed as a manually chosen window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def _rls_update(y_prev, y):
    """RLS-AR(1) estimator for mean reversion.
    The forgetting factor is determined self-consistently by the estimated half-life.
    No fixed window.
    """
    global _rls_a, _rls_b, _rls_p00, _rls_p01, _rls_p10, _rls_p11
    global _rls_resid_var, _rls_init, _ou_half_life, _ou_sigma_eq

    # Forgetting factor: memory ≈ OU_MEMORY half-lives.
    # Use a gentle default while the half-life is unknown.
    if _ou_half_life &amp;gt; 0:
        mem = max(OU_MEMORY * _ou_half_life, 5.0)
        ff = math.exp(-1.0 / mem)
    else:
        ff = 0.99
    ff = min(max(ff, 0.90), 0.9995)

    phi0, phi1 = 1.0, y_prev
    pph0 = _rls_p00 * phi0 + _rls_p01 * phi1
    pph1 = _rls_p10 * phi0 + _rls_p11 * phi1
    denom = ff + phi0 * pph0 + phi1 * pph1
    k0 = pph0 / denom
    k1 = pph1 / denom
    e = y - (_rls_a * phi0 + _rls_b * phi1)
    _rls_a += k0 * e
    _rls_b += k1 * e
    _rls_p00 = (_rls_p00 - k0 * pph0) / ff
    _rls_p01 = (_rls_p01 - k0 * pph1) / ff
    _rls_p10 = (_rls_p10 - k1 * pph0) / ff
    _rls_p11 = (_rls_p11 - k1 * pph1) / ff
    _rls_resid_var = _rls_resid_var + KF_ADAPT * (e * e - _rls_resid_var)

    b = _rls_b
    if 0.0 &amp;lt; b &amp;lt; 1.0:
        theta = -math.log(b)
        _ou_half_life = math.log(2.0) / theta               # Half-life in bars
        if (1 - b * b) &amp;gt; 1e-9 and _rls_resid_var &amp;gt; 0:
            _ou_sigma_eq = math.sqrt(
                _rls_resid_var / (1 - b * b)
            )                                              # Equilibrium amplitude
    else:
        _ou_half_life = 0.0   # b ≥ 1: no reversion (unit root/trend); handled by the trend gate

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

&lt;/div&gt;



&lt;p&gt;The range width is then determined directly by σ_eq. It adapts to volatility and reversion speed while remaining protected by upper and lower bounds—&lt;strong&gt;with no fixed lookback window&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;def effective_width_abs(ref_price):
    """Set range width from OU equilibrium amplitude σ_eq.
    Adapt to the volatility structure; fall back to a fixed percentage before warm-up.
    """
    if estimators_ready() and _ou_sigma_eq &amp;gt; 0:
        w = WIDTH_SIGMA_MULT * _ou_sigma_eq
        floor_w = pct_to_abs(GRID_WIDTH_PCT, ref_price) * WIDTH_FLOOR_FRAC
        cap_w = pct_to_abs(GRID_WIDTH_PCT, ref_price) * WIDTH_CAP_MULT
        return min(max(w, floor_w), cap_w)
    return pct_to_abs(GRID_WIDTH_PCT, ref_price)

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  ③ Cross-Asset β Decomposition + Inventory Risk Control — The Second Eye and the Seat Belt
&lt;/h2&gt;

&lt;p&gt;This is where confirmation of a systematic trend is embedded into the strategy itself. For every new candle, the system recursively estimates the target’s regression coefficient β_j against each reference instrument in &lt;strong&gt;return space&lt;/strong&gt; using EWMA. It then decomposes target drift into systematic and idiosyncratic components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Inside update_estimators: update β and systematic trend one bar at a time

# Recursive regression coefficient β_j = covariance / variance
# Soft memory with no fixed window
r_t = (c - prev_close) / prev_close            # Target return for the current bar
r_j = (rc - _ref_prevc[j]) / _ref_prevc[j]     # Reference return for the current bar
_ref_cov[j] += KF_ADAPT * (r_t * r_j - _ref_cov[j])
_ref_var[j] += KF_ADAPT * (r_j * r_j - _ref_var[j])
_ref_beta[j] = _ref_cov[j] / _ref_var[j]

# Systematic drift confirmed by the references:
# ĝ_sys = Σ β_j · reference fractional drift
g_sys = 0.0
for j in range(nref):
    g_sys += _ref_beta[j] * (_kf_refs[j]["s"] / _kf_refs[j]["price"])
sig_frac = _vol_sigma / pt
_trend_z_sys = g_sys / (sig_frac + 1e-12)       # Systematic trend strength
_sys_share = abs(g_sys) / (abs(g_t) + 1e-12)    # Systematic share

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

&lt;/div&gt;



&lt;p&gt;The trend gate therefore responds only to the component confirmed by the references. When reference instruments are enabled, it uses systematic z_sys; otherwise, it falls back to the target’s own z:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def gate_trend_z():
    """
    References enabled and data available:
        use systematic z_sys, the component confirmed by Samsung, SK Hynix, and others.
    Otherwise:
        fall back to the target's own z.

    Target falls but references do not:
        idiosyncratic noise, z_sys ≈ 0, so the grid keeps harvesting.
    Target and references fall together:
        genuine systematic trend, z_sys becomes significant, so counter-trend entries are blocked.
    """
    if ref_n() &amp;gt; 0 and _refs_ok and estimators_ready():
        return _trend_z_sys
    return _trend_z

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

&lt;/div&gt;



&lt;p&gt;The “flexible lowering of the upper boundary” from thorn four is implemented through an inventory reservation-price adjustment, a discrete version of the Avellaneda–Stoikov reservation price. The larger the long inventory becomes, the more the effective upper boundary is pushed downward. New long entries at high prices automatically stop—the ceiling lowers itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def effective_ceiling():
    """As long inventory grows, push the effective upper boundary downward.
    Stop adding at high prices and let the ceiling adjust automatically.
    """
    long_amt = assets[base_currency].get("long_amount", 0)
    short_amt = assets[base_currency].get("short_amount", 0)
    net_usdt = (
        (long_amt - short_amt)
        * assets[base_currency]["ctVal"]
        * get_price()
    )
    inv_ratio = min(
        max(0.0, net_usdt / (Funding * MAX_NET_EXPOSURE_MULT)),
        1.0,
    )
    drop = INVENTORY_SKEW * inv_ratio * (range_high - range_low)
    return range_high - drop

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

&lt;/div&gt;



&lt;p&gt;All gates are unified inside the entry decision: trend, hard net-exposure limit, inventory ceiling, and drawdown circuit breaker. &lt;strong&gt;If any one of them fails, no new position is opened&lt;/strong&gt;. &lt;strong&gt;Existing take-profit orders are never force-closed&lt;/strong&gt;. In other words, the strategy neither adds aggressively against the trend nor stops out repeatedly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def can_open(side, grid_price):
    """Mathematical gates for new entries:
    trend / net exposure / inventory ceiling / drawdown breaker.
    """
    if estimators_ready():
        gz = gate_trend_z()
        if side == "long" and gz &amp;lt;= -TREND_Z_THRESHOLD:
            return False, "trend"
        if side == "short" and gz &amp;gt;= TREND_Z_THRESHOLD:
            return False, "trend"

    # Hard cap on net exposure: prevent unlimited falling-knife accumulation
    net_usdt = (long_amt - short_amt) * ct_val * price
    if side == "long" and net_usdt &amp;gt;= Funding * MAX_NET_EXPOSURE_MULT:
        return False, "risk"
    if side == "short" and -net_usdt &amp;gt;= Funding * MAX_NET_EXPOSURE_MULT:
        return False, "risk"

    # Inventory ceiling: stop opening new longs at high prices
    if side == "long" and grid_price &amp;gt; effective_ceiling():
        return False, "skew"

    if check_drawdown_breaker():
        return False, "dd"
    return True, ""

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  07 · Implementation: Removing All Four Thorns at Once
&lt;/h2&gt;

&lt;p&gt;After the strategy was rebuilt as v4.0, each thorn mapped directly to a mathematical mechanism. These were no longer isolated patches, but different projections of the same underlying framework:&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%2Fr1o9on3zeviet9qy71si.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%2Fr1o9on3zeviet9qy71si.png" alt=" " width="799" height="167"&gt;&lt;/a&gt;&lt;br&gt;
The reference contracts only need to be entered as one parameter line:&lt;/p&gt;

&lt;p&gt;SAMSUNG_USDT, SKHYNIX_USDT&lt;/p&gt;

&lt;p&gt;The strategy reads them directly through GetRecords, with no need to configure additional exchanges. Everything else adapts automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  08 · Validation: The Second Eye Opens
&lt;/h2&gt;

&lt;p&gt;The framework was validated numerically using synthetic sequences. EWY was declining in both cases, but the gate produced &lt;strong&gt;opposite actions&lt;/strong&gt; depending on whether the constituent references confirmed the move. That is exactly what “confirmation rather than prediction” should look like:&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%2F1q69r7vtsjfzbpf534lz.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%2F1q69r7vtsjfzbpf534lz.png" alt=" " width="799" height="93"&gt;&lt;/a&gt;&lt;br&gt;
Looking only at the target, both cases appear bearish (z ≈ −0.3). But with the second eye, one is classified as a genuine decline to avoid, while the other is treated as noise to harvest. &lt;strong&gt;The distinction that price alone cannot reveal is extracted by the mathematics&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Epilogue
&lt;/h2&gt;

&lt;p&gt;The real gain from this optimization was not the code. It was the path:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start from a specific pain observed in live trading. Refuse to cover it with outdated rules that cannot justify themselves. Keep asking questions until the problem reaches first principles, and let the parameters converge from the data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The four thorns eventually collapsed into two adjustable controls—trend sensitivity and range looseness—plus one risk preference. Everything else is left to the data, and to the backtest that must determine whether the foundation actually makes money.&lt;/p&gt;

&lt;p&gt;That is the next part of the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Note · The Answer for This Era
&lt;/h2&gt;

&lt;p&gt;To be honest, I do not fully understand all the mathematics in this article. I cannot derive the Kalman filter from first principles, clearly explain why the RLS forgetting factor should be self-consistent in exactly this way, or walk through the stochastic differential equation behind the OU half-life from beginning to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But that is precisely what makes this era so powerful&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To cook a good meal, we do not need to brew the soy sauce and vinegar ourselves. We can still create something delicious. In the age of large language models, the division of capability has quietly shifted: &lt;strong&gt;what matters most is no longer “solving problems,” but “discovering problems through practice.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tools can complete the solution path for us: derivation, modeling, coding, and validation. But the questions “Where does it hurt?”, “Why does it hurt?”, and “What is the real problem?” can only come from real practice.&lt;/p&gt;

&lt;p&gt;I knew where to ask because I spent day after day watching those four thorns appear on the screen. I arrived at a recursive, windowless architecture because I kept insisting that fixed-window indicators were not good enough. &lt;strong&gt;Tools can answer questions, but they cannot decide which questions matter. They can complete the journey, but they cannot choose the direction.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ladder of knowledge is becoming lower. Almost anyone can use tools to stand one step away from an answer. But precisely because answers are becoming easier to obtain, scarcity has shifted elsewhere: &lt;strong&gt;to the ability to see the real problem, and to the judgment required to choose the right path among countless surface-level explanations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Choose the right direction, and the answer will eventually emerge. Choose the wrong direction, and greater computing power will only take you toward the wrong destination faster.&lt;/p&gt;

&lt;p&gt;So this article is less a record of one quantitative strategy than a demonstration:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Someone who does not fully master every piece of the mathematics can still use the tools of this era to remove the problems one by one—as long as they can identify the four thorns precisely in live trading and refuse to settle for a plausible-sounding but superficial fix.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In an era when everyone can obtain answers, &lt;strong&gt;learn to ask the right questions first&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>quantstrategy</category>
      <category>gridtrading</category>
      <category>riskcontrol</category>
      <category>ewy</category>
    </item>
    <item>
      <title>I Spent Tens of Millions of Tokens Reworking a Polymarket ETH 15-Minute Strategy</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 21 Jul 2026 03:05:21 +0000</pubDate>
      <link>https://dev.to/quant001/i-spent-tens-of-millions-of-tokens-reworking-a-polymarket-eth-15-minute-strategy-15lo</link>
      <guid>https://dev.to/quant001/i-spent-tens-of-millions-of-tokens-reworking-a-polymarket-eth-15-minute-strategy-15lo</guid>
      <description>&lt;p&gt;On the very first day GPT 5.6 launched, I wanted to try it out.&lt;/p&gt;

&lt;p&gt;I could have written a small utility, run a few prompts, and called that an evaluation. But that felt a little pointless. To find out whether a new model is actually good, you need to give it a genuinely troublesome project—one full of edge cases and awkward details. So I dug up a Polymarket ETH 15-minute strategy that I had been running for a long time.&lt;/p&gt;

&lt;p&gt;The old version was not unusable. Its win rate even looked fairly decent. The problem was that each winning trade made only a little, while losing trades gave back a lot. That had bothered me for quite a while, so it became a good test of how far GPT 5.6 sol could push the strategy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Let me start with something that sounds exaggerated but is genuinely true: this project consumed tens of millions of GPT 5.6 sol tokens from beginning to end. In the end, I copied the code into FMZ, and it worked on the first try—no errors, it simply started running.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But “it runs” and “it makes money” are two completely different things. That needs to be made clear up front, otherwise the rest of the article could easily be misunderstood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Starting Point: The Win Rate Looked Good, but the Money Did Not Stay
&lt;/h2&gt;

&lt;p&gt;I had already run this Polymarket ETH 15-minute strategy for quite a long time.&lt;/p&gt;

&lt;p&gt;The market is easy to understand: each round lasts 15 minutes. If the ending price is above the opening reference price, the result is Up; if it is below the reference price, the result is Down. It looks extremely simple—almost like flipping a coin.&lt;/p&gt;

&lt;p&gt;The old version actually had a reasonable win rate, but it had an annoying problem: it would usually make a little on each winning trade, then give back a large chunk on one mistake. Sometimes it won several rounds in a row and the equity curve slowly climbed. Then one large loss erased most of the previous progress.&lt;/p&gt;

&lt;p&gt;This is one of the easiest traps to fall into in prediction markets.&lt;/p&gt;

&lt;p&gt;Suppose Up is already trading at 0.85. If you buy it, the most you can make is 0.15, while a loss could cost you 0.85. Roughly speaking, the loss-to-profit ratio is already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.85 / 0.15 ≈ 5.67
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, a high win rate does not necessarily mean profitability. The price you pay, fees, slippage, and the occasional total loss will all come back to settle the account.&lt;/p&gt;

&lt;p&gt;So during this reconstruction, I stopped asking, “How can I make the win rate even higher?” and replaced it with a different question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is the true probability of this contract actually high enough above its market price?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That sounds like a single sentence, but more than 4,000 lines of code ended up dealing with everything required to answer it properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Separate the Three Prices, or Everything Gets Messier
&lt;/h2&gt;

&lt;p&gt;This strategy watches Chainlink, Binance, and Polymarket at the same time. All three provide ETH prices, but they serve completely different purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chainlink RTDS: Official reference price and settlement basis
Binance: Short-term lead, momentum, and volatility observation
Polymarket CLOB: Bid/ask prices, depth, fees, and actual execution

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

&lt;/div&gt;



&lt;p&gt;At first, I also had the instinct to treat Binance as the “real price,” because it updates quickly and has deep liquidity. But that does not work in this market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chainlink Is the Referee
&lt;/h2&gt;

&lt;p&gt;Polymarket-style 15-minute markets determine the outcome using a specified price source. The strategy must capture the official Price-to-Beat at each round boundary, and all later Up or Down calculations are based on that value.&lt;/p&gt;

&lt;p&gt;There is an easy shortcut to take here: if the strategy starts in the middle of a round, can it simply use the Chainlink price observed at startup as the opening price for that round?&lt;/p&gt;

&lt;p&gt;No. A difference of a few dollars may look insignificant, but in the final few dozen seconds it can completely reverse the direction.&lt;/p&gt;

&lt;p&gt;So if the strategy cannot find the official reference price for the current round, it skips the entire round. If it appears to “do nothing” immediately after startup, that does not necessarily mean it is broken. It may simply be waiting for the next round and the correct starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Binance Is More Like a Sideline Observer
&lt;/h2&gt;

&lt;p&gt;Binance ETH/USDT updates quickly and is useful for observing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which direction price has moved over the last five seconds;&lt;/li&gt;
&lt;li&gt;the usual spread between Binance and Chainlink;&lt;/li&gt;
&lt;li&gt;whether volatility has suddenly increased;&lt;/li&gt;
&lt;li&gt;whether Binance has moved slightly ahead of Chainlink.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But Binance does not make the final ruling. It can provide clues, but it cannot replace Chainlink as the settlement authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polymarket Is Where the Purchase Actually Happens
&lt;/h2&gt;

&lt;p&gt;A model probability of 90% is meaningless if the market is already charging nearly 89% after costs.&lt;/p&gt;

&lt;p&gt;For example, if the theoretical win probability is 90% but the total contract acquisition cost is already close to 89%, I would rather skip the trade. It may look almost certain to win, but the available profit margin is paper-thin, and even a small error can wipe it out.&lt;/p&gt;

&lt;p&gt;What the strategy actually cares about is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Net edge = conservative probability - actual fill price - entry fee - exit-cost reserve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&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;netEdge = conservativeProb - cappedFillPrice - entryFee - exitReserve

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

&lt;/div&gt;



&lt;p&gt;Notice that this uses a conservative probability, not the raw model probability. It also does not use the attractive top-of-book quote, but an executable price estimated from market depth. If either assumption is too optimistic, the backtest and live execution can become two completely different systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Only Trade Between 105 and 45 Seconds Before the End?
&lt;/h2&gt;

&lt;p&gt;A round lasts 900 seconds, but the strategy only allows entries during:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T+795 seconds to T+855 seconds

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

&lt;/div&gt;



&lt;p&gt;In other words, between 105 and 45 seconds before settlement.&lt;/p&gt;

&lt;p&gt;Why not search for opportunities from the beginning of the round? Because when the round is still young, ETH has plenty of time to move around. A small lead now could turn into several crossings of the reference price over the next five minutes.&lt;/p&gt;

&lt;p&gt;Then why not simply buy in the final ten seconds, when the outcome is more certain? That does not work well either.&lt;/p&gt;

&lt;p&gt;In the final ten seconds, network latency, disappearing liquidity, and order-status confirmation suddenly become much more important. You may not even know whether the order filled before the market has already ended. If something goes wrong, there is no time left to handle it.&lt;/p&gt;

&lt;p&gt;The 105-to-45-second window is not a magical answer. It is just the current compromise: the result is becoming clearer, while there is still some time left for execution. With enough samples, this window can be divided and studied more carefully later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning “How Far Ahead” into a Probability
&lt;/h2&gt;

&lt;p&gt;The strategy first calculates how far the current Chainlink price is from the round’s reference price:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rawGap = direction × log(current Chainlink price / round reference price)

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

&lt;/div&gt;



&lt;p&gt;When evaluating Up, a price above the reference becomes a positive distance. When evaluating Down, the direction is reversed. The logarithm is mainly used to make upward and downward changes more symmetrical.&lt;/p&gt;

&lt;p&gt;Next, the strategy looks at Binance’s lead residual relative to Chainlink. I intentionally made this adjustment conservative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Binance lead favorable to the current direction is trusted at only 35%;&lt;/li&gt;
&lt;li&gt;an unfavorable move is counted in full;&lt;/li&gt;
&lt;li&gt;the total adjustment is capped at 3 bps;&lt;/li&gt;
&lt;li&gt;another 2 bps is deducted to allow for uncertainty in the reference price.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In simple terms, good news gets discounted first, while bad news is temporarily treated as real. This may cause the strategy to miss some opportunities, but at least the model is less likely to become impressed with its own optimism.&lt;/p&gt;

&lt;p&gt;The strategy then uses recent one-minute candlesticks to estimate short-term volatility and scales it by the remaining time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Remaining volatility ≈ per-second volatility × stress factor × sqrt(remaining seconds + latency buffer)

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

&lt;/div&gt;



&lt;p&gt;This produces a z-score:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z = safe lead distance / remaining volatility

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

&lt;/div&gt;



&lt;p&gt;A 10-dollar lead may be very safe in a quiet market. In a market that can move dozens of dollars within a minute, the same lead means very little. The z-score is an attempt to compare “distance” with “how far the market could still move.”&lt;/p&gt;

&lt;p&gt;The normal cumulative distribution function is then used to convert z into a probability. But the strategy does not trade directly on that probability. It first shrinks it toward 50%, applies another haircut, and constrains the final range.&lt;/p&gt;

&lt;p&gt;The current default thresholds are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z-score ≥ 1.45
Conservative probability ≥ 86%

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

&lt;/div&gt;



&lt;p&gt;These parameters are not mathematical truths. They are simply the starting point of the research. After the strategy has run long enough, they should be revised using actual settlement samples rather than trusted indefinitely because the formula looks elegant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passing the Probability Threshold Still Does Not Mean Buying
&lt;/h2&gt;

&lt;p&gt;This is somewhat counterintuitive. If the model already says the probability is above 86%, why not place the order?&lt;/p&gt;

&lt;p&gt;Because a correct prediction can still be a bad trade.&lt;/p&gt;

&lt;p&gt;The strategy also checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;whether the ask price is inside the permitted range;&lt;/li&gt;
&lt;li&gt;whether the bid-ask spread has widened suddenly;&lt;/li&gt;
&lt;li&gt;whether the complementary prices of Up and Down look abnormal;&lt;/li&gt;
&lt;li&gt;whether the market has jumped suddenly in the last five seconds;&lt;/li&gt;
&lt;li&gt;whether short-term momentum is moving against the position;&lt;/li&gt;
&lt;li&gt;how much net edge remains after fees;&lt;/li&gt;
&lt;li&gt;whether the order book is deep enough to hold the order.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In normal mode, the allowed ask range is approximately 0.65–0.86. In fixed 1 USDC market-order mode, the strategy is more conservative and will only buy up to 0.80.&lt;/p&gt;

&lt;p&gt;Some people may understand the upper limit but wonder why the strategy also refuses to buy below 0.65. Isn’t cheaper always better?&lt;/p&gt;

&lt;p&gt;Under normal conditions, a direction that is clearly leading in the final stage and has a very high model probability should not be absurdly cheap. If the model says 90% while the market is selling it for only 0.55, do not rush to call it a bargain. More likely, some data is out of sync, the reference price is wrong, or the market knows something the strategy does not.&lt;/p&gt;

&lt;p&gt;A cheap price is sometimes an opportunity and sometimes an alarm. Since the program cannot distinguish every case, it chooses not to buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Signal Must Hold, and the Order Book Must Be Read Again
&lt;/h2&gt;

&lt;p&gt;When an entry signal first appears, the strategy does not immediately rush into the market. By default, it requires three consecutive confirmations over at least 800 ms.&lt;/p&gt;

&lt;p&gt;The same quote read repeatedly by the main loop cannot simply be counted as three confirmations. Key checks require new source ticks. Otherwise, a stale price could be mistaken for a stable signal.&lt;/p&gt;

&lt;p&gt;Even after confirmation, the process is not finished. Immediately before placing the order, the strategy will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetch the full REST depth for both Up and Down again;&lt;/li&gt;
&lt;li&gt;rerun the probability and market-condition checks from the beginning;&lt;/li&gt;
&lt;li&gt;recalculate VWAP, fees, and net edge using the new depth;&lt;/li&gt;
&lt;li&gt;refresh the target-side order book separately in 1 USDC market-order mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any condition fails during the recheck, the trade is abandoned.&lt;/p&gt;

&lt;p&gt;This costs some speed, certainly. But I would rather lose a little speed than buy the current market using an order book snapshot from 800 ms ago. In the final stage of a 15-minute round, 800 ms is not always a short time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 1 USDC Order Is Real Trading, Not an API Demonstration
&lt;/h2&gt;

&lt;p&gt;This version includes a dedicated fixed 1 USDC market-buy path, mainly for live testing with a small account.&lt;/p&gt;

&lt;p&gt;Under FMZ’s Polymarket adapter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the amount of a market BUY is specified in USDC;&lt;/li&gt;
&lt;li&gt;the amount of a market SELL is specified in shares;&lt;/li&gt;
&lt;li&gt;limit orders usually have a minimum size of 5 shares;&lt;/li&gt;
&lt;li&gt;market buys can be executed with an order value of 1 USDC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids having to enlarge each trade simply to reach five shares.&lt;/p&gt;

&lt;p&gt;But one calculation cannot be ignored: if the account contains only 9 dollars, a 1-dollar trade plus fees can lose more than 11% of the entire account in the worst case. A few consecutive losses can quickly leave too little capital to continue collecting samples. So “it is only one dollar” is true in absolute terms, but the percentage risk is not small at all.&lt;/p&gt;

&lt;p&gt;The current small-account mode includes these restrictions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no more than two trades per day;&lt;/li&gt;
&lt;li&gt;no more than one unsettled position at a time;&lt;/li&gt;
&lt;li&gt;worst-case risk per trade cannot exceed 15% of account equity;&lt;/li&gt;
&lt;li&gt;if the next trade were a total loss, it cannot breach the session drawdown limit;&lt;/li&gt;
&lt;li&gt;consumed depth cannot exceed 15% of visible market depth;&lt;/li&gt;
&lt;li&gt;permitted entry slippage cannot exceed one percentage point;&lt;/li&gt;
&lt;li&gt;net edge must be at least 6.5%;&lt;/li&gt;
&lt;li&gt;the maximum loss-to-profit ratio cannot exceed 4.5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is another awkward real-world issue: after buying 1 dollar worth of a contract, if the position falls sharply, its sale value may drop below the exchange’s minimum active-sell amount. In other words, you may want to stop out, but the order cannot even be submitted.&lt;/p&gt;

&lt;p&gt;That is why the strategy calculates worst-case loss from the moment of entry under the assumption that the contract may go to zero. It does not assume that a stop-loss order will definitely fill. This is especially important for very small positions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Order Code Is Not Hard. The Hard Part Is Knowing Whether It Filled
&lt;/h2&gt;

&lt;p&gt;Strategy tutorials often show code 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;if (signal) {
    exchange.Buy(price, amount)
}

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

&lt;/div&gt;



&lt;p&gt;Live trading is not that cooperative. A request may time out even though the order was never sent—or even though it already filled. A cancel request may succeed after the order has already been partially filled. WebSocket may report a fill before REST has updated. Worse still, the program may restart immediately after execution and lose all in-memory state.&lt;/p&gt;

&lt;p&gt;If this is not handled properly, the strategy may not only miscalculate returns—it may buy the same position twice.&lt;/p&gt;

&lt;p&gt;That is why a large amount of code is devoted to the order state machine. The rough logic is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Write an intent before sending the order&lt;br&gt;
Persist what the strategy intends to buy in the current round before sending the request to the exchange. If the process crashes in the middle, the restarted strategy still knows that there is an unresolved action to investigate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allow only one intent per round&lt;br&gt;
A brief network failure must not become an extra purchase. Until the previous intent is resolved, the strategy does not create another attempt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check partial fills after cancellation&lt;br&gt;
Cancelling the remainder does not mean nothing was filled. Order reports, trade records, and the actual position must be checked together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Market orders also have an observation period&lt;br&gt;
If the API temporarily shows FAILED or CANCELED, the strategy does not immediately assume zero fill. It cross-checks the order query, User WebSocket, user-trade REST data, and position changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If it cannot be resolved, stop&lt;br&gt;
When the strategy can neither prove that nothing filled nor determine the filled amount, it fails closed, stops trading, and waits for manual verification.&lt;br&gt;
This is somewhat clumsy, but it is more reliable than “send another order and see what happens.” Missing one trade is an opportunity cost. Buying twice by mistake can become an incident.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  After Buying the Right Side, Do Not Rush to Trade Away a Few Cents
&lt;/h2&gt;

&lt;p&gt;The correct side of a prediction-market contract pays out at 1, while the wrong side goes to zero. If the entry logic remains valid, frequent profit-taking often means paying another round of spread and fees.&lt;/p&gt;

&lt;p&gt;The basic idea is therefore to hold winners until official settlement whenever possible and sell only when the risk has genuinely changed.&lt;/p&gt;

&lt;p&gt;The main exit triggers include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chainlink officially crossing the round reference price;&lt;/li&gt;
&lt;li&gt;z-score falling below a hard threshold;&lt;/li&gt;
&lt;li&gt;actual loss reaching the hard stop;&lt;/li&gt;
&lt;li&gt;the current sellable price moving clearly above the model’s fair value;&lt;/li&gt;
&lt;li&gt;key data remaining unavailable near the end of the round;&lt;/li&gt;
&lt;li&gt;a soft exit signal being confirmed across consecutive new ticks. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the position is too small and its sale value does not meet the exchange’s minimum threshold, the strategy does not repeatedly submit an order that is guaranteed to be rejected. It can only wait for official settlement.&lt;/p&gt;

&lt;p&gt;The final result is determined exclusively by Polymarket’s official market state. Whether Binance rose or whether the strategy “believes” it won does not count. Only after official confirmation does the strategy enter the redeem process.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Let It “Pretend to Trade,” Then Talk About Live Execution
&lt;/h2&gt;

&lt;p&gt;The strategy defaults to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var SimMode = true

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

&lt;/div&gt;



&lt;p&gt;Simulation mode does more than display signals on the screen. Opportunities that satisfy all live-trading conditions—and therefore would have triggered a real order—are stored as shadow trades.&lt;/p&gt;

&lt;p&gt;Samples are grouped across three dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entry ask range;&lt;/li&gt;
&lt;li&gt;z-score range;&lt;/li&gt;
&lt;li&gt;time remaining until settlement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the market officially settles, each bucket is updated with the sample count, number of wins, realized profit and loss, model edge, and the one-sided Wilson confidence lower bound.&lt;/p&gt;

&lt;p&gt;Why use something as academic-sounding as the Wilson lower bound? Consider a simple example: if nine out of ten trades win, the observed win rate is 90%, but ten samples are far too few. Nobody knows what the next ten trades will look like. Feeding 90% directly back into the model is an easy way to overestimate performance.&lt;/p&gt;

&lt;p&gt;For live calibration, the strategy uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Effective probability = min(model probability, empirical win-rate confidence lower bound)

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

&lt;/div&gt;



&lt;p&gt;In other words, historical samples are allowed to cool the model down, but they are not allowed to inflate an ordinary signal into a miracle trade.&lt;/p&gt;

&lt;p&gt;The current recommendation is to collect at least 300 shadow samples first. Even when the overall sample count is sufficient, a specific entry bucket still needs at least 25 samples and must pass the net-edge check.&lt;/p&gt;

&lt;p&gt;The number 300 does not mean the strategy “graduates” at that point. It is simply more credible than a dozen or two observations. Market regimes change, and sample distributions can become biased. Those issues still need to be monitored.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Status Panel and JSONL Logs Are More Useful Than a Beautiful Equity Curve
&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%2Fbkuy6c1w5abf4ku32kqd.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%2Fbkuy6c1w5abf4ku32kqd.png" alt=" " width="799" height="332"&gt;&lt;/a&gt;&lt;br&gt;
After a strategy has been running for several days, logs containing only “buy succeeded” and “sell succeeded” are almost useless for research.&lt;/p&gt;

&lt;p&gt;The FMZ status panel displays the current round, reference price, Chainlink and Binance data age, direction, z-score, probability, order-book state, fees, net edge, account risk, shadow samples, recent settlements, and other information.&lt;/p&gt;

&lt;p&gt;Detailed events are also written to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pm_eth15_alpha_v8.jsonl

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

&lt;/div&gt;



&lt;p&gt;The file records why signals were rejected, the volatility at the time, shadow-strategy snapshots, order intents, partial fills, position diagnostics, official settlements, and recovery procedures.&lt;/p&gt;

&lt;p&gt;These logs look verbose in the short term, but after the strategy has run for a while, they become the most valuable part. What I really want to investigate is not simply “how many trades did it win yesterday,” but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is buying at 0.70 or buying at 0.80 more profitable after costs?&lt;/li&gt;
&lt;li&gt;Does a higher z-score actually correspond to a higher win rate?&lt;/li&gt;
&lt;li&gt;How different are the 45–75-second and 75–105-second windows?&lt;/li&gt;
&lt;li&gt;How much of the paper edge was actually realized?&lt;/li&gt;
&lt;li&gt;Was profit lost to fees, slippage, or exit logic?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without preserving the original context, looking back at the equity curve a few days later often leads to explanations that are little more than imagination.&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%2F0zqisq5fsir3czftexkn.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%2F0zqisq5fsir3czftexkn.png" alt=" " width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Did Tens of Millions of Tokens Go?
&lt;/h2&gt;

&lt;p&gt;That number is certainly attention-grabbing. If all I had asked the AI to do was write a moving-average crossover strategy, consuming that many tokens would have been absurd.&lt;/p&gt;

&lt;p&gt;But most of the consumption in this project was not about predicting whether ETH would rise or fall in the next minute. It went into dozens of small but unavoidable edge cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to capture and recover the official reference price;&lt;/li&gt;
&lt;li&gt;how to align timestamps across three data sources;&lt;/li&gt;
&lt;li&gt;how to use Binance only as an auxiliary source without contaminating the settlement logic;&lt;/li&gt;
&lt;li&gt;whether probability shrinkage or fee deduction should be applied first;&lt;/li&gt;
&lt;li&gt;how to account for USDC on market BUY orders and shares in the resulting position;&lt;/li&gt;
&lt;li&gt;minimum order sizes, partial fills, and conflicting order states;&lt;/li&gt;
&lt;li&gt;what happens if the program restarts at exactly the wrong moment;&lt;/li&gt;
&lt;li&gt;how to prevent duplicate orders within the same round;&lt;/li&gt;
&lt;li&gt;whether the shadow strategy and future live strategy use exactly the same conditions;&lt;/li&gt;
&lt;li&gt;when redemption is allowed after official settlement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these issues is especially profound by itself. Combined, they become exhausting. Change one convention and the status panel, position accounting, risk calculation, and logs may all need to be updated. A lot of tokens were spent repeatedly checking these interactions.&lt;/p&gt;

&lt;p&gt;The most interesting thing about “first try, zero errors” was not that the AI had proved it could make money. It was that such a long FMZ strategy started successfully the first time it was pasted in, with WebSocket connections, the status panel, simulation flow, and logging all running. From an engineering perspective, that saved a great deal of frustration.&lt;/p&gt;

&lt;p&gt;Whether there is any Alpha still has to be answered honestly by the samples. Long code does not automatically create a higher win rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Plan to Run It, Do Not Disable Simulation Too Quickly
&lt;/h2&gt;

&lt;p&gt;My own sequence would look roughly like this.&lt;/p&gt;

&lt;p&gt;First, keep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var SimMode = true

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

&lt;/div&gt;



&lt;p&gt;Confirm that Chainlink, Binance, and Polymarket are all connected correctly, that the data-age indicators in the status panel do not frequently turn red, and that the JSONL file continues to update.&lt;/p&gt;

&lt;p&gt;It is best to start the strategy shortly before a new round begins. If the strategy skips the current round after starting midway through it, do not rush to remove the protection logic. Wait for the next round and check whether the reference price is captured correctly.&lt;/p&gt;

&lt;p&gt;Then collect shadow samples. Three hundred trades are only the first threshold. What matters more is the sample count in each bucket, the Wilson lower bound, average realized P&amp;amp;L, and the gap between theoretical edge and realized edge.&lt;/p&gt;

&lt;p&gt;Next, examine fees before the win rate. Prediction markets are very good at creating the illusion of success: the trade history is full of green, but the account balance barely moves. Only after including actual fill prices, fees, and full losses can you determine whether the expectation is positive.&lt;/p&gt;

&lt;p&gt;Only then should you consider very small live trades. Even if every trade is fixed at just 1 USDC, you still need to confirm that the account setup, signing method, User WebSocket, minimum-order rules, and current FMZ adapter behavior all match.&lt;/p&gt;

&lt;p&gt;Only after everything is ready should you switch to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var SimMode = false

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

&lt;/div&gt;



&lt;p&gt;A 9-dollar account can theoretically run the 1-dollar mode, but the margin for error is extremely small. It is more suitable for verifying real execution and accounting than for proving long-term strategy performance. A few consecutive losses could end the sample experiment early.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Final Thoughts
&lt;/h2&gt;

&lt;p&gt;polymarket_eth15m_alpha_v8 appears to predict whether ETH will finish Up or Down, but most of the time it is actually rejecting trades.&lt;/p&gt;

&lt;p&gt;If the reference price is uncertain, do nothing. If data is stale, do nothing. If the spread is too wide, do nothing. If the fees erase the edge, do nothing. If depth is insufficient, do nothing. If the order state cannot be resolved, stop entirely.&lt;/p&gt;

&lt;p&gt;The actual order may take only a moment, but it is preceded by a long series of “no.”&lt;/p&gt;

&lt;p&gt;In the past, when a strategy had a signal but did not buy, I felt as though it had missed money. Now I am more inclined to think that filtering out high-win-rate trades that are still economically unattractive may be the most valuable feature of this version.&lt;/p&gt;

&lt;p&gt;GPT 5.6 sol, tens of millions of tokens, first-run success, zero errors—of course that makes for a good story. I also find it somewhat unbelievable.&lt;/p&gt;

&lt;p&gt;But whether the strategy survives will not be determined by token count or lines of code. It comes down to one thing:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is the probability sold to you by the market genuinely cheaper than what it is worth?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The rest can be answered slowly by the samples.&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%2F1ra9dlh3vsloxk58pzpf.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%2F1ra9dlh3vsloxk58pzpf.png" alt=" " width="799" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Strategy Source Code
&lt;/h2&gt;

&lt;p&gt;Finally, I am sharing the strategy with everyone. The source code below can be copied directly into an FMZ JavaScript strategy.&lt;/p&gt;

&lt;p&gt;The source defaults to SimMode = true. After copying it, readers will first enter simulation and shadow-sample mode. Live trading requires changing the mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risk Warning
&lt;/h2&gt;

&lt;p&gt;This article is only a discussion of strategy research and program design. It does not constitute investment advice and does not promise returns. Prediction-market contracts can fall to zero and involve liquidity, slippage, API, fee, settlement, regulatory, and smart-contract risks. A strategy starting with zero errors does not mean the logic has zero risk, nor does it mean the strategy can generate stable profits in the future. Before live trading, conduct sufficient simulation, shadow-sample calibration, and small-scale validation.&lt;/p&gt;

</description>
      <category>polymarket</category>
      <category>quanttrading</category>
      <category>gpt5</category>
      <category>crypto</category>
    </item>
    <item>
      <title>If You Learn Slowly Enough, You Won’t Need to Learn Anything—Applying the TabFM Prediction Framework to Quantitative Trading</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 14 Jul 2026 08:56:20 +0000</pubDate>
      <link>https://dev.to/quant001/if-you-learn-slowly-enough-you-wont-need-to-learn-anything-applying-the-tabfm-prediction-42e8</link>
      <guid>https://dev.to/quant001/if-you-learn-slowly-enough-you-wont-need-to-learn-anything-applying-the-tabfm-prediction-42e8</guid>
      <description>&lt;p&gt;Recently, Google Research released TabFM, a foundation model designed for tabular classification and regression tasks.&lt;/p&gt;

&lt;p&gt;It attempts to compress model training, hyperparameter search, and complex feature engineering in traditional tabular machine learning into a more direct workflow: give the model a set of labeled historical samples, provide one new row, and let the model make a prediction in context. (Google Research)&lt;/p&gt;

&lt;p&gt;When I first saw this framework, the first application that came to mind was quantitative trading.&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%2F8kg8m202bnlnz4j7v96v.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%2F8kg8m202bnlnz4j7v96v.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Quantitative research has always been about producing tables. Candlestick data, trading volume, funding rates, basis, and open interest all eventually get organized into rows and passed to a model.&lt;/p&gt;

&lt;p&gt;This time, we adopted a very direct approach:&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%2Fi9seo5h6mpx99s1axekp.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%2Fi9seo5h6mpx99s1axekp.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Do not calculate RSI. Do not calculate MACD. Do not use moving averages or predesign complex factors. Simply convert a sequence of candlesticks into a time window and let TabFM determine whether the next candlestick is more likely to move up, move down, or remain flat.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This does not conflict with the traditional approach of calculating moving averages, RSI, or ATR.&lt;/p&gt;

&lt;p&gt;Traditional indicators also begin by manually defining a time window and then use a fixed formula to extract features from that window.&lt;/p&gt;

&lt;p&gt;The difference is that, in the past, we first decided whether the model should observe averages, directional strength, or volatility. Now, we only provide the raw time window and let TabFM search for potentially useful relationships on its own.&lt;/p&gt;

&lt;p&gt;That is the most interesting part of this framework.&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%2Fo0d1paearnz0y2io8kwk.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%2Fo0d1paearnz0y2io8kwk.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Turning a Time Window into a Single Row
&lt;/h2&gt;

&lt;p&gt;Suppose we define a candlestick window with a length of &lt;em&gt;window&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Each candlestick contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open&lt;/li&gt;
&lt;li&gt;High&lt;/li&gt;
&lt;li&gt;Low&lt;/li&gt;
&lt;li&gt;Close&lt;/li&gt;
&lt;li&gt;Volume
Each sample uses the &lt;em&gt;window&lt;/em&gt; completed candlesticks immediately preceding the target candlestick. The label is the direction of the target candlestick that follows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, when preparing to predict the next candlestick, the latest input consists of a number of candlesticks that have already completed.&lt;/p&gt;

&lt;p&gt;Historical training samples are then shifted backward one candlestick at a time:&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%2Fjmym8tim74q9e5uri0zn.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%2Fjmym8tim74q9e5uri0zn.png" alt=" " width="800" height="142"&gt;&lt;/a&gt;&lt;br&gt;
Training samples are ordered from newest to oldest.&lt;/p&gt;

&lt;p&gt;Within each row, the most recent data also comes first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lag_1 represents the candlestick closest to the target;&lt;/li&gt;
&lt;li&gt;lag_2 represents the candlestick two periods before the target;&lt;/li&gt;
&lt;li&gt;lag_window represents the earliest candlestick in the window.
The feature-column generation code is as follows:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def make_feature_columns(window):
    columns = []

    for lag in range(1, window + 1):
        prefix = "lag_{}".format(lag)

        columns.extend([
            prefix + "_open",
            prefix + "_high",
            prefix + "_low",
            prefix + "_close",
            prefix + "_volume",
        ])

    return columns

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

&lt;/div&gt;


&lt;p&gt;If the window length is 15, each row contains 75 fields. If the window length is 24, each row contains 120 fields.&lt;/p&gt;

&lt;p&gt;The window length, candlestick timeframe, and number of historical samples are all parameters. More suitable combinations can be found through rolling backtests or parameter search; none of them are fixed answers.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Why Not Feed Absolute Prices Directly into the Model?
&lt;/h2&gt;

&lt;p&gt;Although we use raw candlestick data, we should not pass absolute prices to the model unchanged.&lt;/p&gt;

&lt;p&gt;For example, after BTC rises from 30,000 to 100,000, the price scale has changed significantly. If absolute values are used directly, the model may incorrectly treat the price level itself as a stable pattern.&lt;/p&gt;

&lt;p&gt;Therefore, each row uses the closing price closest to the prediction target as an anchor.&lt;/p&gt;

&lt;p&gt;Prices are converted into percentage changes relative to the anchor, while volume is divided by the average volume within 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;def make_raw_kline_row(
    bars,
    target_idx,
    window,
):
    anchor_close = float(
        bars[target_idx - 1]["Close"]
    )

    if anchor_close &amp;lt;= 0:
        anchor_close = 1.0

    volumes = [
        float(
            bars[target_idx - lag].get(
                "Volume",
                0.0,
            )
        )
        for lag in range(1, window + 1)
    ]

    mean_volume = (
        sum(volumes) / len(volumes)
        if volumes
        else 1.0
    )

    if mean_volume &amp;lt;= 0:
        mean_volume = 1.0

    row = {}

    for lag in range(1, window + 1):
        bar = bars[target_idx - lag]
        prefix = "lag_{}".format(lag)

        row[prefix + "_open"] = (
            float(bar["Open"]) - anchor_close
        ) / anchor_close

        row[prefix + "_high"] = (
            float(bar["High"]) - anchor_close
        ) / anchor_close

        row[prefix + "_low"] = (
            float(bar["Low"]) - anchor_close
        ) / anchor_close

        row[prefix + "_close"] = (
            float(bar["Close"]) - anchor_close
        ) / anchor_close

        row[prefix + "_volume"] = (
            float(bar.get("Volume", 0.0))
            / mean_volume
        )

    return row

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

&lt;/div&gt;



&lt;p&gt;No traditional indicators are calculated here.&lt;/p&gt;

&lt;p&gt;We are only normalizing the price and volume scales across different periods.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What Does the Model Need to Predict?
&lt;/h2&gt;

&lt;p&gt;This time, the task is defined as a three-class classification problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;up&lt;/li&gt;
&lt;li&gt;down&lt;/li&gt;
&lt;li&gt;flat
The label represents the change in the target candlestick relative to the previous closing price.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def make_next_bar_label(
    bars,
    target_idx,
    threshold,
):
    previous_close = float(
        bars[target_idx - 1]["Close"]
    )

    target_close = float(
        bars[target_idx]["Close"]
    )

    if previous_close &amp;lt;= 0:
        return "flat"

    target_return = (
        target_close / previous_close - 1.0
    )

    if target_return &amp;gt; threshold:
        return "up"

    if target_return &amp;lt; -threshold:
        return "down"

    return "flat"

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;threshold&lt;/em&gt; is used to separate upward movement, downward movement, and flat movement.&lt;/p&gt;

&lt;p&gt;This threshold is not fixed either.&lt;/p&gt;

&lt;p&gt;Different instruments, candlestick timeframes, and trading costs require different reasonable thresholds.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. How Historical Samples Are Generated
&lt;/h2&gt;

&lt;p&gt;Historical data begins with the latest sample and gradually moves backward in time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def build_dataset(
    bars,
    window,
    train_rows,
    return_threshold,
):
    minimum_bars = window + train_rows

    if len(bars) &amp;lt; minimum_bars:
        raise ValueError(
            "Insufficient candlestick data. At least {} bars are required.".format(
                minimum_bars
            )
        )

    rows = []
    labels = []

    latest_known_target_idx = len(bars) - 1

    for offset in range(train_rows):
        target_idx = (
            latest_known_target_idx - offset
        )

        rows.append(
            make_raw_kline_row(
                bars=bars,
                target_idx=target_idx,
                window=window,
            )
        )

        labels.append(
            make_next_bar_label(
                bars=bars,
                target_idx=target_idx,
                threshold=return_threshold,
            )
        )

    latest = make_raw_kline_row(
        bars=bars,
        target_idx=len(bars),
        window=window,
    )

    return rows, labels, latest

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

&lt;/div&gt;



&lt;p&gt;The final result contains three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;rows:&lt;/em&gt; historical time windows;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;labels:&lt;/em&gt; the actual direction of the candlestick following each window;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;latest:&lt;/em&gt; the latest time window, used to predict the next candlestick that has not yet appeared.
Here, &lt;em&gt;rows[0]&lt;/em&gt; is the latest historical sample, and subsequent rows become progressively older.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meanwhile, latest uses only candlesticks that have already completed and contains no future data.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. How Is This Different from Moving Averages and RSI?
&lt;/h2&gt;

&lt;p&gt;Suppose we observe the same time window.&lt;/p&gt;

&lt;p&gt;A traditional approach would first define indicators manually.&lt;/p&gt;

&lt;p&gt;For example, a moving average:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ma = sum(close_list[-window:]) / window

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

&lt;/div&gt;



&lt;p&gt;RSI calculates the average strength of upward and downward movements within the window.&lt;/p&gt;

&lt;p&gt;ATR calculates true range.&lt;/p&gt;

&lt;p&gt;What these methods have in common is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A human first decides which relationship is useful, then compresses the entire time window into a single indicator.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;TabFM takes a different approach.&lt;/p&gt;

&lt;p&gt;We still define the time window, but we do not specify in advance what the model must observe.&lt;/p&gt;

&lt;p&gt;The model can directly read all OHLCV fields inside the window and attempt to identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether closing prices are rising continuously;&lt;/li&gt;
&lt;li&gt;whether highs and lows are moving upward together;&lt;/li&gt;
&lt;li&gt;whether candlestick bodies are expanding;&lt;/li&gt;
&lt;li&gt;whether upper and lower shadows are changing;&lt;/li&gt;
&lt;li&gt;whether volume is increasing;&lt;/li&gt;
&lt;li&gt;whether volatility is contracting or expanding;&lt;/li&gt;
&lt;li&gt;whether combined relationships exist across different positions in the window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional indicators are handcrafted features.&lt;/p&gt;

&lt;p&gt;Here, the raw window is passed to a foundation model, allowing the model to search for potentially useful feature combinations on its own.&lt;/p&gt;

&lt;p&gt;This is the aspect of TabFM that deserves the most attention in quantitative applications.&lt;/p&gt;

&lt;p&gt;We still define the time range and prediction target, but we no longer have to enumerate a large number of indicators in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. It Is Not TimesFM
&lt;/h2&gt;

&lt;p&gt;TabFM is not a dedicated time-series model.&lt;/p&gt;

&lt;p&gt;Google's TimesFM is the foundation model designed to read continuous time series directly and predict future sequences. (Google Research)&lt;/p&gt;

&lt;p&gt;TabFM receives a table with a fixed column structure.&lt;/p&gt;

&lt;p&gt;In our data, temporal relationships are expressed manually through field positions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;lag_1&lt;/em&gt; is always the most recent candlestick;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;lag_2&lt;/em&gt; is always the candlestick two periods back;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;lag&lt;/em&gt;_window is always the earliest candlestick in the window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, a more accurate description is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We use a manually defined time window to convert a time-series problem into a tabular classification problem, and then use TabFM to make the prediction.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is similar to the use of lag features in traditional machine learning.&lt;/p&gt;

&lt;p&gt;The difference is that TabFM is a tabular foundation model. It uses historical samples as context and does not require retraining a new set of model parameters specifically for the current dataset. (Google Research)&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Calling TabFM
&lt;/h2&gt;

&lt;p&gt;Once the data has been prepared, the actual model invocation code is very short.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import numpy as np
import pandas as pd

from tabfm import TabFMClassifier
from tabfm import tabfm_v1_0_0_jax


model = tabfm_v1_0_0_jax.load(
    model_type="classification"
)

classifier = TabFMClassifier(
    model=model,
    random_state=42,
)

rows, labels, latest = build_dataset(
    bars=bars,
    window=window,
    train_rows=train_rows,
    return_threshold=return_threshold,
)

x_train = pd.DataFrame(rows)
y_train = np.asarray(labels)

x_test = pd.DataFrame(
    [latest],
    columns=x_train.columns,
)

classifier.fit(
    x_train,
    y_train,
)

probabilities = (
    classifier.predict_proba(x_test)[0]
)

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

&lt;/div&gt;



&lt;p&gt;Organize the prediction results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;probs = {
    str(class_name): float(probability)
    for class_name, probability in zip(
        classifier.classes_,
        probabilities,
    )
}

for class_name in (
    "up",
    "flat",
    "down",
):
    probs.setdefault(
        class_name,
        0.0,
    )

label = max(
    probs,
    key=probs.get,
)

confidence = probs[label]

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

&lt;/div&gt;



&lt;p&gt;The result is a probability for whether the next candlestick will move up, move down, or remain flat.&lt;/p&gt;

&lt;p&gt;TabFM's fit() is not traditional retraining either.&lt;/p&gt;

&lt;p&gt;Historical samples are primarily used as contextual input, allowing the model to infer the relationship between features and labels in the current table before predicting the latest sample. (GitHub)&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%2Fm2sqzxtjucsxpbvq0u8d.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%2Fm2sqzxtjucsxpbvq0u8d.png" alt=" " width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Installation Notes
&lt;/h2&gt;

&lt;p&gt;TabFM currently requires Python 3.11.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conda create -n py311 python=3.11 -y
conda activate py311

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

&lt;/div&gt;



&lt;p&gt;Install the JAX backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/google-research/tabfm.git
cd tabfm

python -m pip install \
    --upgrade pip setuptools wheel

python -m pip install -e '.[jax]'

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

&lt;/div&gt;



&lt;p&gt;The pretrained weights will also be downloaded the first time the model is loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model = tabfm_v1_0_0_jax.load(
    model_type="classification"
)

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

&lt;/div&gt;



&lt;p&gt;In this experiment, the JAX model download was approximately 6 GB.&lt;/p&gt;

&lt;p&gt;The pretrained weights currently use the TabFM non-commercial license. Before using them in production or for commercial purposes, you should verify the scope of the license yourself. (Hugging Face)&lt;/p&gt;

&lt;p&gt;When using an FMZ Quant local host, you also need to confirm that the Python interpreter used by the strategy is the same environment in which TabFM was installed.&lt;br&gt;
&lt;/p&gt;

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

Log(
    "Python executable: "
    + sys.executable
)

Log(
    "Python version: "
    + sys.version
)

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

&lt;/div&gt;



&lt;p&gt;If TabFM can be imported successfully in the terminal but the strategy reports that the module cannot be found, the local host is usually using a different Python environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Why It Is Currently Better Suited to Low-Frequency Prediction
&lt;/h2&gt;

&lt;p&gt;TabFM is not currently a lightweight model.&lt;/p&gt;

&lt;p&gt;The first run involves model loading, JAX initialization, XLA compilation, and CPU inference, making it significantly slower than traditional models such as LightGBM and XGBoost.&lt;/p&gt;

&lt;p&gt;For that reason, it is not currently suitable for tick-by-tick, second-level, or order-book-level prediction.&lt;/p&gt;

&lt;p&gt;A more practical approach is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make one prediction when the strategy starts;&lt;/li&gt;
&lt;li&gt;update predictions at fixed intervals afterward;&lt;/li&gt;
&lt;li&gt;continue using conventional code for position monitoring, stop-loss 
handling, and trade execution.
The prediction frequency should ideally match the candlestick timeframe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, when using hourly candlesticks, predict the next one after a new hourly candlestick has completed. When using 15-minute candlesticks, update the prediction after each 15-minute candlestick closes.&lt;/p&gt;

&lt;p&gt;To control inference frequency, the complete example in this experiment runs once at startup and then updates once every clock hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. What This Experiment Actually Demonstrates
&lt;/h2&gt;

&lt;p&gt;This experiment does not prove that TabFM can beat the market.&lt;/p&gt;

&lt;p&gt;It has not undergone complete out-of-sample testing, nor has it been systematically compared with traditional indicators, LightGBM, XGBoost, or dedicated time-series models.&lt;/p&gt;

&lt;p&gt;But it demonstrates a change that deserves attention.&lt;/p&gt;

&lt;p&gt;In the past, when facing a candlestick window, our first question was which indicators we should calculate.&lt;/p&gt;

&lt;p&gt;Now, we can preserve the raw data inside the window first and let a foundation model search for potentially useful combinations on its own.&lt;/p&gt;

&lt;p&gt;The time window is still defined by humans.&lt;/p&gt;

&lt;p&gt;The labels are still defined by humans.&lt;/p&gt;

&lt;p&gt;The window length, data timeframe, and number of samples still need to be selected through backtesting.&lt;/p&gt;

&lt;p&gt;But the features extracted from inside the window no longer have to be specified entirely in advance by humans.&lt;/p&gt;

&lt;p&gt;That is the clever part of TabFM.&lt;/p&gt;

&lt;p&gt;What it lowers is not the barrier to understanding the market, but the operational barrier to feature engineering and model training.&lt;/p&gt;

&lt;p&gt;So the idea that "if you learn slowly enough, you do not need to learn anything at all" does not mean that knowledge is no longer important.&lt;/p&gt;

&lt;p&gt;What has actually changed is this:&lt;/p&gt;

&lt;p&gt;In the past, we might have needed to learn a large number of indicators, models, and parameters before starting a prediction experiment.&lt;/p&gt;

&lt;p&gt;Now, we can begin by answering a few more fundamental questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long should the observation window be?&lt;/li&gt;
&lt;li&gt;What should the prediction target be?&lt;/li&gt;
&lt;li&gt;How should the labels be defined?&lt;/li&gt;
&lt;li&gt;Which historical samples should be provided?&lt;/li&gt;
&lt;li&gt;How should we verify whether the prediction has trading value?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part of the remaining feature-discovery work can be delegated to the foundation model.&lt;/p&gt;

&lt;p&gt;Models will become increasingly general, and the code will become shorter.&lt;/p&gt;

&lt;p&gt;But what is worth predicting, how the data should be organized, and whether the result can actually be traded are still questions that researchers must answer for themselves.&lt;/p&gt;

</description>
      <category>quant</category>
      <category>trading</category>
      <category>tabfm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Loop Engineer Trading: Let Quantitative Strategies Write Their Own Rules</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 14 Jul 2026 08:20:26 +0000</pubDate>
      <link>https://dev.to/quant001/loop-engineer-trading-let-quantitative-strategies-write-their-own-rules-a6o</link>
      <guid>https://dev.to/quant001/loop-engineer-trading-let-quantitative-strategies-write-their-own-rules-a6o</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: From Harness Engineer to Loop Engineer
&lt;/h2&gt;

&lt;p&gt;Some time ago, the idea of the "Harness Engineer" gained traction in quantitative trading circles. Its core principle is not to pick an answer based on intuition, but to build a framework in which multiple candidates can compete under a unified set of evaluation criteria. Applied to trading, this means placing multiple instruments, parameter sets, and rule systems into the same backtesting or evaluation framework, then using historical data and a shared objective function to identify the relatively better combination.&lt;/p&gt;

&lt;p&gt;This approach solves &lt;strong&gt;a horizontal selection problem&lt;/strong&gt;: at a given point in time, choose the best-performing option from a pool of candidates. It is effective, but it also has an inherent limitation: the selected answer is only the "relative optimum within the historical sample." Once that round is complete, the framework itself does not actually become smarter. Run it again, and it is still essentially repeating the same scoring logic.&lt;/p&gt;

&lt;p&gt;But what if the real question is not "Which one should I choose?" but rather "How can the system continuously accumulate its own experience while it is running?"&lt;/p&gt;

&lt;p&gt;That is the direction I have become more interested in recently. For now, we can call it &lt;strong&gt;Loop Engineer&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%2Fp4o5njujqbvgco1z3uiz.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%2Fp4o5njujqbvgco1z3uiz.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Instead of building a one-off scoring framework that ends after a single run, build a closed loop of "decision → execution → review → update," allowing every signal, trade, and outcome to become experience that can be reused in the next decision.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The "evolution" discussed here is not online training in the strict machine-learning sense, nor does it involve updating the weights of a large language model. More precisely, it is the &lt;strong&gt;continuous updating of an external experience base&lt;/strong&gt;: the system writes trade reviews into a database, summarizes them into a playbook, and then lets the next round of decisions read from that playbook. The model itself is not retrained. What changes is the experience available at runtime and a limited set of adjustable parameters.&lt;/p&gt;

&lt;p&gt;If Harness Engineer is concerned with "space"—comparing candidates horizontally within a search space—then Loop Engineer is more concerned with "time"—continuously recording, reviewing, and correcting decisions along the trading timeline. The engineer no longer gives the strategy its final answer directly. Instead, the engineer designs the skeleton of the loop, the evaluation criteria, and the safety boundaries, then lets the system gradually accumulate experience through repeated operation.&lt;/p&gt;

&lt;p&gt;This article discusses a single-instrument AI self-evolving trading system built around that idea. It does not attempt to cover hundreds of cryptocurrencies at once. Instead, it focuses on one instrument—for example, a BTC, gold, or crude-oil perpetual contract—and treats "experience" as the core asset that actually changes while the system is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use a Single Instrument for a Closed-Loop Experiment
&lt;/h2&gt;

&lt;p&gt;Harness-style frameworks are naturally suited to casting a wide net: the larger the candidate pool, the more meaningful horizontal comparison becomes. The trade-off is that the information accumulated for each candidate is usually shallow. Each coin may only end up with a few backtest scores, which is not enough to constitute meaningful "individual experience."&lt;/p&gt;

&lt;p&gt;The Loop Engineer approach works in the opposite direction. It is better suited to beginning with deep observation of a single instrument. The volatility profile, sensitivity to news, false-breakout patterns, and trend-continuation behavior of a given market often have their own distinctive character. These traits are easily averaged away when mixed into a horizontal comparison across hundreds of instruments. But if the system continuously observes and reviews the same instrument, it may repeatedly encounter those traits and eventually turn them into reusable rules.&lt;/p&gt;

&lt;p&gt;Of course, this does not mean that a single instrument naturally makes it easier to learn stable patterns. Quite the opposite: the biggest problem with a single-instrument system is that samples accumulate slowly, statistical evidence remains weak, and the system is especially prone to mistaking short-term noise for a genuine pattern during its early stages. A more accurate statement is that a single instrument provides a consistent trading context in which to observe how closed-loop experience accumulates, but it does not automatically make that experience reliable.&lt;/p&gt;

&lt;p&gt;That is why this system should initially be treated as something to observe and iterate on, rather than as a mature live-trading system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Breakdown: One Closed Loop, Five Stages
&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%2Fehg304n1s24735nbw9nz.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%2Fehg304n1s24735nbw9nz.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
The system operates through five stages: "perception → decision → execution → review → update." There are two primary information channels in the middle: &lt;strong&gt;technical data&lt;/strong&gt; and &lt;strong&gt;news data&lt;/strong&gt;. The technical side comes from indicator snapshots, while the news side comes from search and caching. Both are passed to the decision layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Perception Layer: Translating the Market into Structured Language&lt;/strong&gt;&lt;br&gt;
The system first calculates a set of technical indicators covering trend, momentum, volatility, and volume, including EMA, MACD, RSI, Bollinger Bands, ATR, and KDJ. Instead of sending a loose collection of numbers directly to the model, it assembles them into a structured snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;snapshot = {
    "price": price,
    "trend": {
        "ema_fast": ema_fast,
        "ema_slow": ema_slow,
        "macd_hist": hist,
        "macd_hist_rising": hist &amp;gt; hist_prev,
    },
    "momentum": {
        "rsi14": rsi_v,
        "kdj_k": k_v,
        "kdj_d": d_v,
    },
    "volatility": {
        "boll_upper": boll_upper,
        "boll_lower": boll_lower,
        "atr_pct": atr_v / price * 100,
    },
    "volume": {
        "vol_ratio_to_ma20": vol_ratio,
    },
}

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

&lt;/div&gt;



&lt;p&gt;The point of this step is not to make the model "mysteriously understand the market." It is to translate the market state into structured language that the model can process more easily. The current conditions of trend, momentum, volatility, and volume are all explicitly represented in separate fields.&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%2F256oig87cigupuiomy1o.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%2F256oig87cigupuiomy1o.png" alt=" " width="799" height="266"&gt;&lt;/a&gt;&lt;br&gt;
The news layer retrieves instrument-related news through a search interface and stores deduplicated results in a cache. It does not generate trading signals directly. Instead, it provides the raw material for evaluating the news environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Decision Layer: Technicals and News Must at Least Not Contradict Each Other&lt;/strong&gt;&lt;br&gt;
The indicator snapshot, recent news, current position status, and the system's active playbook are packaged together and passed to the large language model. The model must output a structured trading signal containing the action, direction, confidence score, technical view, news view, and rationale.&lt;/p&gt;

&lt;p&gt;One hard rule applies here: &lt;strong&gt;the system may not open or add to a position when the technical and news views conflict.&lt;/strong&gt; If the news environment is clearly bearish while the technical indicators suggest going long, the model should return a hold or close action rather than forcing a new position.&lt;/p&gt;

&lt;p&gt;The prompt explicitly includes constraints 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;"1. Only return open_long/open_short/add_long/add_short when technical_view "
"does not conflict with news_view (news_view is neutral/no_data, or its "
"direction is consistent with technical_view).",
"2. If news_view points in the opposite direction from technical_view, "
"action must be hold or close.",
"4. confidence must honestly reflect the level of certainty (0-100). "
"Do not habitually assign high scores; use a low score when conviction is weak.",

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

&lt;/div&gt;



&lt;p&gt;It is important to note that "no conflict" is only a necessary condition for opening a position, not a sufficient one. In other words, the absence of conflict between technicals and news does not mean the system must trade. The model still needs to determine whether the signal is strong enough, and then output &lt;em&gt;open_long, open_short&lt;/em&gt;, or &lt;em&gt;hold&lt;/em&gt;. The execution layer performs an additional check to ensure that confidence exceeds the required threshold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if action in ("open_long", "open_short") and not has_pos:
    if decision["confidence"] &amp;lt; min_conf:
        return
    open_position(store, decision, ticker, add_mode=False)

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

&lt;/div&gt;



&lt;p&gt;Therefore, a case in which "the news view is bullish, the technical view is neutral, and the final action is still hold" is fully consistent with the system design. It means that the news has a directional bias, but the technical side does not provide enough confirmation, so the system remains inactive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Execution Layer: Parameters Come from the Playbook, but Hard Boundaries Remain&lt;/strong&gt;&lt;br&gt;
One difference between this system and a traditional fixed-parameter strategy is that execution parameters are not completely hard-coded into the strategy logic. Position size, stop-loss percentage, trailing-profit activation threshold, and trailing giveback threshold are read from the current version of the playbook whenever possible.&lt;/p&gt;

&lt;p&gt;That does not mean the AI can change parameters arbitrarily. Initial values, upper and lower limits, maximum position size, maximum number of additions, leverage, and other boundaries are still defined manually. The playbook can only provide the current parameter values within those boundaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def playbook_param(store, key, config_default, lo, hi):
    v = store.get("current_playbook", {}).get("params", {}).get(key, config_default)
    try:
        v = float(v)
    except Exception:
        v = config_default
    return clamp(v, lo, hi)

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

&lt;/div&gt;



&lt;p&gt;Each time the system executes a decision, it reads the current playbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hard_stop_pct = playbook_param(
    store,
    "hard_stop_pct",
    HARD_STOP_PCT,
    HARD_STOP_PCT_MIN,
    HARD_STOP_PCT_MAX,
)

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

&lt;/div&gt;



&lt;p&gt;Position size is not a fixed percentage either. It is calculated as "base position size × confidence for the current decision." The lower the confidence, the smaller the actual position. Only when confidence is high does the position approach the upper limit implied by the base position size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base_pos_pct = playbook_param(
    store,
    "base_pos_pct",
    BASE_POS_PCT,
    BASE_POS_PCT_FLOOR,
    BASE_POS_PCT_CEIL,
)

size_pct = base_pos_pct * clamp(decision["confidence"] / 100.0, 0.4, 1.0)

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

&lt;/div&gt;



&lt;p&gt;The key is not to let the model decide everything, but to give the system a controllable entry point for parameter updates. The AI may suggest adjustments, but the final values used for execution must always be clipped by the code layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Review Layer: Experience Is Generated Only After a Complete Trade Ends&lt;/strong&gt;&lt;br&gt;
After each complete open-to-close trade, the system passes the full trade context to the large language model for review, including the entry rationale, direction, profit or loss, and reason for closing. The model must output a specific lesson in Chinese rather than a vague summary such as "execution error" or "bad market conditions."&lt;/p&gt;

&lt;p&gt;A better lesson would look like this:&lt;/p&gt;

&lt;p&gt;_Going long after RSI exceeds 70 while the news environment is bearish can easily lead to getting trapped. Wait for a pullback or skip the trade.&lt;br&gt;
_&lt;br&gt;
The review result is stored in the database together with a classification of the mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;schema = {
    "outcome": "win|loss|breakeven",
    "mistake_type": "none|chased_move|ignored_news_conflict|"
                    "stop_too_tight|stop_too_wide|overleveraged|held_too_long|other",
    "lesson": "",
    "tags": [""],
}

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

&lt;/div&gt;



&lt;p&gt;One point needs to be emphasized: a review does not happen immediately after every model decision. It only happens after the trade has actually ended. Opening a position only records the decision. The system cannot know whether the trade was a win, a loss, or roughly breakeven until the position has been closed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Update Layer: Turning Recent Reviews into a New Playbook&lt;/strong&gt;&lt;br&gt;
Once enough review records have accumulated, or a fixed time window has elapsed, the system triggers a playbook update. It reads recent reviews, the overall win rate, and profit-and-loss statistics, then asks the large language model to generate a new experience manual containing updated do/avoid rules and suggested adjustments to selected parameters.&lt;/p&gt;

&lt;p&gt;However, suggested parameters are not accepted unconditionally. Each one is clipped to manually defined safety limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bounds = {
    "min_confidence": (MIN_CONFIDENCE_FLOOR, MIN_CONFIDENCE_CEIL),
    "base_pos_pct": (BASE_POS_PCT_FLOOR, BASE_POS_PCT_CEIL),
    "hard_stop_pct": (HARD_STOP_PCT_MIN, HARD_STOP_PCT_MAX),
    "trail_activate_pct": (TRAIL_ACTIVATE_PCT_MIN, TRAIL_ACTIVATE_PCT_MAX),
    "trail_giveback_pct": (TRAIL_GIVEBACK_PCT_MIN, TRAIL_GIVEBACK_PCT_MAX),
}

for k, (lo, hi) in bounds.items():
    if k in suggested:
        new_params[k] = round(clamp(float(suggested[k]), lo, hi), 4)

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

&lt;/div&gt;



&lt;p&gt;The clipped parameters and new rules are written back to the database and become the current playbook used by the next round of decisions.&lt;/p&gt;

&lt;p&gt;This is the real meaning of "self-evolution" in this system: the model weights do not change, but the external experience base and adjustable parameters are updated during operation. The next time the model makes a decision, it sees the new playbook, which can lead it to exhibit different behavioral tendencies while running the same underlying code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evolution Does Not Mean Losing Control: Hard Constraints Inside the Loop
&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%2Flueoc0tvcqza9dguxo0p.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%2Flueoc0tvcqza9dguxo0p.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Giving a system the ability to update its own experience also introduces obvious risks. Without boundaries, several consecutive losing trades could cause the model to derive the wrong lessons and push the system toward even worse parameters.&lt;/p&gt;

&lt;p&gt;That is why this design includes several layers of constraints.&lt;/p&gt;

&lt;p&gt;First, the AI cannot modify the code. It can only fine-tune parameters within manually defined numeric ranges. For example, the stop-loss percentage may only move within a specified upper and lower bound. The range itself is controlled by code or platform settings, and the playbook cannot exceed it.&lt;/p&gt;

&lt;p&gt;Second, hard stop-loss rules, the maximum position size for a single instrument, and the maximum number of position additions are enforced at the code level. These are separate from the soft parameters stored in the playbook. The latter may be updated; the former cannot be relaxed by the AI.&lt;/p&gt;

&lt;p&gt;Third, a new position requires the technical and news views to at least not conflict. A single signal source cannot independently trigger a trade.&lt;/p&gt;

&lt;p&gt;Fourth, the system can run in "notification-only" mode by default, allowing users to observe its signals, reviews, and playbook changes before enabling live order execution.&lt;/p&gt;

&lt;p&gt;For example, the maximum single-instrument exposure and the maximum number of additions are checked directly in the execution layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max_qty = equity * MAX_SINGLE_POS_PCT * LEVERAGE / ticker["Last"] / market["ctVal"]

if cur_qty + qty &amp;gt; max_qty:
    qty = max(0, max_qty - cur_qty)

if pos.get("add_count", 0) &amp;gt;= MAX_TOTAL_ADD_COUNT:
    return

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

&lt;/div&gt;



&lt;p&gt;The underlying principle is simple: the closed loop may adjust "how to do better," but it may not relax the bottom line of "how much risk it is allowed to take."&lt;/p&gt;

&lt;h2&gt;
  
  
  How Far Is It from a Truly Mature Continual-Learning System?
&lt;/h2&gt;

&lt;p&gt;This needs to be stated clearly: the system is still a long way from a truly mature continual-learning system.&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%2F3d52qv3f0djhajyd87oj.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%2F3d52qv3f0djhajyd87oj.png" alt=" " width="799" height="244"&gt;&lt;/a&gt;&lt;br&gt;
The core difference can be summarized in one sentence: a mature continual-learning system asks, "Does this new lesson still hold on data the system has never seen?" This closed loop, in its current form, mostly summarizes a limited number of trades that have already happened.&lt;/p&gt;

&lt;p&gt;That does not mean the system has no value. Its value lies in structuring activities that are often performed manually in trading systems: recording, reviewing, summarizing, updating, and reusing experience. Previously, those lessons might have been scattered across personal notes, chat records, or subjective memory. Now they are written into a database-backed playbook and become context that the next decision can actually read.&lt;/p&gt;

&lt;p&gt;But this also means the early stages require great caution. A lesson produced by the system may reflect a genuine pattern, or it may merely reflect short-term noise. Especially when dealing with a single instrument, low-frequency trading, and a thin sample size, no "evolutionary result" should be trusted too quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: The Value of the Loop Lies Not in Getting It Right Once, but in Observing It Continuously
&lt;/h2&gt;

&lt;p&gt;What attracts me most about the Loop Engineer idea is not the promise that "the system will become more accurate the longer it runs." It is that it turns "getting better" from a process of manual parameter tuning and strategy redeployment into something that can be recorded, reviewed, and updated as part of the system's normal operation.&lt;/p&gt;

&lt;p&gt;Of course, improvement does not happen automatically. The system may learn useful lessons, or it may learn noise. It may make parameters more robust, or it may drift in the wrong direction because the sample size is too small. A human still needs to observe every playbook update, check whether the rules are specific, whether the parameters are oscillating too aggressively, and whether the signals are becoming more reasonable.&lt;/p&gt;

&lt;p&gt;For that reason, this system is better treated as a &lt;strong&gt;starting point for observation and iteration&lt;/strong&gt; than as a finished product that can immediately be trusted with leveraged live trading. A more prudent approach is to run it in "notification-only" mode for a period of time, observe the quality of its signals, reviews, and playbook evolution, and only then decide whether to move into small-position live trading.&lt;/p&gt;

&lt;p&gt;Building the loop is only the first step. Whether it can truly accumulate useful experience must ultimately be tested through enough time, enough trading samples, and strict risk controls.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is a technical discussion of strategy design ideas only and does not constitute investment advice. Cryptocurrency and other financial-derivatives trading involve substantial risk. Please carefully assess your own risk tolerance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>trading</category>
      <category>quant</category>
      <category>strategy</category>
      <category>data</category>
    </item>
    <item>
      <title>Building an AI-Powered Stock Graph Strategy: From Sector Rotation to Price Attribution</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 07 Jul 2026 08:03:51 +0000</pubDate>
      <link>https://dev.to/quant001/building-an-ai-powered-stock-graph-strategy-from-sector-rotation-to-price-attribution-40nl</link>
      <guid>https://dev.to/quant001/building-an-ai-powered-stock-graph-strategy-from-sector-rotation-to-price-attribution-40nl</guid>
      <description>&lt;p&gt;AI has been incredibly hot lately.&lt;/p&gt;

&lt;p&gt;Over the past two years, the strongest market themes have, one way or another, revolved around AI. NVIDIA, AMD, Broadcom, TSMC, Micron, Microsoft, Google, Meta, CoreWeave, Supermicro... Every once in a while, one of these companies suddenly surges because of keywords such as earnings, orders, capital expenditure, HBM, GPUs, cloud computing, or data centers.&lt;/p&gt;

&lt;p&gt;What is even more interesting is that they often do not rise in isolation.&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%2F37klrt7qdgnd8ojxnnwj.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%2F37klrt7qdgnd8ojxnnwj.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Sometimes NVIDIA moves first, and then the market starts digging into ASICs, optical modules, servers, liquid cooling, and power. Sometimes Micron reports better-than-expected earnings, and capital then starts looking at HBM, memory, semiconductor equipment, and advanced packaging. Sometimes cloud providers raise their capital expenditure guidance, and the entire AI infrastructure chain gets repriced.&lt;/p&gt;

&lt;p&gt;This looks more like asset rotation within the AI theme. The market is not simply buying one stock. Instead, under one big narrative, it keeps looking for new branches, new explanations, and new catch-up targets.&lt;/p&gt;

&lt;p&gt;Then comes the problem: if all these relationships have to be organized manually, it is simply too tiring.&lt;/p&gt;

&lt;p&gt;One person may know NVIDIA, AMD, Micron, Microsoft, and Google, but it is difficult to continuously track the business changes, news updates, sector classification, and price linkage of dozens or even hundreds of TradFi stock contracts. What makes it even more complicated is that many companies do not have only one attribute. Google is AI cloud, Gemini, and an advertising platform at the same time. Microsoft is cloud, Copilot, and enterprise software. Broadcom has ASICs, networking chips, and exposure to AI data centers.&lt;/p&gt;

&lt;p&gt;So I started trying something:&lt;/p&gt;

&lt;p&gt;Can we let a large language model help us build an AI stock graph?&lt;/p&gt;

&lt;p&gt;The goal is not to let the large language model directly tell us what to buy. Instead, it should do what it is better at: understanding company businesses, classifying AI sub-sectors, organizing news, explaining events, and judging which stocks under the same theme may be affected by one company’s price anomaly. The real trading decisions should still be left to price, statistics, and risk control.&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%2Fq66dvd2o5jwt91kyqgqf.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%2Fq66dvd2o5jwt91kyqgqf.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  First, Put Stocks into the Right Basket
&lt;/h2&gt;

&lt;p&gt;The first step of the strategy is not trading, but building the graph.&lt;/p&gt;

&lt;p&gt;There are now quite a few TradFi stock contracts on the exchange. We can directly obtain a batch of stock contracts such as NVDA, AMD, MU, MSFT, GOOGL, and SMCI. The system first filters stock contracts and keeps only real EQUITY instruments.&lt;/p&gt;

&lt;p&gt;There is one very important detail here: ETFs, indices, and leveraged ETFs cannot be mixed in. For example, QQQ, SOXL, and EWT may also appear in the contract list, but they are not company stocks. If they are placed into the AI stock basket, the later statistical relationships will be polluted.&lt;/p&gt;

&lt;p&gt;The core filtering logic looks roughly 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;def refresh_equity_universe(store, force_reanalyze):
    ms = exchange.GetMarkets()
    symbols = []

    for key, market in ms.items():
        info = market.get("Info", {}) or {}
        sub_type = info.get("underlyingSubType", []) or []
        underlying_type = info.get("underlyingType", "")

        if ".swap" not in key:
            continue
        if "TradFi" not in sub_type:
            continue
        if underlying_type != "EQUITY":
            continue

        symbol = normalize_symbol(key)
        store["markets"][symbol] = {
            "symbol": symbol,
            "contract": key,
            "underlyingType": underlying_type,
            "amountPrecision": market.get("AmountPrecision", 0),
            "pricePrecision": market.get("PricePrecision", 2),
            "ctVal": market.get("CtVal", 1) or 1,
            "minQty": market.get("MinQty", 0) or 0,
        }
        symbols.append(symbol)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After obtaining the stock universe, the system asks the large language model to create a profile for each stock.&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%2Fqkm7rrxphbixf492ym50.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%2Fqkm7rrxphbixf492ym50.png" alt=" " width="800" height="370"&gt;&lt;/a&gt;&lt;br&gt;
The profile is not simply a sentence like “this is a technology stock.” Instead, it needs to determine which AI sub-sectors the stock belongs to. For example, one stock can belong to multiple baskets at the same time. Google can belong to AI cloud, AI model applications, and AI platform advertising. Microsoft can belong to AI cloud and enterprise AI applications. NVIDIA can belong to AI computing power and AI servers. Micron can belong to AI memory.&lt;/p&gt;

&lt;p&gt;The key point of this step is: in the AI era, stocks are not single-label assets. They are multi-label assets.&lt;/p&gt;

&lt;p&gt;Each stock carries several attributes: which baskets it belongs to, how high its exposure is, whether it is more like a leader or a follower in that basket, which companies may affect it, and which companies it may affect.&lt;/p&gt;

&lt;p&gt;I gave the large language model a strongly constrained output structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;schema = {
    "symbol": symbol,
    "asset_type": "stock|adr|etf|leveraged_etf|index|fund|other",
    "company_name": "",
    "sector": "",
    "business_summary": "",
    "tradable": False,
    "model_confidence": 0,
    "baskets": [
        {
            "id": "ai_cloud",
            "exposure": 0.0,
            "role": "leader|follower|both|observer",
            "sub_theme": "",
            "reason": ""
        }
    ],
    "search_names": [],
    "event_keywords": [],
    "negative_keywords": [],
    "possible_leaders": [],
    "possible_followers": [],
    "brave_queries": [],
    "reason": ""
}

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

&lt;/div&gt;



&lt;p&gt;The most important field here is asset_type. Only stock and adr can enter a basket. ETFs, leveraged ETFs, indices, funds, commodities, and regional ETFs are filtered out directly.&lt;/p&gt;

&lt;p&gt;In addition, reason must be written in Chinese, field names must remain consistent, and confidence must be normalized to a range from 0 to 100. Otherwise, the live trading dashboard will look very messy later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sector Information Cannot Rely on Guesswork
&lt;/h2&gt;

&lt;p&gt;Stock classification alone is not enough.&lt;/p&gt;

&lt;p&gt;The AI sector changes too quickly. Today the market may be speculating on GPUs. Tomorrow it may move to HBM. The day after that it may move to optical modules. A few days later, it may start trading liquid cooling, power, or cloud provider capital expenditure.&lt;/p&gt;

&lt;p&gt;If the system relies only on the large language model’s own knowledge, it can easily become outdated. So the system also needs a real-time information source.&lt;/p&gt;

&lt;p&gt;Here I connected BraveSearch. The system periodically searches for sector information for each AI basket, and also searches for news about key stocks inside each basket.&lt;/p&gt;

&lt;p&gt;For example, the AI memory basket will look at keywords such as Micron, Western Digital, SanDisk, HBM, DRAM, and NAND. The AI optical communication basket will look at information such as Ciena, Credo, Lumentum, AAOI, 800G, and 1.6T optical modules.&lt;/p&gt;

&lt;p&gt;The core process for basket intelligence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def refresh_basket_intelligence(store):
    if not BRAVE_KEY:
        return
    if not LLM_API_KEY:
        return

    intel = {}

    for bid, basket in store["baskets"].items():
        members = sorted(
            basket["members"],
            key=lambda x: x.get("exposure", 0),
            reverse=True
        )
        top_symbols = [m["symbol"] for m in members[:8]]

        basket_news = fetch_basket_news(basket, top_symbols)
        stock_news = {}

        for symbol in top_symbols[:6]:
            profile = store["profiles"].get(symbol, {})
            stock_news[symbol] = fetch_stock_news(profile, bid)
            Sleep(800)

        analysis = analyze_basket_intel_with_llm(
            store, basket, top_symbols, basket_news, stock_news
        )

        if analysis:
            analysis["basket"] = bid
            analysis["basket_name"] = basket["name"]
            analysis["symbols"] = top_symbols
            analysis["updatedAt"] = int(time.time())
            intel[bid] = analysis

    store["basket_intel"] = intel

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

&lt;/div&gt;



&lt;p&gt;The role of this step is to build a dynamic background for each AI sub-sector.&lt;/p&gt;

&lt;p&gt;Based on the news pulled by BraveSearch, the large language model outputs the sector trend, core catalysts, core risks, candidate leaders, candidate followers, and relationships that need to be validated later.&lt;/p&gt;

&lt;p&gt;This is different from directly asking the large language model, “What do you think about AI memory?” Here, we first feed it real-time news, and then let it summarize.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Is Not News-Driven Trading; Price Abnormality Comes First
&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%2Fntj4eqpj7ql7s1qg9sxd.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%2Fntj4eqpj7ql7s1qg9sxd.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
At first, I also considered a news-driven approach.&lt;/p&gt;

&lt;p&gt;For example, when a company reports better-than-expected earnings, the system could ask the large language model which stocks may benefit, and then directly generate a signal. But after thinking about it carefully, this path is very noisy. There is too much news. Many news items look important, but the market simply does not care.&lt;/p&gt;

&lt;p&gt;So I reversed the logic.&lt;/p&gt;

&lt;p&gt;Look at price first, then look at news.&lt;/p&gt;

&lt;p&gt;If all stocks inside a basket are relatively stable, the system only observes. Only when one stock clearly deviates from the average performance of the basket and becomes the current leader will the system ask:&lt;/p&gt;

&lt;p&gt;Why did it move?&lt;/p&gt;

&lt;p&gt;This step uses the ZScore inside the basket.&lt;/p&gt;

&lt;p&gt;In simple terms, it checks whether one stock is abnormally strong or abnormally weak relative to other stocks in the same basket. If one stock suddenly breaks out while other stocks in the same basket have not yet reacted, there may be a diffusion opportunity.&lt;/p&gt;

&lt;p&gt;The core logic for calculating basket ZScore is 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;def calc_basket_zscores(store, symbols):
    returns = {}
    min_len = 999999

    for symbol in symbols:
        bars = get_records(
            store["markets"][symbol]["contract"],
            KLINE_PERIOD,
            SYNC_WINDOW + 10
        )
        if len(bars) &amp;lt; SYNC_WINDOW + 2:
            return None

        r = calc_returns(bars)[-SYNC_WINDOW:]
        returns[symbol] = r
        min_len = min(min_len, len(r))

    latest = {s: returns[s][-1] for s in symbols}
    avg_latest = mean(list(latest.values()))
    dev_series = {s: [] for s in symbols}

    for i in range(min_len):
        row = [returns[s][-min_len + i] for s in symbols]
        row_avg = mean(row)
        for s in symbols:
            dev_series[s].append(returns[s][-min_len + i] - row_avg)

    zscores = {}
    max_abs = 0
    for s in symbols:
        sigma = std(dev_series[s])
        z = (latest[s] - avg_latest) / sigma if sigma &amp;gt; 0 else 0
        zscores[s] = round(z, 3)
        max_abs = max(max_abs, abs(z))

    return {"zscores": zscores, "maxAbs": max_abs}

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

&lt;/div&gt;



&lt;p&gt;If the maximum deviation of a basket exceeds the breakout threshold, the system enters the BREAKOUT_DETECTED state.&lt;/p&gt;

&lt;p&gt;At this point, the system does not trade immediately. Instead, it starts looking for the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  News Is Responsible for Explanation; Statistical Relationships Are
&lt;/h2&gt;

&lt;p&gt;Responsible for Validation&lt;br&gt;
After the leader shows a price breakout, the system fetches its latest news and then asks the large language model to determine whether the news can explain the price anomaly.&lt;/p&gt;

&lt;p&gt;But this is not simply asking, “Is this news bullish?”&lt;/p&gt;

&lt;p&gt;The system sends the current basket, sector intelligence, real-time price state, the leader’s ZScore, candidate followers, already validated statistical relationships, and news pulled by BraveSearch to the large language model together.&lt;/p&gt;

&lt;p&gt;The large language model needs to answer:&lt;/p&gt;

&lt;p&gt;(1) Whether this news can explain the leader’s price anomaly.&lt;br&gt;
(2) Whether the news direction is consistent with the price direction.&lt;br&gt;
(3) Whether trading is allowed.&lt;br&gt;
(4) Which followers are affected.&lt;br&gt;
(5) How large the impact is on each follower.&lt;br&gt;
(6) Why these followers may be affected.&lt;/p&gt;

&lt;p&gt;The core attribution logic is 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;def explain_breakout_with_news(store, bid, leader, direction, leader_z, followers, edges):
    if not BRAVE_KEY:
        return None
    if not LLM_API_KEY:
        return None

    profile = store["profiles"].get(leader, {})
    news = fetch_stock_news(profile, bid)
    if not news:
        return None

    event = analyze_breakout_news_with_llm(
        store, bid, leader, direction, leader_z,
        followers, edges, news
    )
    if not event:
        return None

    if not event.get("explains_price_move"):
        return None

    expected_direction = "bullish" if direction == "long" else "bearish"
    if event.get("direction") != expected_direction:
        return None

    event["confidence"] = normalize_confidence(event.get("confidence", 0))
    if event["confidence"] &amp;lt; MIN_NEWS_CONFIDENCE:
        return None

    if not event.get("trade_allowed"):
        return None

    return event

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

&lt;/div&gt;



&lt;p&gt;But even if the large language model says that a certain follower may be affected, the system still cannot trade immediately.&lt;/p&gt;

&lt;p&gt;It also needs to check the K-line statistical relationship.&lt;/p&gt;

&lt;p&gt;Inside each basket, the system calculates historical linkages between stocks. For example: after the source breaks out, does the target often follow? What is the correlation? What is the best lag period? What is the average follow-up return?&lt;/p&gt;

&lt;p&gt;Only a source -&amp;gt; target relationship that passes the statistical filter becomes an active edge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def rebuild_basket_stats(store):
    edges = []

    for bid, basket in store["baskets"].items():
        symbols = [
            m["symbol"] for m in basket["members"]
            if m["symbol"] in store["markets"]
        ]

        for source in symbols:
            for target in symbols:
                if source == target:
                    continue

                metric = validate_pair(store, source, target)
                if not metric:
                    continue

                source_exp = basket_member_info(basket, source).get("exposure", 0)
                target_exp = basket_member_info(basket, target).get("exposure", 0)

                score = clamp(
                    metric["corr"] * 0.3 +
                    metric["follow"] * 0.4 +
                    min(source_exp, target_exp) * 0.3,
                    0, 1
                )

                edge = {
                    "basket": bid,
                    "source": source,
                    "target": target,
                    "corr": round(metric["corr"], 3),
                    "follow": round(metric["follow"], 3),
                    "lag": metric["lag"],
                    "avgReturn": round(metric["avgReturn"], 6),
                    "sample": metric["sample"],
                    "score": round(score, 3),
                    "active": (
                        score &amp;gt;= MIN_EDGE_SCORE and
                        metric["corr"] &amp;gt;= MIN_CORR and
                        metric["follow"] &amp;gt;= MIN_FOLLOW_SUCCESS
                    ),
                    "updatedAt": int(time.time()),
                }
                edges.append(edge)

    store["edges"] = edges

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

&lt;/div&gt;



&lt;p&gt;This step is very important.&lt;/p&gt;

&lt;p&gt;Large language models are good at understanding logic, but the market does not necessarily move according to logic. Statistical relationships are used to verify whether this logic has actually been traded by the market in the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strategy Trades Diffusion, Not Chasing the High
&lt;/h2&gt;

&lt;p&gt;This strategy is not designed to chase the leader.&lt;/p&gt;

&lt;p&gt;The leader has already broken out. If we buy it directly, it can easily become chasing at a high price. What the strategy really wants to capture is the diffusion opportunity inside the same AI basket.&lt;/p&gt;

&lt;p&gt;In other words: the leader has already moved, and the news makes sense, but some followers have not fully reacted yet. If these followers have indeed shown linkage with the leader historically, there may be a short-term follow-up opportunity.&lt;/p&gt;

&lt;p&gt;During breakout scanning, the system first looks for active edges, and then looks for lagging followers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def scan_price_breakouts(store):
    for bid, rt in store["realtime"].items():
        if rt["state"] != "BREAKOUT_DETECTED":
            continue

        leader = rt["leader"]
        z = 0
        for m in rt["members"]:
            if m["symbol"] == leader:
                z = m["z"]

        direction = "long" if z &amp;gt; 0 else "short"

        edges = [
            e for e in store["edges"]
            if e["active"] and e["basket"] == bid and e["source"] == leader
        ]

        followers = []
        for e in edges:
            target_rt = find_realtime_member(rt, e["target"])
            if target_rt and abs(target_rt["z"]) &amp;lt; BREAKOUT_THRESHOLD * 0.75:
                followers.append(e["target"])

        if not followers:
            continue

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

&lt;/div&gt;



&lt;p&gt;Only after the news attribution also passes will the system generate a confirmed signal.&lt;/p&gt;

&lt;p&gt;The main trading targets are the followers.&lt;/p&gt;

&lt;p&gt;If there is no current position, the system opens a position according to the signal direction. If the same contract already has a position in the same direction, it adds to the position. If there is already a position in the opposite direction, it first closes the position, and then opens a new position in the new direction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def execute_symbol_trade(store, signal, symbol, detail):
    direction = signal["direction"]

    if TRADE_MODE != "trade":
        return open_or_add_position(store, signal, symbol, detail, add_mode=False)

    pos = get_position_by_symbol(symbol)
    if pos:
        pos_dir = position_direction(pos)

        if pos_dir == direction:
            return open_or_add_position(store, signal, symbol, detail, add_mode=True)

        close_existing_position(store, symbol, pos, "reverse_signal")
        Sleep(1000)

    return open_or_add_position(store, signal, symbol, detail, add_mode=False)

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

&lt;/div&gt;



&lt;p&gt;This is also because many stocks may appear in multiple baskets at the same time. A stock may be a follower in the AI cloud basket, while also being a member of another AI application logic. Signals from different baskets may affect it at the same time, so the direction of the same contract needs to be handled in a unified way.&lt;/p&gt;

&lt;p&gt;Risk Control Is Simple for Now, But It Must Exist&lt;br&gt;
In terms of risk control, the current system uses a hard stop-loss and a trailing stop.&lt;/p&gt;

&lt;p&gt;If the loss reaches the threshold, the position exits. After profit reaches a certain level, the trailing stop is activated. If the position gives back too much from the highest floating profit, the system exits to lock in profit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def monitor_positions(store):
    for pos in exchange.GetPosition() or []:
        symbol = position_symbol(pos)
        if symbol not in store["markets"]:
            continue

        state = _G(position_state_key(symbol)) or {}
        market = store["markets"][symbol]
        ticker = exchange.GetTicker(market["contract"])

        direction = position_direction(pos)
        pnl_pct = (
            (ticker["Last"] - pos["Price"]) /
            pos["Price"] * 100 *
            (1 if direction == "long" else -1)
        )

        if pnl_pct &amp;gt; state.get("peakPnlPct", 0):
            state["peakPnlPct"] = pnl_pct

        if not state.get("trailActive") and state.get("peakPnlPct", 0) &amp;gt;= TRAIL_ACTIVATE_PCT:
            state["trailActive"] = True

        hard_stop = False
        if state.get("stop"):
            hard_stop = (
                ticker["Last"] &amp;lt;= state["stop"]
                if direction == "long"
                else ticker["Last"] &amp;gt;= state["stop"]
            )

        trail_hit = False
        if state.get("trailActive"):
            giveback = state.get("peakPnlPct", 0) - pnl_pct
            allowed = max(
                1.5,
                state.get("peakPnlPct", 0) * TRAIL_GIVEBACK_PCT / 100.0
            )
            trail_hit = giveback &amp;gt;= allowed

        if hard_stop or trail_hit:
            close_existing_position(
                store, symbol, pos,
                "hard_stop" if hard_stop else "trailing_stop"
            )

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

&lt;/div&gt;



&lt;p&gt;This is not the final version of the risk control system, but it is already enough for the first version of live observation.&lt;/p&gt;

&lt;p&gt;Later, account-level total position limits, single-sector exposure limits, daily loss limits, execution confirmation, and limits on the number of add-ons still need to be added.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the System Can Do Now
&lt;/h2&gt;

&lt;p&gt;At this stage, the system can already complete a relatively full research loop.&lt;/p&gt;

&lt;p&gt;It can automatically scan TradFi stock contracts, automatically ask the large language model to create multi-basket stock profiles, automatically build AI sub-sectors, automatically pull sector and stock information through BraveSearch, automatically summarize sector intelligence, automatically detect real-time strength and weakness inside each basket, automatically calculate follow relationships between stocks inside each basket, and perform news attribution after the leader price breakout.&lt;/p&gt;

&lt;p&gt;If the news explanation holds, the direction is consistent, and the statistical relationship also supports it, the system will generate a confirmed signal. By default, it only sends notifications and does not trade live. It will trade only after switching to trade mode.&lt;/p&gt;

&lt;p&gt;The live trading page is no longer just a log page. It displays the system overview, real-time basket status, sector intelligence, recent signals, recent trades, and recent steps in tables. This makes it easier to see where the system is stuck: whether there is only price abnormality, whether the news fails to explain the move, whether the follower does not satisfy the conditions, or whether risk control blocks the trade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzfg7ylpsnxdo9ys6w2lt.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%2Fzfg7ylpsnxdo9ys6w2lt.png" alt=" " width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Still Immature
&lt;/h2&gt;

&lt;p&gt;To be honest, this system cannot yet be considered a mature automated trading strategy.&lt;/p&gt;

&lt;p&gt;It is more like an AI theme rotation research framework. It can already connect stock profiling, sector intelligence, price abnormality, news attribution, statistical validation, and trading signals, but it still needs further validation.&lt;/p&gt;

&lt;p&gt;The areas that need the most improvement are as follows.&lt;/p&gt;

&lt;p&gt;(1) Stock profile quality. The large language model may sometimes place marginal stocks into a basket, so asset_type must be strictly enforced to prevent ETFs, indices, and regional funds from being mixed in.&lt;/p&gt;

&lt;p&gt;(2) News quality. The news captured by BraveSearch is not necessarily always the core news. The model needs to judge which news items can truly explain the price move.&lt;/p&gt;

&lt;p&gt;(3) Statistical relationships. Historical follow relationships do not mean they will definitely remain valid in the future. They need continuous review.&lt;/p&gt;

&lt;p&gt;(4) Trading risk control. Before enabling automatic trade mode, account-level positions, sector exposure, daily loss limits, and execution confirmation must be added.&lt;/p&gt;

&lt;p&gt;So for now, it is more suitable to run in notify mode first and observe the quality of confirmed signals.&lt;/p&gt;

&lt;p&gt;If, after a period of observation, the signals can indeed explain market anomalies and the followers often react afterward, then small-position live trading can be considered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;This strategy is not about letting AI place orders directly.&lt;/p&gt;

&lt;p&gt;I have always believed that the most suitable role for large language models in a trading system is not “tell me what to buy,” but helping us process information structures that are difficult for humans to maintain continuously: company businesses, sector classification, real-time news, event explanations, capital rotation paths, and potential affected targets.&lt;/p&gt;

&lt;p&gt;Real trading should still be left to price, statistics, and risk control.&lt;/p&gt;

&lt;p&gt;In the AI era, a quantitative strategy should not simply add one more large language model API. Instead, it should connect real-time data, thematic graphs, news events, price structure, trade execution, and post-trade review into one system.&lt;/p&gt;

&lt;p&gt;This AI stock graph strategy is one experiment along this direction.&lt;/p&gt;

</description>
      <category>strategy</category>
      <category>ai</category>
      <category>trading</category>
      <category>tradfi</category>
    </item>
    <item>
      <title>Between Arbitrage and Prediction: A Naive Experiment in a World Cup Path-Convergence Strategy</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 07 Jul 2026 06:56:54 +0000</pubDate>
      <link>https://dev.to/quant001/between-arbitrage-and-prediction-a-naive-experiment-in-a-world-cup-path-convergence-strategy-4hj2</link>
      <guid>https://dev.to/quant001/between-arbitrage-and-prediction-a-naive-experiment-in-a-world-cup-path-convergence-strategy-4hj2</guid>
      <description>&lt;p&gt;The World Cup had barely kicked off before the market gave us a little dose of shock education: it's not that strong teams never crash — it's that the way they crash is often more creative than we imagine.&lt;/p&gt;

&lt;p&gt;Take Spain 0:0 Cape Verde. If you only looked at strength, ranking, and squad depth before the match, most people's gut reaction would be pretty simple: Spain should take this, right? But this is exactly where football is most interesting — and most tormenting for traders. You think you're buying "strong team wins," but what you're actually buying is a full 90-minute drama of fate. No goal in the first 15 minutes, and the odds twitch. Still 0:0 at the half hour, and the market starts to frown. Nothing by the 70th minute, and the win that looked rock-solid suddenly isn't so solid anymore.&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%2Fyt283ebaklp7lni6cfv4.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%2Fyt283ebaklp7lni6cfv4.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
This is when you run into a classic contradiction: arbitrage is great, but prediction is hard.&lt;/p&gt;

&lt;p&gt;True arbitrage should, in theory, be risk-free. For instance, you find a set of mutually exclusive and collectively exhaustive contracts where, no matter what happens in the end, the portfolio pays out, and your entry cost is below the payout. It sounds wonderful — the trader's version of an afternoon nap. But that's exactly the problem: such opportunities are too rare, often vanish quickly when they appear, and may not even have enough capacity.&lt;/p&gt;

&lt;p&gt;Prediction is another matter. You can judge that a strong team will probably win; you can analyze the lineup, form, schedule, and injuries; you can look at the implied probability from the odds. But the match won't cooperate with the script just because we analyzed it carefully. Football especially: an early red card, a goalpost, a ridiculous VAR call — any of these can turn beautiful logic into three seconds of silence in the post-match review.&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%2Fll6boqz1oxlmgnahyj5g.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%2Fll6boqz1oxlmgnahyj5g.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
So here's the question: if pure arbitrage is too scarce and pure prediction is too random, can we stand somewhere in between? The market itself has already given us some prior probabilities — the price of a strong-team win, the price of 0:0, the price of 0:1. These prices don't come from nowhere; they reflect the market's collective pricing of different paths. So can we use these prior probabilities, plus a bit of our own mathematical model, to construct an imperfect but more protective portfolio that pries open a "path-convergence" window?&lt;/p&gt;

&lt;p&gt;That's the starting point of this idea.&lt;/p&gt;

&lt;p&gt;It isn't traditional risk-free arbitrage, and it isn't pure gut-feel prediction either. It's more like a compromise: first acknowledge that the main probability the market gives us has reference value, then use low-probability paths to protect the most fragile part of the main judgment, while using a simple model to check whether the price is still acceptable.&lt;/p&gt;

&lt;p&gt;Suppose in a given match, Brazil is clearly stronger than Haiti. The market prices a Brazil win at 0.89, which says a Brazil win is a very strong main path. But we don't just buy the Brazil win outright — we simultaneously watch two protective paths: 0:0 and 0:1. Because for a "strong team wins" judgment, the most uncomfortable early script is usually not every possible outcome, but the game staying stubbornly locked, or the weaker team stealing one.&lt;/p&gt;

&lt;p&gt;So the initial portfolio becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Brazil win   Yes
0:0          Yes
0:1          Yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose the prices are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Brazil win = 0.89
0:0        = 0.016
0:1        = 0.011
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total cost across the three legs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C = 0.89 + 0.016 + 0.011 = 0.917
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This bundle is not a complete event. It doesn't cover 1:1, 0:2, 2:2, or any number of weird scripts. So if you hold it to the final whistle, it can of course lose — and it can lose quite directly. But it has one important feature: it stitches together three probabilities the market has already given, forming a basket of paths around the main win.&lt;/p&gt;

&lt;p&gt;If Brazil ultimately wins, the portfolio pays out 1. If the final score is 0:0 or 0:1, it also pays out 1. In other words, what you're buying 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%2Fi5ttik8poy6s1hm7m2kb.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%2Fi5ttik8poy6s1hm7m2kb.png" alt=" " width="800" height="450"&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;Brazil win ∪ 0:0 ∪ 0:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As long as one of these paths occurs, the payout at expiry is 1. The cost is 0.917, leaving 0.083 of room within the covered paths. But the real key isn't at the final whistle — it's in the middle.&lt;/p&gt;

&lt;p&gt;If Brazil scores early and the score moves to 1:0, then the two protective legs, 0:0 and 0:1, effectively go to zero, but the Brazil-win price rises. As soon as the sellable price of the Brazil win exceeds the initial total cost plus a little target profit, you can close the main win and converge the portfolio early.&lt;/p&gt;

&lt;p&gt;The formula is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Take-profit condition = Brazil win bid &amp;gt;= initial total cost C + target profit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, with a target profit of 0.02:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger price = 0.917 + 0.02 = 0.937
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If, after 1:0, the Brazil-win bid reaches 0.95, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Locked-in profit = 0.95 - 0.917 = 0.033
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we aren't making money off the final settlement — we're making it because the match entered a favorable branch, letting us sell the portfolio early. The process is a bit like the market opening a small window for you: don't stand at the window writing poetry — climb through it first.&lt;/p&gt;

&lt;p&gt;But there's still a problem here: just because three legs look cheap, are they necessarily worth buying? Not necessarily. Market price is only the first layer of information. We also need our own reference ruler, however naive.&lt;/p&gt;

&lt;p&gt;Here I used the most basic Poisson goal model.&lt;/p&gt;

&lt;p&gt;A football scoreline can be roughly viewed as two teams each scoring as a random process over 90 minutes. Suppose the home team's expected goals over 90 minutes is λ_home and the away team's is λ_away. Then the probability of the home team scoring i goals and the away team scoring j goals can be written as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P(i, j) = Pois(i; λ_home) × Pois(j; λ_away)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pois(k; λ) = e^(-λ) × λ^k / k!
&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%2Flmu58zorw9q6kssnct3p.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%2Flmu58zorw9q6kssnct3p.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
It's certainly crude. Football isn't two independent lottery machines — after a goal, tactics change, red cards change things, mentalities shift. But its virtue is simplicity and transparency: at the very least it keeps us from acting purely on feel.&lt;/p&gt;

&lt;p&gt;Before the match, the Poisson model can give us an initial reference. We can manually supply λ_home and λ_away, or we can let the program back them out from the market's exact-score markets. Scores like 0:0, 0:1, 1:0, 1:1, 2:0, 2:1, 3:0 — each of these score markets is equivalent to one probability point the market has provided. The program looks for a pair λ_home and λ_away such that the score probabilities computed by the Poisson model are as close as possible to these market prices.&lt;/p&gt;

&lt;p&gt;In other words, the market is saying, "I think these scorelines are worth roughly this much." And the Poisson model murmurs back, "Then let me try to fit it, and see what goal intensity is implied behind this set of prices."&lt;/p&gt;

&lt;p&gt;The strategy can be configured with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CALIBRATE_LAMBDA_FROM_MARKET = True
MODEL_SCORE_SAMPLES = "0-0,0-1,1-0,1-1,2-0,2-1,3-0"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These scores are used only for modeling and do not participate in order placement. The legs actually traded are still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Target team win
0:0
0:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code fits λ with a grid search. It's not complex, but it's intuitive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def fit_lambdas_from_score_markets(quotes, model_score_legs, event_state=None):
    samples = []
    live_score = event_state.get("score_tuple") if event_state else None
    minute = event_state.get("elapsed") if event_state else None
    is_live = bool(live_score and minute not in [None, ""])

    for leg in model_score_legs:
        q = quotes.get(leg["name"])
        p_market = quote_probability(q)
        if p_market is None:
            continue

        target_h, target_a = parse_score_text(leg["score"])

        if is_live:
            current_h, current_a = live_score
            if current_h &amp;gt; target_h or current_a &amp;gt; target_a:
                continue
            samples.append((target_h - current_h, target_a - current_a, p_market, leg["score"]))
        else:
            samples.append((target_h, target_a, p_market, leg["score"]))

    if len(samples) &amp;lt; 2:
        return None

    best = None
    for ih in range(5, 501, 5):
        lh = ih / 100.0
        for ia in range(5, 501, 5):
            la = ia / 100.0
            err = 0.0
            for add_h, add_a, p_market, _score in samples:
                p_model = poisson_pmf(add_h, lh) * poisson_pmf(add_a, la)
                err += (p_model - p_market) ** 2

            if best is None or err &amp;lt; best["err"]:
                best = {"lambda_home": lh, "lambda_away": la, "err": err, "samples": samples}

    if is_live:
        ratio = max(0.01, max(0.0, 90.0 - float(minute)) / 90.0)
        best["lambda_home"] = best["lambda_home"] / ratio
        best["lambda_away"] = best["lambda_away"] / ratio
        best["source"] = "live_score_markets"
    else:
        best["source"] = "pre_match_score_markets"

    return best
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the match, this λ is only an initial calibration. Since the game hasn't started, there's no live path to update; the score defaults to 0:0 and the remaining time is the full 90 minutes. At this stage we use it as an entry filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model coverage probability = P(target team win) + P(0:0) + P(0:1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model coverage probability - market cost &amp;gt;= safety margin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only when the market price is cheap enough, and the model also thinks the basket has a slight edge, do we allow opening a position.&lt;/p&gt;

&lt;p&gt;What's really interesting is after the match begins.&lt;/p&gt;

&lt;p&gt;Once the match starts, the Poisson model is no longer static. Suppose the game reaches the 30th minute and the score is still 0:0. Then only 60 minutes remain, and the future goal intensity must shrink in proportion to the remaining time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;λ_home_remaining = λ_home × (90 - t) / 90
λ_away_remaining = λ_away × (90 - t) / 90

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

&lt;/div&gt;



&lt;p&gt;If the current score is already 1:0, then 0:0 and 0:1 can no longer occur. At that point the model must start from the current score and only compute how many more goals will be scored in the remaining time. Final scores like 2:0, 2:1, 3:0, 1:1 that are still possible are the ones eligible to participate in the new estimate.&lt;/p&gt;

&lt;p&gt;This is the key to live Poisson updating: not mechanically shrinking the pre-match λ each minute, but re-estimating how the rest of the match might still unfold, combining the current score, the remaining time, and the exact-score markets that are still possible.&lt;/p&gt;

&lt;p&gt;The final strategy becomes a three-layer judgment.&lt;/p&gt;

&lt;p&gt;The first layer is market cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;win_ask + 0:0_ask + 0:1_ask &amp;lt;= maximum allowed cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second layer is the Poisson filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model coverage probability - market cost &amp;gt;= safety margin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third layer is path convergence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current portfolio bid value &amp;gt;= initial cost + target profit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only when the first and second layers are both satisfied do we consider entering. After entry, we no longer rely on the model to fantasize about the final outcome; we use the real bid quotes to judge whether we can close out. The model is responsible for raising entry quality; the order book is responsible for deciding whether we can cash in.&lt;/p&gt;

&lt;p&gt;Contract lookup is kept as simple as possible. Polymarket's World Cup market slugs are quite regular. For example, a match's event slug is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVENT_SLUG = "fifwc-aut-jor-2026-06-17"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we're protecting a Jordan win, the corresponding win-contract suffix is:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then the three contracts needed for trading can be spelled out directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def yes_symbol(slug):
    return slug + "_USDC.Yes"


def build_legs():
    legs = [
        {
            "name": "win",
            "slug": EVENT_SLUG + "-" + WIN_SUFFIX,
            "symbol": yes_symbol(EVENT_SLUG + "-" + WIN_SUFFIX),
            "kind": "win",
        }
    ]

    for score in parse_scores(PROTECT_SCORES):
        legs.append(
            {
                "name": "score_" + score.replace("-", "_"),
                "slug": EVENT_SLUG + "-exact-score-" + score,
                "symbol": yes_symbol(EVENT_SLUG + "-exact-score-" + score),
                "kind": "score",
                "score": score,
            }
        )

    return legs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, try not to use the team name for fuzzy search. Search Jordan, for instance, and you can easily turn up Michael B. Jordan, Jordan Pickford, and Jordan Spieth — and before your football strategy even gets going, the entertainment industry, an England goalkeeper, and a golfer have all sat down at the table first. Spelling out contracts from the event slug is much cleaner.&lt;/p&gt;

&lt;p&gt;During the match we also need the live score. The first version can pull it directly from the Polymarket Gamma event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get_event_state():
    data = get_json(GAMMA_BASE + "/events", slug=EVENT_SLUG)
    e = data[0]

    return {
        "title": e.get("title"),
        "score": e.get("score"),
        "score_tuple": parse_score(e.get("score")),
        "elapsed": e.get("elapsed"),
        "period": e.get("period"),
        "live": bool(e.get("live")),
        "ended": bool(e.get("ended")),
        "start_time": e.get("startTime"),
    }

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

&lt;/div&gt;



&lt;p&gt;The portfolio's current sellable value uses the real bid quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def basket_bid_value(legs, quotes):
    total = 0.0
    for leg in legs:
        pos = positions.get(leg["name"], {})
        amount = float(pos.get("amount", 0))
        if amount &amp;lt;= 0:
            continue

        q = quotes.get(leg["name"])
        if not q or q["bid"] is None:
            continue

        total += amount * q["bid"]

    return total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take-profit judgment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost = current_position_cost()
value = basket_bid_value(legs, quotes)
target = cost + TARGET_PROFIT * SHARES

if value &amp;gt;= target:
    close_all()

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

&lt;/div&gt;



&lt;p&gt;That's the minimal version. It doesn't pretend to have solved football prediction, nor does it claim to arbitrage reliably. It only attempts to take that gray zone between "arbitrage" and "prediction" and study it a bit.&lt;/p&gt;

&lt;p&gt;The beauty of arbitrage is certainty, but certainty is scarce. The temptation of prediction is the wide space it opens, but the randomness is large too. What this strategy does is treat the market's prediction prices as raw material, take one high-probability main judgment plus two low-probability protective paths, add a layer of crude filtering with the Poisson model, and try to construct a window that can converge early during the course of the match.&lt;/p&gt;

&lt;p&gt;The risks must be stated clearly. This portfolio is not a complete event, and it is not risk-free arbitrage. It suits matches with a large gap in strength, where the main path is clear enough and the protective-path prices are low enough. If the match itself is a coin flip, or the protective legs are already expensive, forcing the trade is pointless. Worse, it doesn't cover all unfavorable paths — 1:1, 0:2, 2:2 can all leave the portfolio with a clear loss. So you must set position caps, maximum loss, and stop-loss rules; you can't treat it as arbitrage just because it "looks like arbitrage."&lt;/p&gt;

&lt;p&gt;The Poisson model is no magic either. It's just a very plain mathematical foundation, helping us take one step forward from "I feel this price is good" to "at least I checked this price with a transparent model." It will be wrong, the market will be wrong, and we ourselves will be even more wrong. The most honest part of trading is probably admitting that all of these will be wrong, and then doing your best to keep any single error from flipping the whole table.&lt;/p&gt;

&lt;p&gt;This attempt is shallow. It's more like asking a question: when pure arbitrage opportunities grow scarcer and pure prediction is too hard, can we use the probabilities the market has already given to construct trades with more structure? Not to fantasize about eliminating risk, but to break risk apart and see clearly which paths it arrives by.&lt;/p&gt;

&lt;p&gt;Maybe this isn't the answer, but it's a little hole worth digging into further. Prediction tells us which main road may be wider; the arbitrage mindset reminds us not to look only at direction but also at portfolio structure. Between the two, there may still be a lot of space to explore.&lt;/p&gt;

</description>
      <category>strategy</category>
      <category>arbitrage</category>
      <category>backtesting</category>
      <category>market</category>
    </item>
    <item>
      <title>FUSE: An Experiment in Drawing "News" Directly Onto the Candlestick Chart</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 07 Jul 2026 05:39:01 +0000</pubDate>
      <link>https://dev.to/quant001/fuse-an-experiment-in-drawing-news-directly-onto-the-candlestick-chart-186b</link>
      <guid>https://dev.to/quant001/fuse-an-experiment-in-drawing-news-directly-onto-the-candlestick-chart-186b</guid>
      <description>&lt;h2&gt;
  
  
  1. The Starting Point: Trump Drawing Candlesticks by Hand
&lt;/h2&gt;

&lt;p&gt;The time-sensitivity of news needs no argument. When word broke that the U.S. had carried out airstrikes on Iran, crude oil spiked violently in a very short window — and throughout that move, factors like the statements from Trump and from Iran kept interweaving and reinforcing one another, pushing the market into one new range after another.&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%2Frucpacbjkodn8ksucmil.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%2Frucpacbjkodn8ksucmil.png" alt=" " width="800" height="352"&gt;&lt;/a&gt;&lt;br&gt;
We often joke that "Trump draws the candlesticks by hand" — much of the time, a violent price swing isn't something that evolved out of technical indicators at all; it's punched straight into the chart by a single tweet, a single speech, a single policy remark. Technical analysis tells you "where we are right now," but the news is often the key variable behind "why we got here, and where we might go next."&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%2Fic4vpqt9vr05c7mcph4i.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%2Fic4vpqt9vr05c7mcph4i.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;br&gt;
The importance of news is beyond dispute, but the problem is just as real: a human can't stare at the charts and the newswire 24 hours a day, and it's even harder to be the first to catch — in a flood of information — the one headline that will actually move the market. So a very plain idea surfaced: could we "draw" the news directly onto the candlestick chart, so that price and news are presented in the same view at the same time? At the very least, let's first solve the problem of seeing it.&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%2Fnn2ebsv9dlh16eekbon2.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%2Fnn2ebsv9dlh16eekbon2.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Choosing a News Source: Connecting to Jin10 via MCP
&lt;/h2&gt;

&lt;p&gt;To pipe news in, the first step is finding a source that updates fast enough and has a relatively standardized structure. This time we went with Jin10 Data (a Chinese financial newswire), connected via MCP (Model Context Protocol), calling two kinds of endpoints: list_flash (flash news) and list_news (longer-form articles).&lt;/p&gt;

&lt;p&gt;I don't intend to spend much space introducing Jin10 itself — it's just one option we happen to be using right now. The approach is decoupled from any specific news source: as long as a source can provide timestamped headlines/bodies and can be called via a standard mechanism like MCP, it can be swapped in. The point is the design of this ingestion layer, not being locked to any one particular tool.&lt;/p&gt;

&lt;p&gt;MCP's connection and session management is one of the more "low-level" but also critical pieces of this system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def _mcp_post(payload, is_notification=False):
    global _mcp_session_id, _mcp_req_id
    if not is_notification:
        _mcp_req_id += 1
        payload["id"] = _mcp_req_id
    body = json.dumps(payload, ensure_ascii=False).encode()
    req  = urllib.request.Request(
        JIN10_MCP_URL, data=body, headers=_mcp_headers(), method="POST"
    )
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            sid = resp.headers.get("Mcp-Session-Id")
            if sid:
                _mcp_session_id = sid
            if resp.status == 202:
                return {}
            text = resp.read().decode("utf-8", errors="replace")
    except urllib.error.HTTPError as e:
        raise RuntimeError("HTTP %d: %s" % (e.code, e.read().decode()[:400]))
    except urllib.error.URLError as e:
        raise RuntimeError("Network: " + str(e))
    return _mcp_parse(text)


def mcp_init():
    global _mcp_ready
    mcp_rpc("initialize", {
        "protocolVersion": "2025-11-25",
        "capabilities":    {},
        "clientInfo":      {"name": "fuse-fmz", "version": "1.0"},
    })
    mcp_notify("notifications/initialized")
    _mcp_ready = True
    Log("MCP ready  session_id=" + (_mcp_session_id or "(none)"))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the session is established, pulling news is just two tool calls, followed by unified format normalization and deduplication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def refresh_news():
    global _cached_news, _last_news_at, _mcp_ready
    if not JIN10_MCP_TOKEN:
        return
    now = int(time.time())
    if now - _last_news_at &amp;lt; NEWS_REFRESH_SEC and _cached_news:
        return
    _last_news_at = now
    try:
        if not _mcp_ready:
            mcp_init()
        flash_raw = mcp_call_tool("list_flash")
        news_raw  = mcp_call_tool("list_news")
        combined  = (
            _normalize(_extract_items(flash_raw), "flash") +
            _normalize(_extract_items(news_raw),  "news")
        )
        combined.sort(key=lambda x: x["ts"], reverse=True)
        _cached_news = combined[:80]
        Log("News updated: %d items" % len(_cached_news))
    except Exception as e:
        Log("News refresh failed: " + str(e))
        _mcp_ready = False

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

&lt;/div&gt;



&lt;p&gt;Different news sources return wildly varying field names (title/content/introduction, time/ts/created_at, and so on), so we added a middle layer of _extract_items + _normalize to coerce all the various formats into a standard structure of {ts, time, title, source, full_text}. That way the downstream charting and filtering logic doesn't have to care which endpoint the data actually came from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; you need to apply for an MCP API before you can use 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%2Fyz83ucj47nrzhjw0ge79.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%2Fyz83ucj47nrzhjw0ge79.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Core Idea: Making News "Grow" on the Candlesticks
&lt;/h2&gt;

&lt;p&gt;Next is the part of this tool that's actually interesting — putting news and candlesticks on the same chart.&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%2F5wq9z4sz93wy92qyjhd3.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%2F5wq9z4sz93wy92qyjhd3.png" alt=" " width="799" height="266"&gt;&lt;/a&gt;&lt;br&gt;
We added a second series to the chart, of type flags, attached to the candlestick series, acting as the "news marker layer":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def init_chart(symbol):
    global _chart
    _chart = Chart({
        "__isStock": True,
        "chart":     {"style": {"fontFamily": "Microsoft YaHei, SimHei, Arial, sans-serif"}},
        "title":     {"text": "FUSE  " + symbol},
        "xAxis":     {"type": "datetime"},
        "series": [
            {
                "id":   "kline",
                "type": "candlestick",
                "name": symbol,
                "data": [],
            },
            {
                "type":      "flags",
                "name":      "News",
                "onSeries":  "kline",
                "shape":     "circlepin",
                "color":     "#F59E0B",
                "fillColor": "#F59E0B",
                "width":     16,
                "data":      [],
            },
        ],
    })
    _chart.reset()

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

&lt;/div&gt;



&lt;p&gt;On each refresh, we first incrementally update the candlestick data, then filter out the "important" news by keyword and align it in time to the corresponding candlestick bar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def draw_chart(records):
    global _last_bar_time, _last_news_hash, _flagged_news_ts

    if not _chart or not records:
        return

    # Detect whether the news has updated; if so, reset and redraw the chart
    news_hash = hash(tuple(n.get("ts", 0) for n in _cached_news[:10]))
    news_changed = (news_hash != _last_news_hash)
    if news_changed:
        _chart.reset()
        _last_bar_time   = 0
        _last_news_hash  = news_hash
        _flagged_news_ts = set()

    # series 0: candlesticks, incremental add
    for r in records:
        t   = r['Time']
        bar = [t, r['Open'], r['High'], r['Low'], r['Close']]
        if t &amp;gt; _last_bar_time:
            _chart.add(0, bar)
            _last_bar_time = t
        elif t == _last_bar_time:
            _chart.add(0, bar, -1)

    # series 1: keyword-news flags, aligned to the candlestick bar time
    if not _cached_news:
        return
    kws = [k.strip() for k in NEWS_KEYWORD.split("|") if k.strip()]
    kw_news = [n for n in _cached_news if not kws or any(k in n.get("full_text", n["title"]) for k in kws)]
    if not kw_news:
        return
    p_ms  = PERIOD_MS.get(KLINE_PERIOD, 60000)
    first = records[0]['Time']
    last  = records[-1]['Time']
    by_bar = {}
    for n in kw_news:
        if not n.get("ts"):
            continue
        key = (n["ts"] // p_ms) * p_ms
        if key not in by_bar:
            by_bar[key] = n

    for ts, item in sorted(by_bar.items()):
        if not (first &amp;lt;= ts &amp;lt;= last):
            continue
        if ts in _flagged_news_ts:
            continue
        _chart.add(1, {
            "x": ts,
            "title": "📰",
            "text": item["title"][:100],
        })
        _flagged_news_ts.add(ts)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The effect: every time a 📰 marker appears on the chart, hovering over it shows you the corresponding headline, and its position sits exactly on the candlestick bar for the time that piece of news broke. For the first time, the price's turning points and the news's timestamps are laid out in an intuitive way on the same canvas — you no longer have to switch between two windows to figure out "what caused this stretch."&lt;/p&gt;

&lt;p&gt;NEWS_KEYWORD supports multiple keywords separated by | (e.g. "Iran|rate hike|nonfarm|tariffs"); the system prioritizes flagging news that matches a keyword onto the chart, preventing the chart from being spammed by irrelevant flash items.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Status Panel: Quotes, Positions, and News on One Screen
&lt;/h2&gt;

&lt;p&gt;Besides the chart, we also built a set of status tables, output via LogStatus, covering live quotes, account equity and P&amp;amp;L, current positions, keyword-matched news, and the latest full feed of flash news:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def make_status(symbol, ticker, positions, equity):
    # 4. Keyword news (show the matched keywords, not the source)
    kws = [k.strip() for k in NEWS_KEYWORD.split("|") if k.strip()]
    kw_rows = []
    for item in _cached_news[:40]:
        t     = item.get("time") or (_D(item["ts"]) if item.get("ts") else "-")
        title = item["title"][:90]
        text  = item.get("full_text", item["title"])
        hit_kws = [k for k in kws if k in text]
        if hit_kws:
            kw_rows.append([t, "/".join(hit_kws), title])

    if not kw_rows:
        kw_rows = [["-", "-", "No keyword-related news yet"]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a simple manual command interface — open long, open short, close long, close short, one-click close-all, and adjust order size — all received via GetCommand():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def handle_command(symbol):
    global _cur_amount, _last_news_at
    cmd = GetCommand()
    if not cmd:
        return
    Log("CMD: " + cmd)
    parts = cmd.split(":")
    key   = parts[0]
    val   = parts[1] if len(parts) &amp;gt; 1 else ""
    if   key == "openLong":    market_order(symbol, "openLong",   _cur_amount)
    elif key == "openShort":   market_order(symbol, "openShort",  _cur_amount)
    elif key == "closeLong":   market_order(symbol, "closeLong",  _cur_amount)
    elif key == "closeShort":  market_order(symbol, "closeShort", _cur_amount)
    elif key == "closeAll":    close_all(symbol)
    elif key == "amount":
        _cur_amount = float(val)
        Log("Amount updated: " + str(_cur_amount))

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

&lt;/div&gt;



&lt;p&gt;Taken together, FUSE is essentially an "information aggregation + manual execution" monitoring panel: it tries to put price, news, positions, and account status on the same screen, while the decision still rests entirely with the human. It won't judge for you — it just tries to make sure you overlook a little less when you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Limitations: The Human Is Still the Biggest Variable
&lt;/h2&gt;

&lt;p&gt;The limitations of this version are honestly quite obvious, and we don't want to dodge them.&lt;/p&gt;

&lt;p&gt;First, the mapping between news and price is "coarse-grained" — it only hangs the news onto the corresponding candlestick bar by timestamp, with no interpretation at the content level. Whether a given headline is bullish or bearish, whether it will move the market at all, is left entirely to the human to judge.&lt;/p&gt;

&lt;p&gt;Second, keyword filtering is itself a fairly crude approach. Matching a keyword doesn't mean the news is actually important, and missing one doesn't mean it isn't — &lt;strong&gt;a person's own knowledge, experience, even their state of mind that day, can carry a lot of weight here.&lt;/strong&gt; The same headline, with different people staring at the same chart, can lead to completely different conclusions.&lt;/p&gt;

&lt;p&gt;Third, the whole flow is still "human-in-the-loop," and response speed is limited by human reaction time — yet much of the time the market reacts to news on a minute-by-minute or even second-by-second scale.&lt;/p&gt;

&lt;p&gt;If there's interest in this direction, we'll later try building an LLM-based automated version, letting a model do the preliminary interpretation and importance-ranking of news, as an aid to — or even a replacement for — manual decision-making. If that sounds interesting to you, stay tuned.&lt;/p&gt;

</description>
      <category>news</category>
      <category>chart</category>
      <category>candlestick</category>
      <category>strategy</category>
    </item>
    <item>
      <title>Starting From a Viral X Influencer, I Built an Automated Trading System That Tracks Social Signals in Real Time</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Mon, 06 Jul 2026 07:25:53 +0000</pubDate>
      <link>https://dev.to/quant001/starting-from-a-viral-x-influencer-i-built-an-automated-trading-system-that-tracks-social-signals-9cd</link>
      <guid>https://dev.to/quant001/starting-from-a-viral-x-influencer-i-built-an-automated-trading-system-that-tracks-social-signals-9cd</guid>
      <description>&lt;h2&gt;
  
  
  There's Someone Going Viral on X Lately
&lt;/h2&gt;

&lt;p&gt;If you've been scrolling X (Twitter) recently, you may have already come across this account: @aleabitoreddit, handle "Serenity."&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%2F3cm9t2bt9b22osudyny9.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%2F3cm9t2bt9b22osudyny9.png" alt=" " width="544" height="356"&gt;&lt;/a&gt;&lt;br&gt;
Their bio is a single line: former well-known Reddit WallStreetBets trader, AI / semiconductor supply-chain analyst, former RISC-V Foundation member, former AI research scientist, now trading exclusively "those overlooked bottleneck companies."&lt;/p&gt;

&lt;p&gt;Sounds like the standard X braggart bio? But the numbers speak for themselves: they only registered on X in July 2025, and by May this year their follower count had grown past 350,000, with paid subscribers rivaling Musk's.&lt;/p&gt;

&lt;p&gt;What turns even more heads is the track record. They claim a peak annualized return of +501%, currently holding steady around +122%, and have publicly named more than 38 stock tickers. Someone even built a "Serenity Tracker" website to follow the positions, finding that the claimed 3,840% annualized return came mainly from getting in early on obscure small-cap names in the AI and semiconductor supply chain.&lt;/p&gt;

&lt;p&gt;Has anyone fact-checked this? Yes. Back in their Reddit days, they were banned by a WallStreetBets moderator for recommending $AXTI early (which ran from $12 to $70), allegedly because "retail making too much money made the mods uncomfortable." That detail is rather interesting.&lt;/p&gt;

&lt;p&gt;Of course, return screenshots on social media should always be taken with a grain of salt. But purely in terms of stock-picking ability, independent verifications of their recommended tickers basically all conclude the same thing — &lt;strong&gt;they really are accurate&lt;/strong&gt;. So I started wondering: could I wire their tweet signals into a trading system in real time?&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%2Fipzn2k8aznqydgg5aek4.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%2Fipzn2k8aznqydgg5aek4.png" alt=" " width="799" height="484"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Where Does the Value of This Kind of Information Come From?
&lt;/h2&gt;

&lt;p&gt;First, some background.&lt;/p&gt;

&lt;p&gt;In China's A-share market, we're used to reading research reports, watching institutional money flows, and chasing news. But in U.S. equities and crypto, the influence of KOLs on X is sometimes no less than institutional research — especially those with genuine depth in a vertical niche.&lt;/p&gt;

&lt;p&gt;Serenity's core methodology is called "Chokepoint theory": start from high-certainty end demand (the explosion in AI compute), reverse-engineer the supply chain layer by layer, and find the upstream small-cap companies with extremely high technical barriers and severe supply-demand mismatch. These companies often have tiny market caps and no institutional coverage, but once demand spikes, their upside elasticity is enormous.&lt;/p&gt;

&lt;p&gt;The logic is clear, and they track names persistently under a real (if pseudonymous) identity — not the kind of short-term hype account that pumps and disappears.&lt;/p&gt;

&lt;p&gt;Similar logic can be extended to many places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single Musk tweet on X can send Dogecoin soaring within minutes.&lt;/li&gt;
&lt;li&gt;A crypto big shot shilling an altcoin is often the beginning of retail being left holding the bag (you can also use this in reverse).&lt;/li&gt;
&lt;li&gt;Some analysts in traditional finance circles disclose positions on X ahead of their official reports.
Social-media information is itself a form of alpha — most people just haven't systematically plugged into it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  But There's a Practical Problem: We Can't Buy U.S. Stocks Directly
&lt;/h2&gt;

&lt;p&gt;Most of Serenity's recommended tickers are U.S. equities — NVDA, MRVL, AVGO, SIVE, and the like. The average user doesn't have a U.S. brokerage account, or doesn't want to go down that road. What then?&lt;/p&gt;

&lt;p&gt;Here's one idea: Binance's TradFi stock perpetual contracts.&lt;/p&gt;

&lt;p&gt;Binance has listed a batch of perpetual contracts using U.S. stocks as the underlying, settled in USDT, supporting both long and short positions, requiring no U.S. brokerage account, and trading 24/7. They currently cover dozens of mainstream U.S. tickers including NVDA, MRVL, AMD, AVGO, META, MSFT, AMZN, and GOOGL, and the list keeps expanding.&lt;/p&gt;

&lt;p&gt;This means: if Serenity recommends MRVL, I don't need to open a U.S. brokerage account — I can just open a long position on the MRVL_USDT perpetual contract on Binance.&lt;/p&gt;

&lt;p&gt;Of course, these contracts differ from holding the stock directly — there are no dividends, it's purely price tracking, and there's a funding-rate cost. But for short-to-medium-term directional trades, it's good enough.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Overall Design of the System
&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%2Fz028ri78x2a75q97yrly.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%2Fz028ri78x2a75q97yrly.png" alt=" " width="800" height="640"&gt;&lt;/a&gt;&lt;br&gt;
Once I'd thought this through, I started designing the system.&lt;/p&gt;

&lt;p&gt;The whole thing is just three steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;① Fetch Serenity's tweets in real time
        ↓
② Parse the tweet signal with an LLM (which ticker is bullish? how high is the confidence?)
        ↓
③ Match it to a Binance TradFi contract, execute the trade + risk control

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

&lt;/div&gt;



&lt;p&gt;The first step is the most critical — how do you fetch tweets in real time?&lt;/p&gt;

&lt;p&gt;Twitter's official API now charges, and it isn't cheap. Is there a free option?&lt;/p&gt;

&lt;p&gt;Yes: &lt;strong&gt;RSSHub&lt;/strong&gt;. It's an open-source tool that turns all kinds of websites into RSS feeds, including Twitter tweets. Paired with your own account's cookie, it can convert any user's tweets into an RSS feed in real time — essentially using your own account to scrape the page, completely free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying RSSHub
&lt;/h2&gt;

&lt;p&gt;I deployed RSSHub on an overseas server, so there's no extra network plumbing to deal with. The server runs Podman (a common container runtime on CentOS), and one command does it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;podman run -d \
  --name rsshub \
  -p 1200:1200 \
  -e NODE_ENV=production \
  -e CACHE_TYPE=memory \
  -e TWITTER_AUTH_TOKEN="your_auth_token" \
  -e TWITTER_COOKIE="auth_token=your_auth_token; ct0=your_ct0" \
  diygod/rsshub:latest

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

&lt;/div&gt;



&lt;p&gt;Here auth_token and ct0 are cookies from your Twitter account, found in the browser's developer tools (F12 → Application → Cookies → under x.com).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These two values are effectively your account's login credentials — only use a throwaway account, and don't leak them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl "http://localhost:1200/twitter/user/aleabitoreddit" | head -3

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

&lt;/div&gt;



&lt;p&gt;If you see RSS content beginning with &amp;lt;?xml, it worked. After that, the strategy just makes an HTTP request to this address to get the latest list of tweets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching the Contract List Dynamically
&lt;/h2&gt;

&lt;p&gt;Binance's TradFi contracts are continuously being added, so the contract table can't be hardcoded — refresh it on every startup and on a timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def refresh_equity_contracts():
    ms = exchange.GetMarkets()
    new_map = {}
    for key, market in ms.items():
        info = market.get("Info", {}) or {}
        sub_type = info.get("underlyingSubType", [])
        # Filter for TradFi EQUITY perpetual contracts
        if (
            ".swap" in key
            and "TradFi" in sub_type
            and info.get("underlyingType") == "EQUITY"
        ):
            ticker = key.replace("_USDT.swap", "")
            new_map[ticker] = key   # {"NVDA": "NVDA_USDT.swap", ...}
    return new_map

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

&lt;/div&gt;



&lt;p&gt;This way newly listed contracts are automatically included, and when the LLM parses a tweet it's also handed the latest contract list as its reference scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching the LLM to Understand Serenity's Language
&lt;/h2&gt;

&lt;p&gt;This step is the most interesting part of the whole system, and also the part that needs the most polishing.&lt;/p&gt;

&lt;p&gt;Asking the LLM directly whether "this tweet is bullish or bearish" is too crude — Serenity's way of expressing things is distinctive, and someone unfamiliar with them will misjudge a lot.&lt;/p&gt;

&lt;p&gt;For example, they often post tweets like this:&lt;/p&gt;

&lt;p&gt;"Wow… new extremely transformative news got released today. Making a certain photonics company the effective upstream laser chokepoint for $NVDA NVLink fusion CPO ecosystem. Can anyone guess the name?"&lt;/p&gt;

&lt;p&gt;This tweet is essentially &lt;strong&gt;teasing to drive engagement&lt;/strong&gt; — the next tweet is the one that actually names the bullish target. If the LLM doesn't know this habit, it might judge this as a strong bullish signal for NVDA — completely off the mark.&lt;/p&gt;

&lt;p&gt;So the system prompt needs to spell out their expression habits explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system_prompt = (
    "You are a trading-signal extractor specialized in interpreting tweets from the Twitter user 'Serenity'. "
    "This user is an AI and semiconductor supply-chain analyst, and you need to understand their particular expression habits:\n"
    "1. They rarely say 'buy' directly; instead they hint at being bullish by describing a company's supply-chain position and moat\n"
    "2. Strongly bullish keywords: 'I personally think', 'undervalued', 'going much higher', "
    "'chokepoint', 'structural', 'thesis validated', 'go brrr', 'bullish'\n"
    "3. Bearish keywords: 'avoid', 'overvalued', 'nuking', 'ban', 'bearish'\n"
    "4. Question-style tweets ('Can anyone guess?', 'Does anyone know?') "
    "are teasers to drive engagement and do not themselves constitute a trading signal; direction should be neutral\n"
    "5. Macro-trend descriptions that don't express a stance on a specific named ticker do not constitute a signal\n"
    "Output only valid JSON, nothing else."
)

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

&lt;/div&gt;



&lt;p&gt;Return a uniform JSON format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "tickers": ["MRVL", "LITE"],  # Only tickers the author explicitly takes a stance on; must be in the contract list
    "direction": "long",           # long / short / neutral
    "confidence": 85,              # 0-100, the combined strength of the signal
    "reason": "The author clearly expresses bullishness on AI networking/interconnect demand"
}

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

&lt;/div&gt;



&lt;p&gt;Criteria for judging confidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explicit stance + concrete supporting logic: 80–95&lt;/li&gt;
&lt;li&gt;Describes positive facts but takes no explicit stance: 55–75&lt;/li&gt;
&lt;li&gt;Question / teaser / engagement-style: 10–40 (force direction to neutral here)
Macro description with no specific ticker: 30–50 (return empty tickers)
In practice, accuracy on identifying "teaser tweets" is quite high — this category basically all gets filtered out correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Risk-Control Design
&lt;/h2&gt;

&lt;p&gt;Once you have the signal, the order logic isn't actually that complex — the key is keeping risk control solid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position management:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-trade position size: 5% of account equity&lt;/li&gt;
&lt;li&gt;Maximum concurrent positions: 5 tickers&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leverage: 1x, no leverage&lt;br&gt;
&lt;strong&gt;Stop-loss:&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hard stop: close immediately at a 5% loss from entry price, no room for negotiation&lt;br&gt;
&lt;strong&gt;Take-profit: no fixed take-profit, only a drawdown-based take-profit&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design comes from Serenity's holding style — their logic is to wait for the supply-demand mismatch to play out, so holding periods tend to be long, and a fixed take-profit would cut off a lot of profit. So I switched to a trailing drawdown take-profit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Once unrealized gain reaches 8% → activate trailing
# Actual drawdown threshold = max(30%, peak × 35%)
# The larger the peak, the larger the allowed drawdown

giveback_pct = max(30, peak * 0.35)
drawdown = peak - pnl_pct
if drawdown &amp;gt;= giveback_pct:
    # Execute close

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

&lt;/div&gt;



&lt;p&gt;An example to get a feel for it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Peak +20%, threshold = max(30%, 7%) = 7%, exit on a pullback to +13%&lt;/li&gt;
&lt;li&gt;Peak +80%, threshold = max(30%, 28%) = 28%, exit only on a pullback to +52%
Big winners get to run fully, small losses get cut quickly — this structure matches Serenity's own holding style.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Safety Valve: Notify First, Trade Second
&lt;/h2&gt;

&lt;p&gt;By default the strategy runs in "notify-only" mode: when there's a signal it only logs it, without actually placing an order. After observing for a while and confirming the LLM's judgments match your own expectations, you then manually switch to "live trading" mode.&lt;/p&gt;

&lt;p&gt;The dashboard has four tables:&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%2Fnv4sxe8zxc50wdh5pqu5.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%2Fnv4sxe8zxc50wdh5pqu5.png" alt=" " width="605" height="184"&gt;&lt;/a&gt;&lt;br&gt;
The Tweet History table has a "signal type" column that automatically labels each tweet as either "💎 Strong signal" or "🎭 Teaser/Question," making it easy to go back and verify the quality of the LLM's judgments.&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%2F8hfoiw6rdxnqfkm3sttv.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%2F8hfoiw6rdxnqfkm3sttv.png" alt=" " width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bit of Further Thinking
&lt;/h2&gt;

&lt;p&gt;This framework is essentially about &lt;strong&gt;structuring a specific information source in real time and wiring it into a trading system&lt;/strong&gt; — Serenity is just one example. As long as there's an RSS feed or a scrapable data source, in theory it can be plugged in. RSSHub already supports more than several thousand sources, not just Twitter.&lt;/p&gt;

&lt;p&gt;Of course, the quality of the signal source sets the ceiling for the entire system. Pick someone who shills recklessly, and no matter how elegant the system is, it's useless. Serenity works because they have a complete analytical framework — not the kind of short-term influencer who pumps and runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Closing Words
&lt;/h2&gt;

&lt;p&gt;Building out the whole system, the parts that actually took time were two: first, deploying RSSHub (overseas server + cookie configuration), and second, tuning the LLM prompt (getting it to accurately understand a specific KOL's expression habits).&lt;/p&gt;

&lt;p&gt;The code itself isn't that complex — the FMZ platform's framework wraps up a lot of the low-level details, so the focus is on thinking through the business logic clearly.&lt;/p&gt;

&lt;p&gt;I'd suggest newcomers run notify-only mode for a while first, watch the logs to see whether the LLM's judgments go noticeably off the rails, and only switch to live trading after confirming it's stable. After all, trading by following social-media signals is itself a bet on a KOL's judgment — keep position sizes light, diversify, and hold your stops.&lt;/p&gt;

&lt;p&gt;The strategy code and full implementation are in the comments section — feedback welcome.&lt;/p&gt;

</description>
      <category>strategy</category>
      <category>fmz</category>
      <category>signal</category>
      <category>trading</category>
    </item>
    <item>
      <title>Closing the Loop on Automated Trading for AI Agents: AI-Trader + FMZ MCP + RunJobs in Practice</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Mon, 06 Jul 2026 06:39:35 +0000</pubDate>
      <link>https://dev.to/quant001/closing-the-loop-on-automated-trading-for-ai-agents-ai-trader-fmz-mcp-runjobs-in-practice-58o3</link>
      <guid>https://dev.to/quant001/closing-the-loop-on-automated-trading-for-ai-agents-ai-trader-fmz-mcp-runjobs-in-practice-58o3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
Recently there's been a popular open-source project on GitHub called AI-Trader (HKUDS/AI-Trader, 19.4k stars). It positions itself as an "AI-Agent-native trading platform" — not a graphical interface built for humans, but a platform where AI agents autonomously handle registration, trading, and social interaction through APIs.&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%2Fv8itur6pk36kin2ujgmr.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%2Fv8itur6pk36kin2ujgmr.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
I wired it together with FMZ and got two complete automation pipelines running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline B (production side)&lt;/strong&gt;: An agent reads data from my live, running quantitative strategy via FMZ MCP, then publishes trading signals on AI-Trader based on the strategy's judgment — acting as a Signal Provider and earning points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline A (consumption side)&lt;/strong&gt;: An agent filters and subscribes to high-quality signal sources on AI-Trader, then uses an FMZ copy-trading strategy to turn those signals into real trades.
These two pipelines are mirror images of each other, and the core idea is the same: **treat the AI agent as an automation middle layer between the "strategy execution layer" and the "signal platform." **Below I'll break down the whole flow, with the focus on the FMZ MCP pipeline that connects the strategy data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything in this article was done with simulated funds / in a test environment, for technical demonstration only. Nothing here constitutes investment advice.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. The Roles of the Three Players
&lt;/h2&gt;

&lt;p&gt;The whole setup involves three platforms. Let's clarify what each is responsible for:&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%2Frd7x0qzk11asb0wn20pi.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%2Frd7x0qzk11asb0wn20pi.png" alt=" " width="797" height="111"&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%2Fngll67n0zbl5g9nd87qu.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%2Fngll67n0zbl5g9nd87qu.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
Key design point: the agent only operates at the "signal layer" (reading data, publishing signals, subscribing), while real trade execution is always handed off to FMZ strategies. The agent never directly touches exchange API keys — this is the security boundary of the whole setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Agent Self-Registration: Done in One Sentence
&lt;/h2&gt;

&lt;p&gt;AI-Trader's registration method is very "agent-friendly" — the platform provides a SKILL.md document, and once the agent reads it, it knows how to call the registration interface.&lt;/p&gt;

&lt;p&gt;Create an agent on RunJobs, choose Claude Sonnet as the model (for this kind of "read the docs → understand the API → multi-step operation" task, Sonnet's completion rate is fairly high), then give it just one sentence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read https://ai4trade.ai/SKILL.md and register.

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

&lt;/div&gt;



&lt;p&gt;The agent reads the doc on its own, understands the registration flow, calls the selfRegister interface, registers successfully, and obtains an Agent ID, a Token, and $100,000 in simulated funds.&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%2F50la4gy32bak3je67hi5.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%2F50la4gy32bak3je67hi5.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;A practical tip:&lt;/strong&gt; prompts for interacting with an AI agent must be written accurately. Expensive models are more forgiving; with cheaper models, you have to describe the task clearly, and it's best to give an example before letting it act — otherwise the experience with cheaper models drops off noticeably.&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%2Ffndc7utp0jpfvad4qgio.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%2Ffndc7utp0jpfvad4qgio.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
The identity obtained from registration is recognized by Token. The same Token can be reused across RunJobs, FMZ, and any local environment, with points and fund status staying in sync — this matters later when we split the pipelines across different platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Pipeline B: Using FMZ MCP to Turn Strategy Data into Trading Signals
&lt;/h2&gt;

&lt;p&gt;This is the most valuable part of the whole setup, and the part FMZ users should pay the most attention to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;I have a quantitative strategy running live on FMZ (an AskTrigger strategy based on Polymarket probability data). Rather than having the agent "make up" a trading signal out of thin air, it's better to have it read the real strategy's judgment and forward that as a signal. FMZ's MCP interface is exactly what connects this path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FMZ platform MCP configuration:&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%2Fqbwos6wv02iurvj1huqk.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%2Fqbwos6wv02iurvj1huqk.jpg" alt=" " width="799" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Provide the agent with FMZ's MCP address + Token;&lt;/li&gt;
&lt;li&gt;The agent connects to FMZ MCP and reads data from the live strategy bot;&lt;/li&gt;
&lt;li&gt;It obtains the strategy's judgment: a 99% probability that ETH rises in 15 minutes (Up ask 0.99 / Down ask 0.01);&lt;/li&gt;
&lt;li&gt;Based on this judgment, the agent publishes a trading signal on AI-Trader: ETH BUY @ $2,102.58;&lt;/li&gt;
&lt;li&gt;Published successfully, signal ID #666093, earning +9 points.&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%2Fqnscjqhr25ewfpenbjl8.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%2Fqnscjqhr25ewfpenbjl8.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Account status after publishing two signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initial funds $100,000 → current cash $76,872.50&lt;/li&gt;
&lt;li&gt;Position: 11 ETH Long @ $2,100.40, market value ~$23,104&lt;/li&gt;
&lt;li&gt;18 points (2 signals, +9 each); points can be redeemed for simulated funds at 1:1000 (e.g., 18 points = $18,000)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key takeaway
&lt;/h2&gt;

&lt;p&gt;The signal isn't something the agent made up — it's converted from data read out of your actually-running quantitative strategy. The role FMZ MCP plays here is bridging the "strategy execution layer" and the "signal publishing layer." For users who already have mature strategies on FMZ, this means your strategy gains an additional outlet for exporting value (signals + points + followers).&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Pipeline A: Subscribe to Signals → FMZ Copy-Trading Auto-Execution
&lt;/h2&gt;

&lt;p&gt;The other direction: what if you don't have your own strategy, but you want to trade by following high-quality signals?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The flow&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent queries high-quality ETH-related signals on AI-Trader, filtering by metrics like "number of followers, signal style";&lt;/li&gt;
&lt;li&gt;It selects the signal source Cyber Six Kings Trader (followed 53 times, near the top of the platform; signal style is an AI-trained mode fusing news, sentiment, and technicals);&lt;/li&gt;
&lt;li&gt;The agent calls the follow interface to subscribe;&lt;/li&gt;
&lt;li&gt;On FMZ, create a copy-trading live bot, configure the signal source and exchange (Binance Futures here); once started it monitors automatically. Give the copy-trading bot's signal address to the agent on RunJobs, so that when the agent receives a signal on AI-Trader, it pushes a message to the copy-trading bot, which then trades automatically;&lt;/li&gt;
&lt;li&gt;When a signal arrives, the FMZ copy-trading strategy executes automatically — the log shows: signal received → position Empty → ETH 0.01 → filled on Binance Futures @ $2,075.56.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;RunJobs subscribing to signals&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%2F0o2piwk4bgw9uip3xo7d.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%2F0o2piwk4bgw9uip3xo7d.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FMZ copy-trading / lead-trading strategy&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%2F9h9wvmz6wigjh22q5hpv.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%2F9h9wvmz6wigjh22q5hpv.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running the copy-trading / lead-trading strategy&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%2Fja8zvrko7xm36wymt7je.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%2Fja8zvrko7xm36wymt7je.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The copy-trading strategy receives the agent's signal and produces a trade&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%2Faz6bayyzmne709wz08b3.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%2Faz6bayyzmne709wz08b3.png" alt=" " width="800" height="481"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Key takeaway&lt;/strong&gt;&lt;br&gt;
The division of labor is clear: the agent handles selecting and subscribing to signals on AI-Trader; actual trade execution is handed off to the FMZ copy-trading strategy. The latter runs stably and continuously, independent of whether the agent is online — that's the benefit of decoupling "decision" from "execution," and the core reason for using FMZ as the execution layer instead of letting the agent place orders directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Why Run the Agent in the Cloud
&lt;/h2&gt;

&lt;p&gt;Running the agent in a cloud environment like RunJobs, rather than locally, has three practical reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Simple operation: a one-line instruction drives it — no environment setup or glue code needed;&lt;/li&gt;
&lt;li&gt;Environment isolation: the cloud agent is isolated from your local environment and doesn't expose local files or configs;&lt;/li&gt;
&lt;li&gt;Account security: the agent only does signal-layer operations; actual trade execution goes through the FMZ copy-trading strategy, and the agent never directly touches exchange API keys.
This isolation design is the security premise of the whole article: even if the agent misbehaves, all it can affect is the signal layer — it can't reach your exchange keys or your real funding channels.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Summary
&lt;/h2&gt;

&lt;p&gt;Two pipelines, one core idea — the AI agent as an automation middle layer between the strategy and the trading platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have a strategy → let the agent read your strategy data via FMZ MCP and publish signals for you on AI-Trader, earning points and followers;&lt;/li&gt;
&lt;li&gt;No strategy → let the agent filter and subscribe to high-quality signals on the platform, then land execution with an FMZ copy-trading strategy;&lt;/li&gt;
&lt;li&gt;Key point → the agent runs in the cloud, fully isolated from your local environment and trading account; execution is handed to FMZ, which is stable and doesn't depend on the agent being online.
For FMZ users, the most worthwhile thing to try is Pipeline B: your existing live strategy can connect to an AI agent through MCP, gaining an additional outward channel for exporting value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Companion Video and Strategy
&lt;/h2&gt;

&lt;p&gt;Video: &lt;a href="https://youtu.be/VulynwOB_Ao" rel="noopener noreferrer"&gt;https://youtu.be/VulynwOB_Ao&lt;/a&gt;&lt;br&gt;
Copy-trading / lead-trading strategy: &lt;a href="https://www.fmz.com/strategy/513759" rel="noopener noreferrer"&gt;https://www.fmz.com/strategy/513759&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Risk disclaimer: This article is a technical integration demonstration of an AI agent with quantitative tools, done entirely with simulated funds / in a test environment. Quantitative trading and automated trading carry the risk of capital loss. The strategies, signals, and data mentioned in this article are for technical illustration only and do not constitute any investment advice. Before going live, please backtest and test thoroughly, and bear your own trading risk.&lt;/p&gt;

</description>
      <category>runjobs</category>
      <category>strategy</category>
      <category>mcp</category>
      <category>auto</category>
    </item>
    <item>
      <title>The "Flywheel" Trading System That Went Viral on X — Can We Reproduce It Quantitatively as a Strategy?</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Mon, 06 Jul 2026 05:28:33 +0000</pubDate>
      <link>https://dev.to/quant001/the-flywheel-trading-system-that-went-viral-on-x-can-we-reproduce-it-quantitatively-as-a-9pp</link>
      <guid>https://dev.to/quant001/the-flywheel-trading-system-that-went-viral-on-x-can-we-reproduce-it-quantitatively-as-a-9pp</guid>
      <description>&lt;p&gt;Recently I came across an interesting article on X.&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%2Fny2evf0d610fr0c6esxy.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%2Fny2evf0d610fr0c6esxy.png" alt=" " width="570" height="727"&gt;&lt;/a&gt;&lt;br&gt;
The author describes how, over the years, he slowly got a "flywheel" spinning: three wheels — &lt;strong&gt;cash flow, core assets, and Alpha&lt;/strong&gt; — that feed back into one another. Strip it all the way down and it really comes to one sentence: first find a way to survive, survive long enough, and only then talk about compounding.&lt;/p&gt;

&lt;p&gt;The whole thing was built through hands-on manual practice, relying on personal experience and a feel for the market. But after reading it, one thought surfaced: at bottom, this is a set of disciplines — and discipline is precisely the thing humans are most likely to break and programs are most likely to keep. From a quant perspective, can it be reproduced?&lt;/p&gt;

&lt;p&gt;The approach to reproducing it isn't to invent some new strategy from scratch, but to rummage through what we already have: several ready-made things we've built and run before — a DCA strategy, a position-rolling strategy, a coin-selection engine (harness). Which one's natural behavior happens to be exactly the behavior a given wheel requires? Where it lines up, drop it in, and then string them together into a closed loop with capital flows.&lt;/p&gt;

&lt;p&gt;The focus of this piece is to make clear &lt;strong&gt;why these three were chosen, what the logic of each is, and how it maps onto its wheel.&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%2Fhoe75xop6028ctlchrgj.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%2Fhoe75xop6028ctlchrgj.png" alt=" " width="797" height="122"&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%2F7zvhd5gd8bd9cr33wirz.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%2F7zvhd5gd8bd9cr33wirz.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Wheel 2: Core Assets ← DCA Strategy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What the concept requires&lt;/strong&gt;&lt;br&gt;
The original article positions core assets as: used for compounding, being a friend of time, never forced to sell even in extreme markets. They are not for "trading" — they are for "holding and waiting." The implied action is just one: keep buying, never actively sell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What DCA is&lt;/strong&gt;&lt;br&gt;
DCA stands for Dollar Cost Averaging. The logic is extremely simple: at a fixed interval (say every 7 days), regardless of the current price level, buy a fixed dollar amount of the asset.&lt;/p&gt;

&lt;p&gt;Its real power shows when the market falls: with the same $200, you buy more shares when the price is low and fewer when it's high, and over the long run your cost is naturally averaged down to a reasonable level. No need to judge tops and bottoms, no need to time the market — just keep executing.&lt;/p&gt;

&lt;p&gt;More importantly, DCA by design has no active-sell action — it naturally only buys, and once bought, holds. This makes it essentially a machine that only takes in, never lets out, accumulating slowly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this one: behavioral equivalence&lt;/strong&gt;&lt;br&gt;
"Being a friend of time," translated into instructions a machine can execute, is "buy on schedule, never sell." That is behaviorally equivalent to everything left over after you delete the sell logic from DCA. No extra design needed — just remove the take-profit/sell function from a normal DCA, and it becomes the implementation of the core-assets wheel.&lt;/p&gt;

&lt;p&gt;Corresponding code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Every N days, buy a fixed amount of the core asset, buy only, never sell
if (now - lastDca &amp;gt;= DcaIntervalDays * 86400000) {
    var amount = sizeByCash(CoreSymbol, DcaAmount, CoreLeverage, price);
    marketOrder(CoreSymbol, "buy", amount);
    coreInvested += DcaAmount; // record cumulative cost; the corresponding subtraction never appears
}

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

&lt;/div&gt;



&lt;p&gt;Nowhere in the entire strategy is there any logic to sell the core asset — this is deliberate, and it corresponds to "never forced to sell even in extreme markets."&lt;/p&gt;

&lt;h2&gt;
  
  
  Wheel 1: Cash Flow ← Reserve-Floor Mechanism
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What the concept requires&lt;/strong&gt;&lt;br&gt;
The original article puts cash flow first, but its significance isn't to get rich — it's to "keep you from being forced off the train at exactly the moment you should be getting on." Put bluntly: with steady cash flow coming in, even if the market crashes and your account shrinks dramatically on paper, you don't need to sell assets to sustain your living — you tough it out and wait for compounding to happen.&lt;/p&gt;

&lt;p&gt;This is the prerequisite for the whole system to function. Without this safeguard, no matter how well you pick your core assets, being forced to cut them in a big drawdown makes it all for nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to make it equivalent in quant&lt;/strong&gt;&lt;br&gt;
Cash flow is essentially something outside the account — salary, side income, IPO/airdrop proceeds. Code can't conjure income out of thin air.&lt;/p&gt;

&lt;p&gt;But code can reproduce its core effect: never being forced to sell the core asset. The method is to set a reserve floor in the account — always keep a certain proportion of buffer capital that no order is allowed to touch. The DCA cadence itself, plus this floor, simulates the state of "cash flow keeps coming in and never gets cut off."&lt;/p&gt;

&lt;p&gt;In live trading, how comfortably stocked this floor stays actually depends on whether your real external cash flow is stable. This is also the most indirect of the three reproductions — we reproduce its effect, not the thing itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Before each order, check: balance after this order's amount can't fall below the reserve floor
var reserve = equity * ReserveFloorRatio; // e.g. keep 10% of equity
if (balance - DcaAmount &amp;lt; reserve) return; // hit the line, skip — hold the last buffer

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wheel 3: Alpha ← Coin-Selection Engine + Position-Rolling Strategy
&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%2F3jb57325f0apec6nii0f.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%2F3jb57325f0apec6nii0f.png" alt=" " width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
The Alpha wheel is the most complex; it needs to be split in two: where the targets come from, and how to trade them once you have them. These two things map to two separate strategies.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the concept requires
&lt;/h2&gt;

&lt;p&gt;The original article says Alpha's significance is "to trade tiny cost for huge upside," and emphasizes: the money you make can't be spent — it all has to be funneled into the core assets to amplify the principal.&lt;/p&gt;

&lt;p&gt;Two keywords: "tiny cost" — meaning the loss cap on every single trade must be sealed shut, so one mistake can't break your bones; "huge upside" — meaning once you're right, let the profit run as far as possible rather than bailing on a small gain. This is a naturally asymmetric structure: small loss in the bad case, large gain in the good case.&lt;/p&gt;
&lt;h2&gt;
  
  
  Top half: Coin-selection engine (harness) — find the right targets
&lt;/h2&gt;

&lt;p&gt;The crypto market has hundreds to thousands of perpetual contract instruments. Picking one at random and applying a strategy will most likely waste time or even lose money. The coin-selection engine solves exactly the question of "which coins should I trade on."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1 — Volume filter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sort all USDT perpetual contracts in descending order by dollar volume (price × volume) and keep the top 120.&lt;/p&gt;

&lt;p&gt;Why use volume as the first screen? Coins with large volume have two benefits: one, liquidity is sufficient, so orders won't get eaten up by a wide bid-ask spread; two, there are enough participants in the game, making it easier for capturable trends to form. With low-volume altcoins, the price is easily manipulated, and even the best strategy run on them is useless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2 — Moving-average backtest scoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For each shortlisted coin, run historical backtests with several moving-average parameter combinations (e.g. MA5/20, MA10/30, MA20/60), and compute the core metrics for each parameter set: win rate (share of profitable trades), profit/loss ratio (average win / average loss), max drawdown, and number of signals. Then weight the results into a composite score.&lt;/p&gt;

&lt;p&gt;This step is to find out whether the MA strategy has historically actually worked on this coin. Not every coin has a clear trend — some chop sideways for long stretches, and the MA signals are all false breakouts. Kick those coins out early and don't waste bullets on them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3 — Breakout-potential bonus&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add two dynamic factors into the score:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Volatility percentile:&lt;/strong&gt; where the current ATR (Average True Range, a measure of price volatility) sits in the percentile of its historical data. The closer to the high end, the larger current volatility is relative to history, and the more likely a big move is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume-surge coefficient:&lt;/strong&gt; the average volume of the last 5 candles ÷ the average volume of the last 50 candles. If this value is clearly greater than 1, recent volume is abnormally elevated relative to the past, capital may be flowing in, and it's an early signal of a "potential breakout."
These two factors stacked together are meant to lean, beyond static historical performance, toward targets that are "currently showing unusual activity."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final output is a whitelist, usually the 3 to 5 coins with the highest composite scores in the current phase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Coin-selection engine: volume filter → backtest scoring → breakout factors → whitelist
var pool = tickers
    .filter(t =&amp;gt; t.Symbol.endsWith("USDT.swap"))
    .sort(byQuoteVolumeDesc)
    .slice(0, TopVolumeN);

for (var coin of pool) {
    var volPct = calcVolPct(records);            // volatility historical percentile
    var surge = calcVolumeSurge(records);        // recent volume-surge coefficient
    var bt = bestBacktestScore(records, maParamsList); // MA backtest composite score
    var score = bt * 0.56 + volPct * VolSurgeBonus + surge * VolSurgeBonus;
    if (score &amp;gt;= threshold) whitelist.push(coin);
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bottom half: Position-rolling strategy — leverage small into big
&lt;/h2&gt;

&lt;p&gt;With a whitelist in hand, you still need a trading method. Here we use the position-rolling strategy we wrote before; its mechanics fit Alpha's asymmetry requirement closely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the position-rolling strategy is&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a futures trend-following strategy based on moving-average crossover signals.&lt;/p&gt;

&lt;p&gt;First, understand moving averages: take a weighted average of the closing prices of the past N candles and you get the EMA (Exponential Moving Average). The EMA gives more weight to recent prices, so it reacts faster than a plain moving average. The crossover signal works like this: a short-period EMA (e.g. EMA5) crossing up through a long-period EMA (e.g. EMA10) is a "golden cross," meaning the short-term uptrend is stronger than the long-term — a long signal; the reverse is a "death cross," a short signal.&lt;/p&gt;

&lt;p&gt;"Rolling the position" means: after each take-profit, if the MA direction still holds (i.e. the trend is still there), immediately re-enter and keep holding with the trend. In a strong trend, this method compounds profit round after round, rather than grabbing one move and leaving.&lt;/p&gt;

&lt;p&gt;Every trade has a hard stop: if the price moves against you beyond a set amount (e.g. -8%), it force-closes. This stop caps the maximum loss on every single trade — no matter how bad the market gets, a single-trade loss won't exceed this number. This is the source of the "tiny cost" mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this one: matching the asymmetric structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alpha requires "cost tiny and capped + profit as large as possible" — an asymmetric payoff structure. The position-rolling strategy naturally has this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard stop&lt;/strong&gt; = the "ticket price" of each bet; if you lose, this is the most you lose, no more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trailing take-profit + rolling re-entry&lt;/strong&gt; = if you're right, let the profit run with the trend, with no fixed cap in theory.
The two are the same thing stated differently in structure. The specific code:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Cost cap: hit the hard stop and exit immediately — this is the ticket-price ceiling for each bet
if (pnlPct &amp;lt;= -AlphaStopPct) close("hard stop");

// Let profit run: the higher the peak, the more giveback room allowed — give big winners room to breathe
var giveback = Math.max(15, maxPnl * 0.3);
if (maxPnl - pnlPct &amp;gt;= giveback) close("trailing take-profit");

// After take-profit, if the trend still holds, roll the position and keep holding — compound the profit
if (shouldRoll(records, direction)) openAlpha(direction, price);

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Organic integration: feeding Alpha's profit back into core
&lt;/h2&gt;

&lt;p&gt;The coin-selection engine finds targets, the position-rolling strategy generates returns — but that's not enough. Back to the original article: Alpha's real value is "to funnel principal to the core assets." So there must be a capital line: the money Alpha makes buys back core assets one-way; when it loses, it absolutely does not reach back and touch the core principal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Once Alpha's realized profit accumulates to a certain amount, buy back core assets
if (alphaPnlBank &amp;gt;= AlphaSweepMin) {
    var amt = sizeByCash(CoreSymbol, alphaPnlBank, 1, price);
    marketOrder(CoreSymbol, "buy", amt); // profit flows to core, enlarging the principal base
    coreInvested += alphaPnlBank;
    alphaPnlBank = 0;
}

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

&lt;/div&gt;



&lt;p&gt;It's precisely this line that turns three independent old strategies from "each running on its own" into "one wheel that feeds itself": DCA keeps feeding the core, the coin-selection engine picks candidates, the position-rolling strategy leverages small into big, the profit feeds back into the core, the core grows larger and larger, and the multiples required later get smaller and smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  What quant actually does here
&lt;/h2&gt;

&lt;p&gt;To sum it up in one sentence: the value of reproducing it isn't being "smarter," it's being "more disciplined."&lt;/p&gt;

&lt;p&gt;What makes the original system hard isn't that the logic is hard to understand — it's that humans find it too hard to execute: itchy hands wanting to sell in a crash, FOMO chasing in on a big gain, wanting to quit after a small loss, being forced to cut the core assets you should have held when you suddenly need cash. These mistakes aren't from not understanding the principles — they're from emotion.&lt;/p&gt;

&lt;p&gt;Code won't be forced to sell (the reserve floor backstops it), won't FOMO (it only acts on the whitelist and signals), won't stop the wheel (when Alpha's tiny cost is wiped out it just waits for the next trade and never touches the core). We didn't make the theory more powerful — we just handed the few most anti-human rules in it to an executor with no emotions.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is only a shallow attempt
&lt;/h2&gt;

&lt;p&gt;To be clear, this is far from the finish line, and there's one part that is clearly a different path to implementation.&lt;/p&gt;

&lt;p&gt;The real Alpha in the original article — IPOs/airdrops, presales, early structural opportunities — leverages small into big through information and resources. Position-rolling + MA coin-selection is another form of leveraging small into big, earning returns by riding trending markets. The two paths differ, but the core logic is entirely shared. In choppy markets, this trend strategy will inevitably suffer the attrition of repeated small stop-outs; it and the native opportunities complement each other, together forming the source of Alpha returns. The truly scarce opportunities still have to be judged by a human, and after earning them, manually feed them back into the core along the same line.&lt;/p&gt;

&lt;p&gt;Also, a good backtest doesn't mean live trading will profit. Fees, funding rates, slippage — these are easily ignored in a backtest, but in live trading they eat away at the margin bit by bit. Code holds the discipline, but it won't instinctively hit the brakes when it should be conservative.&lt;/p&gt;

&lt;p&gt;So what to do next isn't to rush in with big capital, but to first run it long-term on a demo account and with small positions: are those wheels feeding each other, and is the Alpha part actually adding points or just busywork? Then take the real data and refine it round after round.&lt;/p&gt;

&lt;p&gt;That line from the original article holds equally well for polishing this system — it can be slow, but it can't stop.&lt;/p&gt;

&lt;p&gt;(The above is only a record of one line of thinking and does not constitute investment advice.)&lt;/p&gt;

</description>
      <category>strategy</category>
      <category>quantitatively</category>
      <category>alpha</category>
      <category>trading</category>
    </item>
    <item>
      <title>SpaceX Is About to IPO — How Can Ordinary Retail Investors Get In? The Crypto World Already Left You a Ticket</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 02 Jun 2026 01:18:14 +0000</pubDate>
      <link>https://dev.to/quant001/spacex-is-about-to-ipo-how-can-ordinary-retail-investors-get-in-the-crypto-world-already-left-3om9</link>
      <guid>https://dev.to/quant001/spacex-is-about-to-ipo-how-can-ordinary-retail-investors-get-in-the-crypto-world-already-left-3om9</guid>
      <description>&lt;p&gt;&lt;em&gt;Posted in the Submissions section · Created 2026-05-21 · Updated 2026-05-27 · FMZ Quant&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Biggest IPO Wave in History — Are Retail Investors Just Left Watching?
&lt;/h2&gt;

&lt;p&gt;In 2026, the tech capital markets are riding an unprecedented wave of AI giants going public. Three companies, three narratives, with a combined valuation approaching $4 trillion.&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.amazonaws.com%2Fuploads%2Farticles%2Ftaiei2f0hl8m6iso7xs9.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.amazonaws.com%2Fuploads%2Farticles%2Ftaiei2f0hl8m6iso7xs9.png" alt=" " width="800" height="334"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;SpaceX:&lt;/strong&gt;&lt;br&gt;
On April 2, 2026, SpaceX confidentially filed its IPO application with the SEC, targeting a $1.75 trillion valuation and aiming to raise up to $75 billion — potentially the largest IPO in human history, surpassing Saudi Aramco's prior record. The roadshow is set to begin June 5, ticker symbol SPCX, with trading expected to start June 12. Having merged with Musk's AI company xAI, SpaceX is now a "space + AI" dual-engine super-giant. Starlink's full-year 2025 revenue topped $11.3 billion, growing nearly 50% year-over-year, with over 10.3 million subscribers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI:&lt;/strong&gt;&lt;br&gt;
Just today (May 21, 2026), it was confirmed that OpenAI will file its draft IPO prospectus within the week at the earliest, targeting a September 2026 listing. Its current valuation has reached $852 billion, and this funding round of $122 billion sets a new global record for a single startup funding round, co-led by SoftBank, Amazon, and NVIDIA. ChatGPT became the fastest product ever to break 100 million users, and AI infrastructure is becoming the "water and electricity" of a new era.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anthropic:&lt;/strong&gt;&lt;br&gt;
The most underrated of the bunch. In February 2026, Anthropic closed its Series G, raising $30 billion at a $380 billion valuation, led by Singapore's sovereign fund GIC and Coatue. Yet in the on-chain Pre-IPO market, investors' implied valuation has soared to $1.2 trillion — a 3x premium over the primary market. Driving this frenzy is the explosive spread of Claude Code among developers: Anthropic's annualized revenue jumped from $9 billion at the end of 2025 to $30 billion by May 2026 — tripling in three months. The market currently expects its IPO to land as early as October 2026.&lt;/p&gt;

&lt;p&gt;Three companies — one is the gateway to AI compute, one is humanity's interstellar exit, and one is the research institution closest to the AGI safety frontier. Standing in 2026, "AI is the primary productive force" is already global consensus, and these three targets represent almost the entirety of "the future" itself.&lt;/p&gt;

&lt;p&gt;But here's the problem:&lt;/p&gt;

&lt;p&gt;Opening a U.S. brokerage account is a tedious process — passport, bank statements, overseas accounts…&lt;br&gt;
The vast majority of IPO subscription allocations are locked up by institutions and VIP investors&lt;br&gt;
Even if you can buy in, SpaceX is priced from $420 per share (at current secondary-market prices)&lt;br&gt;
Most importantly — you simply can't grab any first-day shares&lt;br&gt;
So is there any way for retail investors to participate?&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Crypto Got There First: Pre-IPO Contracts Are Already Live
&lt;/h2&gt;

&lt;p&gt;The answer is: of course there is.&lt;/p&gt;

&lt;p&gt;Crypto markets have always had a keen nose. Within a week of SpaceX filing its IPO application, the four major exchanges — Binance, OKX, Bitget, and BingX — successively launched SpaceX Pre-IPO product lines, putting targets that used to be reachable only by institutions and VIPs directly in front of every ordinary user.&lt;/p&gt;

&lt;p&gt;There are currently three main ways to participate:&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.amazonaws.com%2Fuploads%2Farticles%2Fjt6r183hbmn8q6nl7xbf.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.amazonaws.com%2Fuploads%2Farticles%2Fjt6r183hbmn8q6nl7xbf.png" alt=" " width="800" height="136"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Important note:&lt;/strong&gt; None of the above products are real equity — they carry no voting rights, no dividend rights, and no shareholder status. OKX's perpetual contract, for example, is priced at "one-billionth of SpaceX's total valuation," provisionally assuming 1 billion shares as the estimated total share count; once the S-1 officially discloses the actual share count, a Rebase (share adjustment) will be executed.&lt;/p&gt;

&lt;p&gt;SpaceX is far from the only case. Behind this wave of Pre-IPO mania is a microcosm of the crypto market's broader RWA (Real-World Asset tokenization) explosion. Tech unicorn targets already live or in the pipeline include:&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.amazonaws.com%2Fuploads%2Farticles%2F1qfi9mj2fhwsx6akzgx0.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.amazonaws.com%2Fuploads%2Farticles%2F1qfi9mj2fhwsx6akzgx0.png" alt=" " width="798" height="155"&gt;&lt;/a&gt;&lt;br&gt;
The on-chain perpetuals market is expanding rapidly — Hyperliquid's RWA open interest has broken through a record $2.5 billion high. This sector's boom is only just beginning.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. The Logic of "Sipping the Broth": Enter the Grid Strategy
&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.amazonaws.com%2Fuploads%2Farticles%2F63q6i53h3dd7kkfuwbbf.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.amazonaws.com%2Fuploads%2Farticles%2F63q6i53h3dd7kkfuwbbf.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
Of course, going all-in on a single Pre-IPO product is no different from buying a lottery ticket — violent volatility, limited liquidity, and news that's half-true, half-false.&lt;/p&gt;

&lt;p&gt;Capturing the full rally is hard for retail investors. But using a quant strategy to continuously harvest the spread amid the chop is a different matter entirely.&lt;/p&gt;

&lt;p&gt;That brings us to today's protagonist: &lt;strong&gt;the grid strategy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some say the grid strategy is too old. True — it isn't new. But in crypto markets, "old" is precisely a compliment. Having survived countless bull-bear cycles and extreme conditions, it remains one of the most battle-tested quant strategies and one of the best suited to high-volatility instruments. A tool isn't worse for being old — what matters is that it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.1 What Is a Grid Strategy?&lt;/strong&gt;&lt;br&gt;
The core idea of a grid strategy is extremely simple: slice a price range into a number of cells, buy at the bottom of each cell and sell at the top, repeating the cycle to earn the spread.&lt;/p&gt;

&lt;p&gt;Take SPACEX_USDT as an example. Suppose we judge the price will oscillate between 2100 and 3000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upper bound: 3000
├── Cell 4: 2820 ~ 3000 ← buy@2820, sell@3000
├── Cell 3: 2640 ~ 2820 ← buy@2640, sell@2820
├── Cell 2: 2460 ~ 2640 ← buy@2460, sell@2640
├── Cell 1: 2280 ~ 2460 ← buy@2280, sell@2460
└── Cell 0: 2100 ~ 2280 ← buy@2100, sell@2280
Lower bound: 2100

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

&lt;/div&gt;



&lt;p&gt;Each time the price rises from the bottom of a cell to its top, it completes one "buy low, sell high," locking in one cell's profit. The more frequently the price oscillates, the more times you profit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.2 This Strategy's Core Upgrade: Dynamic Moving Grid&lt;/strong&gt;&lt;br&gt;
A static grid has a fatal flaw: once price breaks out of the range, the strategy is dead.&lt;/p&gt;

&lt;p&gt;This article uses the V4 dynamic moving version, which solves that pain point:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;① Percentage parameters, adaptable to any price magnitude&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Range width, shift amount, and trigger offset all use percentages
# GRID_WIDTH_PCT = 30 → range width = current price × 30%
# SHIFT_STEP_PCT → each shift = current price × x%
# BREAKOUT_TRIGGER_PCT → trigger offset = reference price × x%
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pct_to_abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ref_price&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ref_price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This means whether SPACEX is 500 or 5000, the parameters don't need to change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;② Automatically moves the range after a breakout&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_breakout_and_shift&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_price&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ref_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range_low&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;range_high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;
    &lt;span class="n"&gt;trigger_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pct_to_abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BREAKOUT_TRIGGER_PCT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ref_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Upper-bound breakout → shift range up
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;range_high&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;trigger_offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_do_shift_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Lower-bound breakout → shift range down
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;range_low&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;trigger_offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_do_shift_down_auto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;When price breaks through the upper bound (e.g., SpaceX surges), the strategy automatically:&lt;/p&gt;

&lt;p&gt;Cancels all open orders&lt;br&gt;
Market-closes all positions&lt;br&gt;
Computes a new range&lt;br&gt;
Re-deploys the grid&lt;br&gt;
It doesn't miss trending moves, nor does it stubbornly hold onto losses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;③ Three directional modes for flexibility&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;direction = "long"  → only place buy orders below price (go long)
direction = "short" → only place sell orders above price (go short)
direction = "both"  → split at the range midline: lower half long, upper half short

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

&lt;/div&gt;



&lt;p&gt;Too many cells: each cell's profit can't cover the fees — wasted effort. Too few cells: capital sits idle. Dynamic calculation makes the cell count just right.&lt;/p&gt;

&lt;h2&gt;
  
  
  3.3 Strategy Parameter Configuration Reference
&lt;/h2&gt;

&lt;p&gt;The strategy has the following core parameters, all configurable directly in the parameter panel on the FMZ platform:&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.amazonaws.com%2Fuploads%2Farticles%2F2rg8oosv8vw43g77nebt.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.amazonaws.com%2Fuploads%2Farticles%2F2rg8oosv8vw43g77nebt.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why Are Pre-IPO Contracts Well Suited to Grids?
&lt;/h2&gt;

&lt;p&gt;Grid strategies love three kinds of market environments: high volatility, wide-range oscillation, and a trend that isn't a one-directional ramp.&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.amazonaws.com%2Fuploads%2Farticles%2Fb3mdilbavt07m94ht7eu.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.amazonaws.com%2Fuploads%2Farticles%2Fb3mdilbavt07m94ht7eu.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
SpaceX Pre-IPO contracts happen to satisfy all three:&lt;/p&gt;

&lt;p&gt;High volatility: Contract pricing is driven entirely by market sentiment and valuation expectations, with strong news-driven moves — a single roadshow headline can swing the price ±20%&lt;br&gt;
Oscillation-dominated: Plenty of uncertainty exists before the IPO (pricing, window, market conditions), so price won't ramp in one direction&lt;br&gt;
Bullish long-term: The big AI + space narrative isn't going away — every pullback is a chance for the grid to add positions&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Things to Note (Must Read)
&lt;/h2&gt;

&lt;p&gt;⚠️ Everything described here is a quant-strategy discussion and does not constitute any investment advice.&lt;/p&gt;

&lt;p&gt;Before actually operating, be sure to understand the following risks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The product is essentially a synthetic derivative.&lt;/strong&gt; What you hold is not real SpaceX stock and carries no shareholder rights. The price may trade at a significant premium or discount to the real share price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IPO failure risk.&lt;/strong&gt; If the SpaceX IPO is ultimately delayed or canceled, the platform reserves the right to settle at a self-determined price or delist the product, at which point liquidity could shrink sharply.&lt;/p&gt;

&lt;p&gt;Limitations of the grid strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-directional surge: **the range shifts up frequently, and each move requires closing and rebuilding, losing held-position profit&lt;br&gt;
**One-directional crash:&lt;/strong&gt; after shifting the range down, positions can sink ever deeper — strictly control total position size&lt;br&gt;
&lt;strong&gt;Recommendation:&lt;/strong&gt; keep the grid strategy's total position under 30% of total assets, leaving ample buffer&lt;br&gt;
Contract leverage risk. Pre-IPO contracts are extremely volatile on their own — keep leverage at 2~3x. Better to earn less than to get liquidated.&lt;/p&gt;

&lt;p&gt;Parameters need tuning against live trading. Backtest environments differ from live trading in slippage, depth, etc. Start GRID_WIDTH_PCT with a wide 30% range to run stably, then narrow it gradually.&lt;/p&gt;

&lt;p&gt;**Binance algorithmic trading requires manually enabling authorization. **Before using algorithmic trading (API auto-ordering) on Binance, you must find the "algorithmic trading" option in account settings and manually click to agree to the corresponding service agreement or authorization option — otherwise API order requests will be rejected. Confirm this step is complete before connecting the strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Summary
&lt;/h2&gt;

&lt;p&gt;The listings of SpaceX and OpenAI symbolize that the value of the AI era's most core assets is now officially being priced by public markets. While retail investors find it hard to grab the most-prioritized shares during the IPO window, the crypto market's Pre-IPO contracts already offer an entry point.&lt;/p&gt;

&lt;p&gt;Within that entry point, the goal isn't to guess up or down, but to use the grid strategy to continuously harvest the spread amid the chop — and that's the posture a quant practitioner should have.&lt;/p&gt;

&lt;p&gt;The big trend is irreversible; "AI is the primary productive force" is consensus. But before the trend becomes clear, oscillation is the norm. The grid — this plain, unassuming old strategy — will always have its place in the high-volatility crypto market.&lt;/p&gt;

&lt;p&gt;Disclaimer: This article is for strategy research and technical sharing only and does not constitute investment advice. Crypto derivatives trading is extremely high-risk; make independent judgments based on your own situation only after fully understanding the risks.&lt;/p&gt;

&lt;p&gt;Strategy source code: Universal Grid Strategy (Dynamic Moving Version)&lt;/p&gt;

</description>
      <category>strategy</category>
      <category>crypto</category>
      <category>ipo</category>
      <category>spacex</category>
    </item>
    <item>
      <title>TradFi Pairs Are Live: An Adaptive Grid Strategy</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Tue, 19 May 2026 08:03:37 +0000</pubDate>
      <link>https://dev.to/quant001/tradfi-pairs-are-live-an-adaptive-grid-strategy-1lb6</link>
      <guid>https://dev.to/quant001/tradfi-pairs-are-live-an-adaptive-grid-strategy-1lb6</guid>
      <description>&lt;h2&gt;
  
  
  Preface: Why Skip Crypto and Trade TradFi?
&lt;/h2&gt;

&lt;p&gt;Anyone who has run a grid strategy on crypto knows the same nightmare: the grid is set up, the price tanks, every level fills, and you're either chasing margin or getting liquidated outright. Crypto's charm is that prices can move without an upper bound — and that is exactly what kills grid strategies. Grids are built for chop; one-way moves destroy them.&lt;/p&gt;

&lt;p&gt;Is there a class of assets that still gives you enough intraday wiggle to keep the grid firing, but doesn't routinely throw 30% or 50% melt-ups or stampedes at you? Yes — TradFi instruments.&lt;/p&gt;

&lt;p&gt;TradFi (traditional finance) derivatives include perpetual contracts on classic assets: the S&amp;amp;P 500, Nasdaq, gold, crude oil, FX, and so on. Each one is anchored by real fundamentals. Equity indices are constrained by corporate earnings and Fed policy. Commodities are driven by supply and demand. FX rates are set by the relative dynamics between two sovereign economies. These assets don't 5× overnight for no reason, and they don't crater 80% over a tweet. Their prices have gravity — they can chop in the short term, but they revert to fundamentals in the long term.&lt;/p&gt;

&lt;p&gt;That property is a near-perfect fit for grid strategies: 1%–3% of normal intraday range, enough to repeatedly trip the cells; and even in extreme moves, the grid doesn't get blown out entirely, leaving room for stops and capital management. This strategy is built on exactly that logic — it scans every TradFi pair available, picks the most actively oscillating ones, runs circulating grids on each, and automatically rotates between pairs as the volatility structure shifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: Crypto Exchanges Have Quietly Listed a New Asset Class
&lt;/h2&gt;

&lt;p&gt;Over the past two years, top crypto exchanges like OKX and Bitget have quietly listed a large batch of TradFi perpetual contracts. The coverage spans US equity indices (S&amp;amp;P 500, Nasdaq 100), individual stocks (Apple, Nvidia, Tesla), commodities (gold, oil, natural gas), and FX (EUR, JPY). In plain English: you can now trade US stocks, gold, and FX from a crypto exchange account, 24/7, with leverage.&lt;/p&gt;

&lt;p&gt;This matters for quants. On one hand, these instruments inherit the fundamental anchoring of traditional finance — prices don't moonshot or capitulate without reason. On the other hand, they're listed as perpetual contracts on a crypto exchange, which means familiar trading mechanics, deep liquidity, transparent fees, and an API surface identical to regular crypto pairs. They drop right into any existing quant stack.&lt;/p&gt;

&lt;p&gt;In other words, this opens a new arbitrage surface: use crypto-native infrastructure to harvest oscillation on traditional finance assets. This strategy is purpose-built for that scenario — let the program pick the most volatile TradFi names, run grids on them, and eat the chop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Selection Logic: Trade Only the Most Volatile Pairs&lt;/strong&gt;&lt;br&gt;
Whether a grid makes money is 60% determined by what you put it on. Pick the right pair and the cells trip a dozen times a day and profit compounds naturally. Pick the wrong one and the cells sit untouched for a week, locking up margin for nothing.&lt;/p&gt;

&lt;p&gt;The strategy's selection criterion has one dimension only: average daily range over the past N daily bars.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;volatility score = Σ [ (High_i − Low_i) / Close_i × 100 ] / N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code-wise the logic is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def score_symbol(info):
    bars = exchange.GetRecords(info["sym"], PERIOD_D1, KLINE_COUNT + 2)
    if not bars or len(bars) &amp;lt; 3:
        return None
    bars = bars[-KLINE_COUNT:]
    atr_pcts = [(b["High"] - b["Low"]) / b["Close"] * 100 for b in bars if b["Close"] &amp;gt; 0]
    avg_atr = sum(atr_pcts) / len(atr_pcts)
    # Avg daily range must be at least 1.5x grid spacing, otherwise drop the symbol
    if avg_atr &amp;lt; GRID_RATIO * 100 * 1.5:
        return None
    return {"sym": info["sym"], "atr": round(avg_atr, 3), "price": bars[-1]["Close"]}

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

&lt;/div&gt;



&lt;p&gt;The strategy periodically scans every TradFi pair, ranks them, and holds positions on the top-N by range. The entry threshold matters: average daily range has to be at least 1.5× the grid spacing, otherwise the price might not cross a single cell in a day. Anything below that is discarded immediately, so capital doesn't sit idle on a dead pair while still consuming margin.&lt;/p&gt;

&lt;p&gt;Identifying which symbols are TradFi requires special handling. On FMZ, TradFi pairs are distinguished from regular crypto via the instCategory field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def scan_tradfi():
    markets = exchange.GetMarkets()
    for sym, mkt in markets.items():
        if not sym.endswith("USDT.swap"):
            continue
        info = mkt.get("Info") or {}
        # instCategory != 1 means it's a TradFi pair
        if int(info.get("instCategory", 1)) == 1:
            continue
        result.append({"sym": sym, "base": base, "cat": cat})

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Grid Structure: Buy Low, Sell High, Rinse, Repeat&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.amazonaws.com%2Fuploads%2Farticles%2Ffdx5gdx2d8hquikqbn5q.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.amazonaws.com%2Fuploads%2Farticles%2Ffdx5gdx2d8hquikqbn5q.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
On each selected pair, the grid is centered on the current price and extended a fixed percentage in each direction, then divided into geometrically-spaced cells. Every cell below current price is seeded with a buy order, waiting for price to come down and fill it.&lt;/p&gt;

&lt;p&gt;The core of grid construction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def build_grid(sym, price):
    low  = price * (1 - LOWER_RANGE)
    high = price * (1 + LOWER_RANGE)
    # geometric spacing
    grids, p = [], low
    while p &amp;lt;= high * 1.001:
        grids.append(round(p, g_states[sym]["pp"]))
        p = p * (1 + GRID_RATIO)

    for i in range(len(grids) - 1):
        buy_p, sell_p = grids[i], grids[i + 1]
        if buy_p &amp;lt; price:
            oid = buy_open(sym, buy_p, GRID_VALUE)  # below current price: place buy
            g["status"] = "pending_buy" if oid else "skip"
        else:
            g["status"] = "above"  # above current price: wait for price to drop

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

&lt;/div&gt;



&lt;p&gt;Grid sync is the strategy's main loop. It checks every cell's order status and reacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def sync(sym):
    for g in grids:
        if g["status"] == "pending_buy":
            s, deal, avgp = check_order(g["buy_oid"])
            if s == "filled":
                # buy filled → immediately place take-profit sell
                oid = sell_close(sym, g["sp"], ct)
                g["status"] = "pending_sell"

        elif g["status"] == "pending_sell":
            s, deal, avgp = check_order(g["sell_oid"])
            if s == "filled":
                # TP filled → log profit, re-place the buy at the same level
                profit = g["ct"] * cv * (avgp - g["fp"])
                g_total_profit += profit
                oid = buy_open(sym, g["bp"], GRID_VALUE)
                g["status"] = "pending_buy"

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

&lt;/div&gt;



&lt;p&gt;The runtime logic is simple: price drops through a cell → buy. Price rises through the next cell up → take profit. After TP, re-seed the buy at the same level. Around and around. Order cancellation, missed TP, and other anomalies all have automatic detection and re-placement, so the strategy doesn't break on the occasional bad fill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Smart Rotation: Always Keep Capital on the Most Active Pairs&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.amazonaws.com%2Fuploads%2Farticles%2Fgv4ppablxeyhwijoiio3.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.amazonaws.com%2Fuploads%2Farticles%2Fgv4ppablxeyhwijoiio3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
TradFi pairs cycle their volatility regimes with macro events, earnings seasons, and policy shifts. Gold might be the most active name for a stretch; then it rolls over to oil, then to S&amp;amp;P futures. Lock yourself into one pair and eventually it enters a quiet phase and the cells go a full week without firing.&lt;/p&gt;

&lt;p&gt;This strategy re-scores and re-ranks every TradFi pair on a fixed cadence (default 48 hours), then decides whether to swap out a currently-held pair. To avoid churning fees from frequent rotations on marginal differences, it uses a hysteresis mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def needs_rebalance(new_selected):
    cur_scores = {s["sym"]: s["atr"] for s in g_score_log if s["sym"] in g_active}
    for s in new_selected:
        if s["sym"] in g_active:
            continue
        weakest_atr = min(cur_scores.values())
        threshold = weakest_atr * (1 + HYSTERESIS)  # must beat weakest by 20%
        if s["atr"] &amp;gt;= threshold:
            Log(f"{s['base']} ATR={s['atr']:.2f}% &amp;gt; threshold={threshold:.2f}%, rotating")
        else:
            Log(f"{s['base']} ATR={s['atr']:.2f}% &amp;lt; threshold={threshold:.2f}%, hysteresis holds")

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

&lt;/div&gt;



&lt;p&gt;Rotation only triggers when a candidate's average daily range is at least 20% above the weakest currently-held pair. The swap flow: cancel every open order on the outgoing pair, flatten all positions, then rebuild a complete grid on the incoming pair. The whole thing runs unattended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Key Parameters&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;TOP_N&lt;/em&gt; — number of pairs held concurrently. Default 3; capital is spread across the three most volatile names.&lt;br&gt;
&lt;em&gt;GRID_RATIO&lt;/em&gt; — grid spacing as a percentage. Default 1.5%; this is also the per-cell take-profit.&lt;br&gt;
&lt;em&gt;GRID_VALUE&lt;/em&gt; — fixed USDT amount allocated per cell. Default 50. Does not scale with price level.&lt;br&gt;
&lt;em&gt;LOWER_RANGE _— price range the grid covers. Default ±10% around current price.&lt;br&gt;
_REBALANCE_HOURS&lt;/em&gt; — rotation evaluation cadence. Default 48 hours.&lt;br&gt;
&lt;em&gt;HYSTERESIS&lt;/em&gt; — rotation threshold. Default 20%, prevents excessive churning.&lt;br&gt;
&lt;em&gt;LEVERAGE&lt;/em&gt; — leverage multiplier. Recommended ≤ 3×.&lt;br&gt;
&lt;em&gt;STOP_LOSS_RATIO&lt;/em&gt; — global stop-loss. When account loss exceeds this fraction of starting equity, auto-flatten and halt. Default 30%.&lt;br&gt;
&lt;em&gt;KLINE_COUNT&lt;/em&gt; — number of daily bars used for scoring. Default 20.&lt;br&gt;
&lt;em&gt;EXCLUDE_SYMBOLS&lt;/em&gt; — blacklist; comma-separated codes the strategy should never touch.&lt;br&gt;
&lt;strong&gt;5. Risk Controls&lt;/strong&gt;&lt;br&gt;
Global stop-loss is the final safety net. When account equity drawdown exceeds the configured fraction of starting equity, the strategy cancels everything, flattens everything, and halts all subsequent action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def check_stop():
    acc = exchange.GetAccount()
    loss = (g_init_equity - acc.Equity) / g_init_equity
    if loss &amp;gt;= STOP_LOSS_RATIO:
        Log(f"Stop-loss triggered! Loss={loss*100:.1f}% → close all and halt")
        for sym in list(g_active):
            close_all(sym)
        g_state = "STOP"

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

&lt;/div&gt;



&lt;p&gt;The selection phase's entry filter screens out low-volatility pairs, so every name that enters the strategy has enough intraday range to actually drive the grid. The blacklist lets you manually exclude pairs with poor liquidity, abnormal spreads, or unstable behavior. All order prices and sizes are strictly aligned to the exchange's precision requirements, eliminating rejected orders at the source. Capital is evenly distributed across pairs, so a loss on one pair doesn't drag the rest of the book down with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Suitable Regimes and Things to Watch&lt;/strong&gt;&lt;br&gt;
The strategy works best in choppy, range-bound markets. When a target pair oscillates inside a band, the cells get hit at high frequency and P&amp;amp;L accumulates linearly with time — almost no manual intervention required.&lt;/p&gt;

&lt;p&gt;A few things to watch:&lt;/p&gt;

&lt;p&gt;If price runs one-way and breaks below the grid's lower bound, every buy fill is stuck holding inventory until price recovers or the global stop triggers.&lt;br&gt;
Some TradFi pairs have sharply reduced liquidity during off-hours (e.g. when US equities are closed). Orders sitting unfilled for long stretches is normal.&lt;br&gt;
GRID_RATIO should be sized relative to the target pair's average daily range — somewhere between 1/3 and 1/2 of the daily range. Too wide and the trigger frequency drops; too tight and fees eat the profit.&lt;br&gt;
Keep leverage at 3× or below. High leverage in an extreme move accelerates losses faster than the stop-loss can react.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The core logic of this strategy can be stated in one sentence: keep capital on the highest-volatility TradFi pairs at all times, and let the grid be friends with time. Selection, grid construction, rotation, and risk control — four modules, chained, fully automated. The fundamental anchoring of TradFi assets guarantees prices don't drift off to infinity, while the programmatic volatility filter keeps capital allocated to the highest-efficiency names. With reasonable parameters, the strategy can produce steady grid returns across most market regimes, while the stop-loss and hysteresis mechanisms keep downside risk inside an acceptable envelope.&lt;/p&gt;

</description>
      <category>tradfi</category>
      <category>strategy</category>
      <category>crypto</category>
      <category>trading</category>
    </item>
  </channel>
</rss>
