<?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>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>
    <item>
      <title>Shorting Binance Delisted Perpetuals: A Grid Strategy from Monitoring to Auto-Execution</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Mon, 18 May 2026 09:18:20 +0000</pubDate>
      <link>https://dev.to/quant001/shorting-binance-delisted-perpetuals-a-grid-strategy-from-monitoring-to-auto-execution-27bb</link>
      <guid>https://dev.to/quant001/shorting-binance-delisted-perpetuals-a-grid-strategy-from-monitoring-to-auto-execution-27bb</guid>
      <description>&lt;h2&gt;
  
  
  Important Disclaimers
&lt;/h2&gt;

&lt;p&gt;Before reading and deploying this strategy, please pay attention to the following three points:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The strategy requires patience before it opens its first position&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Binance perpetual delistings are low-frequency events — they don't happen every day. After launching the strategy, you may need to wait several days, or even longer, before the first position is opened. The program spends most of its runtime in a "standby monitoring" state. Be mentally prepared for long idle periods, and don't assume the strategy has failed just because there has been no trading activity in the short term.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. There is still room to optimize the detection timing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article uses a scheme that polls the fapi/v1/exchangeInfo endpoint every 15 seconds and identifies delisting signals via changes in the deliveryDate field. Live testing shows this method has some latency and is not the fastest path. Readers can further optimize the detection mechanism based on their own needs — for example: shorten the polling interval, monitor Binance's announcement API in parallel, subscribe to WebSocket pushes, or cross-validate using multiple signal sources. The earlier you enter, the more of the first sharp drop you can capture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Shut the strategy down in time to prevent profit giveback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Live observation shows that not every delisted token grinds down all the way to the delisting moment. Some tokens drop sharply right after the announcement and then gradually rebound over several hours to a full day, sometimes recovering back to pre-announcement levels. If you don't shut the strategy down in time, the floating profits accumulated earlier will be heavily eaten into by the rebound — and can even flip from gain to loss.&lt;/p&gt;

&lt;p&gt;It is recommended to set any of the following as an active exit signal:&lt;/p&gt;

&lt;p&gt;Liquidate immediately once a preset profit target is reached;&lt;br&gt;
Force-close when the price rebounds beyond a certain percentage (e.g., 20%–30%) above the post-announcement low;&lt;br&gt;
Trigger a profit-protection close when drawdown from peak P&amp;amp;L reaches a certain percentage (e.g., 30%–50%).&lt;br&gt;
&lt;strong&gt;Do not passively wait for the T-60-minute force-close — that is a safety net, not an optimal exit.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In Binance's perpetual market, there is a special type of opportunity that most people overlook — contract delisting events.&lt;/p&gt;

&lt;p&gt;From time to time, Binance issues announcements declaring that certain illiquid or low-volume perpetual contracts will be delisted. The instant the announcement drops, the market reacts swiftly: long holders are forced to close out, panic selling cascades in, and the price typically plunges within minutes. After that comes a long, choppy downtrend that lasts all the way to delisting.&lt;/p&gt;

&lt;p&gt;Take the recent MLNUSDT case as an example:&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%2Fbvx5z1b8l77ip6fo8lkn.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.amazonaws.com%2Fuploads%2Farticles%2Fbvx5z1b8l77ip6fo8lkn.jpg" alt=" " width="800" height="302"&gt;&lt;/a&gt;&lt;br&gt;
Within half an hour the price lost nearly a third of its value, and the entire delisting cycle typically lasts several days, during which price keeps oscillating at depressed levels. For a short strategy, this is a natural breeding ground.&lt;/p&gt;

&lt;p&gt;However, trading these opportunities manually has two real difficulties:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, the time-sensitivity is extreme.&lt;/strong&gt; The first 5 minutes after the announcement is the highest-velocity drop window. If you miss the entry, chasing the short later carries significantly more risk. No human can realistically monitor this 24/7.&lt;/p&gt;

&lt;p&gt;**Second, the move is not a clean one-way waterfall. **The price keeps rebounding during the descent. Holding a pure short captures the trend, but you miss out on a large amount of high-frequency mean-reversion profit during those rebounds.&lt;/p&gt;

&lt;p&gt;To address both problems, this article presents a complete automated strategy: &lt;strong&gt;the program watches Binance for delisting signals in real time, immediately opens a short base position the moment an announcement is detected, simultaneously fires up a dynamic short grid to keep harvesting oscillation profits during the overall downtrend, and automatically closes out before delisting — all without manual intervention.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Why This Market Regime Works
&lt;/h2&gt;

&lt;p&gt;Before diving into the strategy, we need to understand the price behavior of delisted tokens — it's the foundation everything else is built on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.1 The Market Shock of a Delisting Announcement&lt;/strong&gt;&lt;br&gt;
When Binance announces that a perpetual is going to be delisted, the market's first reaction is panic. Long holders know the contract is about to disappear and must close out before delisting, otherwise they'll be force-settled. This concentrated selling produces strong short-term sell pressure and drives price down quickly.&lt;/p&gt;

&lt;p&gt;At the same time, market makers widen quotes or pull liquidity, which further amplifies the move. This is why the first few minutes after the announcement consistently see the largest drop of the entire delisting cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.2 The Choppy Downtrend Pattern&lt;/strong&gt;&lt;br&gt;
After the initial plunge, the price doesn't drop in a straight line to the bottom. It exhibits a classic choppy downtrend:&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%2Fbldm7hnynvxv2zb0su1w.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fbldm7hnynvxv2zb0su1w.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;br&gt;
There's an inherent logic to this pattern: each rebound is a short-term trader thinking the drop is overdone and trying to bottom-fish. But the fundamentals haven't changed (the contract is still about to disappear), so the dip-buyers quickly get trapped and the price resumes its decline. Each rebound is lower than the previous one, until liquidity completely dries up just before delisting.&lt;/p&gt;

&lt;p&gt;This kind of &lt;strong&gt;structured oscillation&lt;/strong&gt; is exactly the regime a grid strategy is built for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.3 Two Sources of Returns&lt;/strong&gt;&lt;br&gt;
Based on the analysis above, we can design two independent profit paths:&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%2F2uzs2oex0jliyq7n19g0.png" class="article-body-image-wrapper"&gt;&lt;img 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%2F2uzs2oex0jliyq7n19g0.png" alt=" " width="508" height="116"&gt;&lt;/a&gt;&lt;br&gt;
Stacked together, the strategy has solid earning capacity in delisting regimes. Even if a sizable rebound occurs, the grid keeps harvesting spread; and as long as the overall trend is down, the base position keeps producing profit.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The Monitoring Principle: How to Detect a Delisting Signal Instantly
&lt;/h2&gt;

&lt;p&gt;For monitoring contract information, this strategy uses a more direct approach: monitor the Binance perpetual API endpoint for data changes directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1 The Secret of the deliveryDate Field&lt;/strong&gt;&lt;br&gt;
The Binance fapi/v1/exchangeInfo endpoint returns detailed info on every contract, including a field called deliveryDate — the contract's settlement time.&lt;/p&gt;

&lt;p&gt;For perpetual contracts, this field is normally set to a far-future timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4133404800000  →  corresponds to December 31, 2100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is effectively a "never expires" placeholder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key insight: when Binance decides to delist a perpetual, it updates that contract's deliveryDate to the actual delisting timestamp at the same moment the announcement is published.&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;Normal perpetual:       deliveryDate = 4133404800000 (never expires)
About-to-delist contract: deliveryDate = 1744106400000 (2026-04-08 17:00:00)

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

&lt;/div&gt;



&lt;p&gt;This change is reflected in the API data immediately — faster than the announcement page renders, and in structured form, with no HTML parsing required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.2 The Monitoring Code&lt;/strong&gt;&lt;br&gt;
Poll the endpoint every 15 seconds and filter for USDT perpetuals whose deliveryDate has been switched to a real timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def fetch_delist_symbols():
    body   = HttpQuery("https://fapi.binance.com/fapi/v1/exchangeInfo")
    data   = json.loads(body)
    now_ms = get_now_ms()
    result = {}
    for s in data.get("symbols", []):
        if not s["symbol"].endswith("USDT"):
            continue
        if s.get("contractType") != "PERPETUAL":
            continue
        dd = s.get("deliveryDate", PERPETUAL_END)
        if dd &amp;lt; PERPETUAL_END and dd &amp;gt; now_ms:
            result[s["symbol"]] = dd
    return result

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

&lt;/div&gt;



&lt;p&gt;Sample return value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "HIPPOUSDT":  1744106400000,   # 2026-04-08 17:00:00
    "OLUSDT":     1744106400000,
    "RLSUSDT":    1744106400000,
    "PUFFERUSDT": 1744106400000,
}

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

&lt;/div&gt;



&lt;p&gt;Note: Live testing shows there is still some latency with this method. You may want to pair it with a faster verification path.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Strategy Architecture
&lt;/h2&gt;

&lt;p&gt;The whole system is split into two modules running in parallel:&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%2Fzdlgk88dez324u0hz1k2.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fzdlgk88dez324u0hz1k2.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
When a new delisting contract is discovered, a separate task object is created for it, holding all the state for that contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;task = {
    "symbol":         "HIPPO_USDT",
    "delist_time_ms": 1744106400000,
    "fund_per_task":  250.0,          # allocated capital
    "base_short_qty": 1500000,        # base-position size in contracts
    "range_high":     0.0005287,      # grid upper bound
    "range_low":      0.0004758,      # grid lower bound
    "grid_width":     0.0000529,      # range width (fixed)
    "grids":          [...],          # state of 10 grid cells
    "shift_count":    0,              # number of times the range has shifted
    ...
}

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

&lt;/div&gt;



&lt;p&gt;The task objects for different contracts are fully independent and don't interfere with each other; they run in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Capital Allocation
&lt;/h2&gt;

&lt;p&gt;After discovering N delisting contracts, dynamically split the available balance evenly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capital per contract = available balance × 80% / N

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

&lt;/div&gt;



&lt;p&gt;20% is reserved as a margin buffer to guard against short-term rebounds triggering liquidation.&lt;/p&gt;

&lt;p&gt;Important detail: as multiple contracts are initialized one after another, the available balance shrinks with each initialization (the base position consumes margin). So you can't compute everyone's allocation in a single pass before the loop — instead, re-query the balance before each initialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for idx, (binance_sym, delist_ms) in enumerate(delist_map.items()):
    update_global_account()
    remaining_count = total_new - idx
    available_now   = total_balance - margin_used
    fund_per_task   = available_now * 0.8 / remaining_count
    task = init_task(binance_sym, delist_ms, fund_per_task)

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

&lt;/div&gt;



&lt;p&gt;This way every contract gets a reasonable allocation, and you never end up with later contracts starved of capital because earlier ones consumed too much margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Base Position and Grid in Detail
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;5.1 Opening the Base Short&lt;/strong&gt;&lt;br&gt;
The moment the announcement is detected and initialization runs, open a short at market — don't wait for the grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base notional = allocated capital × 50% × leverage

Example: allocated capital = 250 USDT, leverage = 10x
Base notional = 250 × 50% × 10 = 1,250 USDT

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

&lt;/div&gt;



&lt;p&gt;The base position is held throughout, never participating in the grid's open/close cycles. It's only closed when the final force-close before delisting kicks in.&lt;/p&gt;

&lt;p&gt;The base position is the single biggest profit contributor in the strategy — if a delisted token drops 50% from announcement to delisting, the base position captures that full 50% of short P&amp;amp;L (multiplied by leverage).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.2 Setting Up the Short Grid&lt;/strong&gt;&lt;br&gt;
Take the current price as the upper bound, drop 10% below it for the lower bound, and divide into 10 equal grid cells:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Example (current price 0.0005287, range width 10%):

Upper bound = 0.0005287
Lower bound = 0.0005287 × (1 - 10%) = 0.0004758
Cell width  = (0.0005287 - 0.0004758) / 10 = 0.0000053

Cell 9: short @ 0.0005287 → cover @ 0.0005234
Cell 8: short @ 0.0005234 → cover @ 0.0005181
Cell 7: short @ 0.0005181 → cover @ 0.0005128
...
Cell 0: short @ 0.0004811 → cover @ 0.0004758

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

&lt;/div&gt;



&lt;p&gt;Capital is split equally per cell, and the logic is simple: when price rebounds to the short entry, the sell order fills and a short is opened; when price drops to the cover price, the buy order fills and the short is closed; that completes one round trip, and a new short entry is placed waiting for the next rebound.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.3 The Order-Placement Strategy at Startup&lt;/strong&gt;&lt;br&gt;
At startup, every cell whose short entry price is ≥ the current price gets a live order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current price: 0.0005287

Cell 9: short entry = 0.0005287 ≥ 0.0005287 → place order ✅
Cell 8: short entry = 0.0005234 &amp;lt;  0.0005287 → skip_below (price has already passed it)
Cell 7 and below:                              → skip_below

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

&lt;/div&gt;



&lt;p&gt;The reason to pre-place every cell above the current price is that, in a downtrend, price can spike up unexpectedly at any moment. Having all of them queued in advance guarantees we don't miss a short entry on any rebound.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Dynamic Range Shifting
&lt;/h2&gt;

&lt;p&gt;This is the most critical mechanism in the whole strategy. Price will not stay forever in the initial range — the grid must move with the price to keep harvesting spread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.1 Shifting Down (price breaks below the lower bound)&lt;/strong&gt;&lt;br&gt;
When price breaks below the grid's lower bound, the drop has exceeded the current range's coverage, and the whole range needs to shift down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Old range: 0.0004758 ~ 0.0005287
Current price: 0.0004500 (broke below 0.0004758)

Compute new range (shift step = 5%):
shift_step = 0.0004500 × 5% = 0.0000225

New upper = 0.0005287 - 0.0000225 = 0.0005062
New lower = 0.0005062 - 0.0000529 = 0.0004533

New range: 0.0004533 ~ 0.0005062

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.2 Shifting Up (rebound exceeds the upper bound)&lt;/strong&gt;&lt;br&gt;
When the price rebounds above the upper bound, the range shifts up to follow it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Old range: 0.0004494 ~ 0.0005023
Current price: 0.0005100 (broke above 0.0005023)

Shift the range up so current price falls inside the new range
New range: 0.0004758 ~ 0.0005287

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

&lt;/div&gt;



&lt;p&gt;This mechanism guarantees the grid always tracks the price no matter how it moves — you never end up in a situation where price has run away from the range and all the cells sit idle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.3 Range Width Stays Constant&lt;/strong&gt;&lt;br&gt;
Each shift only changes the range's position, never its width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grid_width = round(range_high - range_low, 8)  # fixed at initialization

# when shifting down
range_high = fp(task, range_high - shift_step)
range_low  = fp(task, range_high - grid_width)  # compute from fixed width

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

&lt;/div&gt;



&lt;p&gt;This prevents floating-point drift from gradually making the range wider or narrower over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.4 Position-Protection Mechanism&lt;/strong&gt;&lt;br&gt;
When the range shifts, all open orders are canceled and the grid is rebuilt. But what about cells that already have an open short position waiting to be covered?&lt;/p&gt;

&lt;p&gt;If you simply rebuild, those positions become "orphaned" — there's an open short but no corresponding cover order, leaving you with a naked short fully exposed to rebound risk.&lt;/p&gt;

&lt;p&gt;The solution: &lt;strong&gt;before shifting, sum up the contracts held across all open-short cells; after shifting, place a cover order on the lowest cell of the new range:&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;# sum up holdings before the shift
holding_contracts = sum(
    g.get("sell_contracts", 0)
    for g in task["grids"]
    if g["status"] in ("pending_cover", "holding_no_cover")
)

cancel_all_orders(task)
activate_grids(task, new_high, new_low)

# after the shift, place a cover-protection order on the lowest cell
if holding_contracts &amp;gt; 0:
    _place_grid_cover(task, 0, holding_contracts)

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

&lt;/div&gt;



&lt;p&gt;This way, no matter how the range moves, no open short ever loses its cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Managing Multiple Contracts in Parallel
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;7.1 Handling Newly Discovered Contracts&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%2F71ffoy72u2v0rof9xwyx.png" class="article-body-image-wrapper"&gt;&lt;img 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%2F71ffoy72u2v0rof9xwyx.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
Capital released when an old contract finishes its lifecycle re-enters the allocation pool, ensuring new contracts get adequate funding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.2 Lifecycle Management&lt;/strong&gt;&lt;br&gt;
The lifecycle of each contract:&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%2Fm6zvn0jjyynrgn5zkeue.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fm6zvn0jjyynrgn5zkeue.png" alt=" " width="800" height="1126"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  8. A Few Key Details
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;8.1 The Precision Trap for Small-Price Tokens&lt;/strong&gt;&lt;br&gt;
For tokens priced in the 0.0003 range like HIPPOUSDT, computing the range shift step hits a precision problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shift_step = 0.0003 × 5% = 0.000015

If PricePrecision = 4 (4 decimal places)
round(0.000015, 4) = 0.0  ← step rounds to zero!
→ the while loop never terminates: infinite loop

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

&lt;/div&gt;



&lt;p&gt;The fix is to skip the precision truncation on the step, and apply a minimum-step floor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shift_step = price * SHIFT_STEP_PCT
min_step   = 10 ** (-PricePrecision)
shift_step = max(shift_step, min_step)  # at least one tick

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8.2 Handling a Failed Base-Short Open&lt;/strong&gt;&lt;br&gt;
The market short can fail due to insufficient funds or a network issue. If it does, you can't proceed to build the grid — otherwise you'd have a naked grid with no base short underneath it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def open_base_short(task):
    oid = place_market_short(task, usdt_amount)
    Sleep(1000)
    amt, _, _ = get_short_position(task)
    task["base_short_qty"] = amt
    if amt &amp;lt;= 0:
        Log(f"Base short failed to open, skipping this contract")
        return False
    return True

# inside init_task
if not open_base_short(task):
    return None  # initialization failed; don't add to tasks

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8.3 Force-Close Timing and Method&lt;/strong&gt;&lt;br&gt;
The choice of T-60 minutes (rather than T-30 minutes, which is when Binance prohibits new positions) leaves a comfortable time window for closing. The closer you get to delisting, the worse the liquidity, and the harder it becomes to close.&lt;/p&gt;

&lt;p&gt;For the close, use a limit order slightly above market rather than a true market order, to avoid getting picked off by predatory matching in low-liquidity conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;buy_p = fp(task, price * 1.005)  # 0.5% above market
exchange.CreateOrder(swapcode, "closesell", buy_p, fc)

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

&lt;/div&gt;



&lt;p&gt;If a close doesn't fill in one go, retry up to 10 times, refetching the latest price each time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.4 Bonus: Funding Rate Revenue&lt;/strong&gt;&lt;br&gt;
In a downtrend, the funding rate typically favors shorts (shorts get paid). This is extra income for holding the base position — it doesn't show up in the grid's spread statistics, but it does show up as growth in account equity.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Suggested Parameters
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LEVERAGE          = 10      # leverage; recommend 5-10, don't go too high
GRID_WIDTH_PCT    = 0.10    # 10% range width covers a normal oscillation amplitude
SHIFT_STEP_PCT    = 0.05    # 5% shift step, about half the range width
GRID_COUNT        = 10      # more cells means less capital per cell
BASE_SHORT_RATIO  = 0.5     # 50% base, 40% grid, 10% buffer
FORCE_CLOSE_MINS  = 60      # force-close 60 minutes before delisting
MONITOR_INTERVAL  = 15000   # poll every 15 seconds; balances responsiveness and rate limits

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Choosing the range width:&lt;/strong&gt; a wider range covers a larger oscillation amplitude, but each cell is also wider apart, so harvest frequency drops. Adjust it based on the historical volatility of the specific token. 10% is a reasonable starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choosing the cell count:&lt;/strong&gt; more cells means tighter spacing and higher harvest frequency, but smaller capital and smaller P&amp;amp;L per cell. Too many cells, and the per-cell capital can fall below the exchange's minimum order size. 10 cells is a balanced choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Risk Warnings
&lt;/h2&gt;

&lt;p&gt;Before deploying this strategy, you need to be fully aware of the following risks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rebound risk:&lt;/strong&gt; if the news has already been priced in ahead of the announcement, you can get a "sell the news" rebound right after the announcement. The base short will be briefly underwater. The grid will still harvest spread on the rebound, but the strategy can experience a short-term drawdown overall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liquidity risk:&lt;/strong&gt; the closer you get to the delisting time, the worse the contract's liquidity gets — bid-ask widens, slippage on exit increases. The strategy includes a 0.5% price premium and 10 retries to address this, but under extreme conditions some contracts may still not fully close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False-signal risk:&lt;/strong&gt; in rare cases Binance cancels or postpones a delisting. The strategy would continue to hold the short until manual intervention or the next monitoring update overrides it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High leverage risk:&lt;/strong&gt; delisted tokens are extremely volatile. At 10x leverage, even a 10% rebound can wipe out close to your full position. Adjust leverage to match your risk tolerance and control overall exposure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start/stop in time:&lt;/strong&gt; live trading reveals that some tokens, after a day of choppy decline, gradually recover to pre-announcement levels. You need to shut the strategy down in time when this happens.&lt;/p&gt;

&lt;p&gt;Long runtime: the strategy needs to wait for opportunities — be patient.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Summary
&lt;/h2&gt;

&lt;p&gt;The core value of this strategy is converting an information advantage (detecting the delisting signal first) into a trading advantage (automated execution), and using the base + grid dual structure to capture both trend return and oscillation spread in a unidirectional downtrend.&lt;/p&gt;

&lt;p&gt;The key design decisions across the 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.amazonaws.com%2Fuploads%2Farticles%2Frzejjlhow95woo26dy55.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Frzejjlhow95woo26dy55.png" alt=" " width="774" height="288"&gt;&lt;/a&gt;&lt;br&gt;
Delisting events don't happen every day, but every one of them is a relatively high-conviction trading opportunity. With programmatic monitoring and execution, you can participate in these opportunities steadily — without staring at the screen.&lt;/p&gt;

</description>
      <category>gridstradegy</category>
      <category>binance</category>
      <category>autotrading</category>
      <category>fmz</category>
    </item>
    <item>
      <title>From 99 Traders to One Signal: Implementing a Distilled KOL Consensus Strategy on FMZ</title>
      <dc:creator>Dream</dc:creator>
      <pubDate>Mon, 18 May 2026 08:37:43 +0000</pubDate>
      <link>https://dev.to/quant001/from-99-traders-to-one-signal-implementing-a-distilled-kol-consensus-strategy-on-fmz-n2a</link>
      <guid>https://dev.to/quant001/from-99-traders-to-one-signal-implementing-a-distilled-kol-consensus-strategy-on-fmz-n2a</guid>
      <description>&lt;p&gt;The word "distillation" has been showing up everywhere lately. In AI it usually means compressing complex capabilities into a more compact, reusable form. The same idea carries over to strategy research — and when you make it concrete, what you're really doing is taking knowledge that was previously scattered, fuzzy, and dependent on subjective experience, and turning it into something you can compute, verify, and continue to refine.&lt;/p&gt;

&lt;p&gt;The crypto-kol-quant project has been getting a lot of attention recently. What's actually interesting about it isn't the number of KOLs it scrapes, or the fact that it uses LLMs. It's that the project tries to do something quantitative research rarely attempts: distill traders' experience into a set of computable capability factors, and then aggregate those factors into a consensus signal. That's a question worth taking seriously. Because if a group of long-active, stylistically consistent traders really have built up their own cognitive frameworks for the market, those frameworks shouldn't only live in tweets, charts, and offhand remarks — they ought to be extractable, organized, and made part of a runnable strategy pipeline.&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%2Foyf8t8w70jiyv0s2tyij.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Foyf8t8w70jiyv0s2tyij.png" alt=" " width="800" height="624"&gt;&lt;/a&gt;&lt;br&gt;
Working from that premise, we built an early implementation inside the FMZ environment. The goal wasn't to "port" the project — it was to wire up its core logic from end to end: pull market data, translate the market into structured state, decide which trading capabilities are being triggered by that state, map those capabilities back to trader profiles, and finally aggregate every trader's individual judgment into a weighted consensus signal. It's clearly not a finished trading system, but it does demonstrate one important thing: trader experience really can be compressed, structured, and inserted into a strategy's decision flow.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Gets Distilled Is Capability, Not Opinion
&lt;/h2&gt;

&lt;p&gt;When people first encounter a project like this, they tend to read it as "a KOL sentiment strategy." That's not quite right. The original project isn't trying to count who's bullish today or who's calling tops and bottoms. It's pushing further: how does this trader actually understand the market? Under what kind of structure does he lean long? Does he focus on trend, position, pattern, volatility, or macro context? And can the way he makes those calls be organized into a stable set of capability tags?&lt;/p&gt;

&lt;p&gt;Once you frame the problem that way, the strategy's center of gravity shifts. The system stops caring about any single statement and starts caring about the methodology behind it. Put differently: what gets distilled here isn't text — it's trading knowledge itself. The system tries to translate subjective experience, which used to require a human to interpret, into rules a program can recognize and call. That's the biggest difference between this and the usual sentiment models. It's not measuring how hot the market mood is. It's reconstructing how different trading frameworks would react to the current market.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Translate the Market Into State Variables
&lt;/h2&gt;

&lt;p&gt;For distillation to actually land, the first step can't be prediction — it has to be feature engineering. The reason is simple: a trader's language is written for humans, not programs. Take a sentence like "price pulled back to a key moving average, decent spot to add on the second tap." A trader gets it instantly, but a program has to break it down: which moving average — the 50-day or the 200-day? Is price actually near that line? Is the trend still intact? Did a confirming candle appear?&lt;/p&gt;

&lt;p&gt;So the system's first job isn't to produce a long/short call. It's to convert raw market data into a set of structured states. The most basic layer here uses price to build trend and momentum features. Moving averages, exponential moving averages, RSI, MACD — these aren't there to pile up indicators, they're there to answer a simple question: roughly what state is the market in right now?&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%2Fwlg9ho64zcjkwjcgdvl9.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fwlg9ho64zcjkwjcgdvl9.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;br&gt;
The key code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Use moving averages of different periods to describe price's position within trends
f['ma20']  = _sma(c, 20)
f['ma50']  = _sma(c, 50)
f['ma100'] = _sma(c, 100)
f['ma200'] = _sma(c, 200)

# Exponential moving averages weight recent price changes more heavily
f['ema20'] = _ema(c, 20)
f['ema50'] = _ema(c, 50)

# RSI captures whether the market is overbought/oversold or losing momentum
f['rsi14'] = _rsi(c, 14)

# MACD line, signal line, and histogram together track trend and momentum shifts
ml, ms, mh = _macd(c)
f['macd']      = ml
f['macd_sig']  = ms
f['macd_hist'] = mh

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

&lt;/div&gt;



&lt;p&gt;The code isn't doing anything complicated. Moving averages help the system locate current price relative to longer-term trend; RSI and MACD describe whether momentum is building or fading. None of this is a trading decision yet — it's purely a "market state" layer.&lt;/p&gt;

&lt;p&gt;The system also adds volatility and positional features, because a lot of trading judgments don't rely on trend alone — they also depend on whether we're in a volatility-compression phase, or whether price is sitting near a range high or low.&lt;/p&gt;

&lt;p&gt;The 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;# Log returns are the basis for volatility computation
logr = np.log(c / c.shift(1))

# 30-day annualized realized volatility, gauging current volatility level
# Note: sqrt(365) — crypto trades around the clock, so we annualize on
# calendar days rather than the 252 trading days used for equities
f['rv30'] = logr.rolling(30, min_periods=10).std() * np.sqrt(365)

# Recent 20-day and 50-day highs/lows, used to locate price within recent structure
f['high_20d'] = h.rolling(20, min_periods=1).max()
f['low_20d']  = l.rolling(20, min_periods=1).min()
f['high_50d'] = h.rolling(50, min_periods=1).max()
f['low_50d']  = l.rolling(50, min_periods=1).min()

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

&lt;/div&gt;



&lt;p&gt;rv30 captures the recent annualized volatility level. The range highs/lows tell the system where current price sits inside the recent price structure. On top of that, macro context gets folded into the state space too — there's a kind of trader who doesn't only look at the coin price; they're simultaneously watching the dollar index, equity risk appetite, and the rate environment. The code aligns these to the daily frame and then turns them into readable state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# DXY as a backdrop for dollar strength
if 'DXY' in macro:
    dxy = _align(macro['DXY'])
    f['dxy_ret_20d']    = dxy.pct_change(20)
    f['dxy_trend_down'] = (dxy.pct_change(20) &amp;lt; -0.01).astype(int)

# SPX as a proxy for risk appetite
if 'SPX' in macro:
    spx = _align(macro['SPX'])
    f['spx_ret_20d']  = spx.pct_change(20)
    f['spx_trend_up'] = (spx.pct_change(20) &amp;gt; 0).astype(int)

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

&lt;/div&gt;



&lt;p&gt;The point of this whole step compresses to one sentence: take "what the market looks like right now" and turn it into structured state the machine can keep reading. Without this layer, there's nothing left to distill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Encode Subjective Experience as Capability Factors
&lt;/h2&gt;

&lt;p&gt;Features alone aren't enough — features only describe the market, they don't say what that state means. The next step is to write traders' experience into rules: given the current state variables, decide which trading capabilities are being triggered.&lt;/p&gt;

&lt;p&gt;This is where the strategy's distillation flavor is strongest. We're no longer talking abstractly about "frameworks that matter"; we're committing them to actual program conditions. The current implementation's capability factors span pattern, structure, indicator, cycle, and macro layers. Some come from pattern recognition — bull flags, bear flags, double tops/bottoms, head-and-shoulders, triangles. Some come from structural analysis — Wyckoff, SMC, ICT-style frameworks. Some come from indicators themselves — RSI divergences, golden/death crosses, Bollinger band squeeze breakouts. And some come from cycle and macro context — halving cycles, trending vs. ranging market regime shifts, DXY drawdowns, risk-on rebounds.&lt;/p&gt;

&lt;p&gt;A textbook example is "trend pullback continuation." Plenty of traders share this pattern: if the larger trend is still up, price pulls back to a key moving average, and the current candle shows a confirming bid, that often means the trend continues. The program states it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Check whether current price is close to the 50-day moving average
near_ma50 = abs(close - ma50_v) / close &amp;lt; 0.02 if close &amp;gt; 0 else False

# If the 50-day MA is still above the 200-day MA, and a green candle shows up
# right at the pullback, score this as a trend-continuation capability signal
# (In production you'd want extra confirmation — volume, candle body, lower wick)
s['cap_014_trend_pullback_continuation'] = 0.6 if (ma50_gt and near_ma50 and is_green) else 0.0

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

&lt;/div&gt;



&lt;p&gt;There's nothing mysterious here — it's just breaking a sentence of human language into a few conditions a machine can evaluate one by one. Another example is "Bollinger squeeze breakout." For many traders, a long stretch of compressed volatility followed by a sudden expansion (up or down) usually signals a fresh directional choice. The rule looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# bb_w20_p1 is the 20-period mean of Bollinger bandwidth (the squeeze baseline).
# If the previous candle's bandwidth sits below that baseline, treat it
# as a volatility-contraction state.
squeezed = bb_w_p1 &amp;lt; bb_w20_p1 if bb_w20_p1 &amp;gt; 0 else False

# Squeeze followed by a breakout above the upper band -&amp;gt; positive signal
# Squeeze followed by a break below the lower band  -&amp;gt; negative signal
s['cap_021_bollinger_squeeze_breakout'] = (
    0.6  if (squeezed and close &amp;gt; bb_u) else
   -0.6  if (squeezed and close &amp;lt; bb_l) else 0.0
)

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

&lt;/div&gt;



&lt;p&gt;Macro factors get the same treatment. For traders who lean macro, BTC isn't an isolated price series — it's affected by the dollar, equities, and the rate environment, so those views are also written into capability checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Falling DXY is generally read as a positive backdrop for BTC
s['cap_027_dxy_inverse_btc']  = 0.4 if (not _nm(dxy_r20) and dxy_r20 &amp;lt; -0.01) else 0.0

# Rising S&amp;amp;P implies improving risk appetite
s['cap_028_spx_risk_on_off']  = 0.4 if (not _nm(spx_r20) and spx_r20 &amp;gt;  0.02) else 0.0

# Falling short-end yields imply a marginal liquidity tailwind
s['cap_029_yields_liquidity'] = 0.4 if (not _nm(y_r20)   and y_r20   &amp;lt; -0.02) else 0.0

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

&lt;/div&gt;



&lt;p&gt;What matters at this layer isn't how many rules we wrote — it's that we completed the most critical move in the entire distillation: compressing judgments that previously required subjective interpretation into computable conditions. Worth noting in passing: most capability factors in the current version are condition-triggered rather than continuously scored. That means the system is closer to detecting whether a particular structure holds than to repricing every micro-fluctuation. It's why this implementation fits daily or mid-to-low frequency reasoning better than high-frequency trading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Don't Sum Factors — Map Them Back to Trader Profiles
&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%2Fbiz0q8dtr0y9vi1m4o29.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fbiz0q8dtr0y9vi1m4o29.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
If the strategy stopped at the factor layer, it would still just be a regular rule system. What makes the original project distinctive is that it doesn't stop there — it pushes one step further: factors don't directly determine direction; they get mapped back to trader profiles first.&lt;/p&gt;

&lt;p&gt;This part matters. Real traders don't "use all capabilities equally." Some lean into trend, some into structure, some into cycle, some into macro. Faced with the same market state, different traders care about completely different things. So instead of averaging all factors at once, the system reads each trader's capability preferences first, then computes that trader's individual signal given the current factor state.&lt;/p&gt;

&lt;p&gt;The profile-loading logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# For each trader, load the capability factors they actually use, with weights
caps = {c['id']: float(c.get('weight', 0.5))
        for c in p.get('capabilities_used', [])}

profiles.append({
    'handle': p.get('handle', item['name'][:-5]),
    'caps':   caps
})

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

&lt;/div&gt;



&lt;p&gt;Each profile is essentially answering one question: which capability factors does this trader actually rely on, and how heavily does each one weigh in his framework? Once profiles are in hand, the system computes each trader's "personal signal" for the current market:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for p in profiles:
    sig = 0.0
    wt  = 0.0

    # Iterate over every capability this trader cares about
    for cap_id, w in p['caps'].items():
        score = factor_scores.get(cap_id, 0.0)

        # Current factor score scaled by this trader's preference weight
        sig += w * score
        wt  += abs(w)

    # Normalize to get this trader's personal signal in the current market
    trader_raw = sig / wt if wt &amp;gt; 0 else 0.0

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

&lt;/div&gt;



&lt;p&gt;By this point the system feels different. It's no longer just looking at "which factors lit up." It's approximating something else: if you handed today's market to these 99 traders, how would each of them judge it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: From Personal Signals to Weighted Consensus
&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%2F030xtk18v9cs6xz0pssl.png" class="article-body-image-wrapper"&gt;&lt;img 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%2F030xtk18v9cs6xz0pssl.png" alt=" " width="800" height="377"&gt;&lt;/a&gt;&lt;br&gt;
Once each trader's personal signal is computed, the system finally enters the consensus layer. "Consensus" here isn't a vote, and it's definitely not whoever-shouts-loudest-wins — it factors in historical effectiveness as well.&lt;/p&gt;

&lt;p&gt;A quick note for readers new to the term: IC stands for Information Coefficient — the historical correlation between a signal and forward returns. Higher absolute IC means the signal carries more information.&lt;/p&gt;

&lt;p&gt;The two most important outputs in the current code are ic_weighted and trust_adjusted. The core logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# First: weight only traders with positive IC, producing ic_weighted
pos_w  = sum(max(t['ic'], 0) for t in trader_signals)
ic_wt  = (
    sum(t['signal'] * max(t['ic'], 0) for t in trader_signals) / pos_w
    if pos_w &amp;gt; 0 else 0.0
)

# trust_adjusted goes a step further:
# positive-IC traders are used directly; negative-IC traders are flipped
# and then everything is weighted by absolute IC magnitude
abs_w = sum(abs(t['ic']) for t in trader_signals)
trust = (
    sum((t['signal'] if t['ic'] &amp;gt;= 0 else -t['signal']) * abs(t['ic'])
        for t in trader_signals) / abs_w
    if abs_w &amp;gt; 0 else 0.0
)

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

&lt;/div&gt;



&lt;p&gt;Two simple but important principles are embedded here. First: traders who've been more effective historically carry more weight today. Second: traders whose historical IC is negative aren't thrown out — they may be used as inverse indicators instead. So trust_adjusted isn't just "what does the crowd think" — it's "who thinks what, and whose view do we actually trust."&lt;/p&gt;

&lt;p&gt;One caveat worth flagging: flipping a negative-IC trader assumes that negative IC reflects stable contrarian information, not statistical noise. In practice you'd want a significance test or a sample-size gate before using the inverse — otherwise you're amplifying randomness instead of correcting it.&lt;/p&gt;

&lt;p&gt;This is also why the system reads differently from a normal sentiment model. It isn't tallying voices — it's running a round of cognitive aggregation that's been checked against history. Compress the whole method into one sentence: turn the market into state variables, map state variables into capability factors, map capability factors into per-trader personal signals, and finally aggregate those personal signals by historical effectiveness into a consensus call.&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%2Fgbv1qj42l9kwe19cm8p0.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Fgbv1qj42l9kwe19cm8p0.png" alt=" " width="800" height="329"&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.amazonaws.com%2Fuploads%2Farticles%2Ffrz8eowooy9k4ro9hmon.png" class="article-body-image-wrapper"&gt;&lt;img 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%2Ffrz8eowooy9k4ro9hmon.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the FMZ Implementation Actually Achieves
&lt;/h2&gt;

&lt;p&gt;If the project stayed in research mode, the system would feel more like a "consensus analyzer." On FMZ, the priority is wiring the entire pipeline so it can keep running. The most important code is just three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Step 1: turn raw price data and macro variables into structured state
feat_df = build_features(records, macro if macro else None)

# Step 2: evaluate which capability factors are triggered given the state
factor_scores = evaluate_factors(feat_df)

# Step 3: map factors back to trader profiles, then aggregate to consensus
consensus = compute_consensus(factor_scores)

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

&lt;/div&gt;



&lt;p&gt;These three lines are the strategy's three most important abstractions. Layer one handles market state; layer two handles capability evaluation; layer three handles trader consensus. Execution, risk control, and state display sit downstream of these, but from a research-logic perspective the critical part is already complete. The point of this implementation isn't how many runtime details it adds — it's that the original project's capability profiles are no longer static files, the factors are no longer just research outputs, and the consensus is no longer just a number in a report. They've been wired into a continuously running judgment loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It's Still Just a Prototype
&lt;/h2&gt;

&lt;p&gt;This implementation isn't the endgame. The current code uses BTC daily bars, so it's better suited to mid-to-low frequency consensus calls than to high-frequency trading. Its core still revolves around daily structure, cycle position, macro backdrop, and trader capability preferences. Trader profiles and IC values are still static inputs — the system hasn't yet entered an online-evolution phase. In other words, "knowledge distillation" step one is done, but "the distilled knowledge keeps self-correcting" hasn't fully landed.&lt;/p&gt;

&lt;p&gt;That doesn't take away from what the project does demonstrate, which is itself important: trader experience really can be compressed, structured, and inserted into a strategy pipeline. The value isn't that it produces stable returns yet — it's that it advances a research path that previously sat at the concept level into something runnable. How those capability factors should evolve, how trader weights should update, and how consensus should keep recalibrating against live markets — those are questions only more runtime data can answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;What's genuinely instructive about crypto-kol-quant isn't the volume of trendy concepts it strings together. It's that it pushes a hard-to-systematize problem one step forward: take traders' experience, turn it from expression into capabilities, from capabilities into factors, from factors into consensus. The FMZ implementation's job is to actually run that distillation pipeline end-to-end. It doesn't oversell itself as the final answer, and it doesn't try to hide the fact that it's still an early prototype. But it does prove one thing: trading experience doesn't have to live only in charts and language — it can be distilled, structured, executed, and even placed inside a system that continuously judges the market.&lt;/p&gt;

&lt;p&gt;If traditional quant is good at finding patterns inside price series, then a strategy of this kind points to a direction worth pushing: extract patterns from human cognition, then let those patterns participate in the market in turn. And that, more than anything else, may be why "distillation" deserves attention in strategy research.&lt;/p&gt;

&lt;p&gt;Original project: Tower of Locked Demons Skill — Refining 99 Crypto Traders (suo yao ta Skill)&lt;/p&gt;

&lt;p&gt;Special thanks to user GiantBin for the underlying ideas. If you have your own thoughts on this direction, we'd welcome the conversation.&lt;/p&gt;

</description>
      <category>kol</category>
      <category>strategy</category>
      <category>fmz</category>
      <category>traders</category>
    </item>
  </channel>
</rss>
