使用机器学习开发趋势交易策略·进阶篇
📈

使用机器学习开发趋势交易策略·进阶篇

(2/3)· 均值回归之后,如何用 Savitzky-Golay 与波动率阈值把趋势延续从噪声里剥出来

含代码示例 第 2/3 篇
把价格一涨就追当成趋势跟踪,是多数 EA 爆仓的前奏。真正要命的是分不清波动噪声和趋势延续,标错训练样本等于让模型学歪。本文接着上篇聚类思路,先把标注逻辑钉死再谈训练。

「给趋势标记加上多种平滑筛子」

单一 Savitzky-Golay 滤波在跨品种上容易过拟合,欧元兑美元和黄金的波动结构不一样,硬套同一套参数会让标记失真。把平滑层做成可切换的接口,系统才可能适配不同工具的特性。 新增的 method 参数支持四种取值:savgol 保留原样、spline 走样条插值、sma 用简单移动平均、ema 用指数移动平均。后三种在 MT5 导出的收盘价序列上都能直接跑,不需要改标签逻辑。 标签函数本身没动结构,依旧用 normalized_trend 与 threshold 比较:大于阈值随机抽 1~15 根 K 线看是否触达 markup 盈利距(默认 0.5),触达标 0/1,否则标 2。外汇与贵金属属高风险品种,这套标记只解决「样本怎么打标」,不预示任何方向。 下面这段 Python 侧实现把四种平滑都列清楚了,开 MT5 把 close 导成 csv 后可直接复用验证。

MQL5 / C++
@njit
def calculate_labels_trend_different_filters(close, normalized_trend, threshold, markup, min_l, max_l):
    labels = np.empty(len(normalized_trend) - max_l, dtype=np.float64)
    for i in range(len(normalized_trend) - max_l):
        if normalized_trend[i] > threshold:
            # Проверяем условие для Buy
            rand = random.randint(min_l, max_l)
            future_pr = close[i + rand]
            if future_pr >= close[i] + markup:
                labels[i] = class="num">0.0  # Buy(Profit reached)
            else:
                labels[i] = class="num">2.0  # No profit
        elif normalized_trend[i] < -threshold:
            # Проверяем условие для Sell
            rand = random.randint(min_l, max_l)
            future_pr = close[i + rand]
            if future_pr <= close[i] - markup:
                labels[i] = class="num">1.0  # Sell(Profit reached)
            else:
                labels[i] = class="num">2.0  # No profit
        else:
            labels[i] = class="num">2.0  # No signal
    class="kw">return labels
def get_labels_trend_with_profit_different_filters(dataset, method=&class="macro">#x27;savgol&class="macro">#x27;, rolling=class="num">200, polyorder=class="num">3, threshold=class="num">0.5,
                                            vol_window=class="num">50, markup=class="num">0.5, min_l=class="num">1, max_l=class="num">15) -> pd.DataFrame:
    # Smoothing and trend calculation
    close_prices = dataset[&class="macro">#x27;close&class="macro">#x27;].values
    if method == &class="macro">#x27;savgol&class="macro">#x27;:
        smoothed_prices = savgol_filter(close_prices, window_length=rolling, polyorder=polyorder)
    elif method == &class="macro">#x27;spline&class="macro">#x27;:
        x = np.arange(len(close_prices))
        spline = UnivariateSpline(x, close_prices, k=polyorder, s=rolling)
        smoothed_prices = spline(x)
    elif method == &class="macro">#x27;sma&class="macro">#x27;:
        smoothed_series = pd.Series(close_prices).rolling(window=rolling).mean()
        smoothed_prices = smoothed_series.values
    elif method == &class="macro">#x27;ema&class="macro">#x27;:
        smoothed_series = pd.Series(close_prices).ewm(span=rolling, adjust=False).mean()
        smoothed_prices = smoothed_series.values
    else:
        raise ValueError(f"Unknown smoothing method: {method}")
    
    trend = np.gradient(smoothed_prices)
    
    # Normalizing the trend by volatility
    vol = dataset[&class="macro">#x27;close&class="macro">#x27;].rolling(vol_window).std().values
    normalized_trend = np.where(vol != class="num">0, trend / vol, np.nan)
    

◍ 清洗 NaN 与对齐标签的实操段

用 numpy 的 isnan 做掩码是最直接的去脏办法:~np.isnan(normalized_trend) 把标准化趋势里的空值位剔除,随后 close 与整体 dataset 都按同一 valid_mask 切片,保证多列严格对齐。若不对齐,后面算标签时索引错一位,回测信号就会整体偏移一根 K 线。 标签生成依赖 calculate_labels_trend_different_filters,入参除了清洗后的收盘价和趋势序列,还有 threshold、markup、min_l、max_l 四个旋钮;threshold 控制触发阈值,markup 是盈利空间系数,min_l / max_l 限定持仓长度区间。 切完标签必须用 iloc[:len(labels)] 回砍 dataset_clean,否则原数据比标签多出的尾部行会带空标签。最后 dropna() 再兜一层,把任何残留空值行清掉,返回的 dataset_clean 才是能直接喂给模型训练的干净表。外汇与贵金属波动跳空频繁,NaN 往往来自休市缺口,这套清洗在实盘前必须跑通。

MQL5 / C++
    # Removing NaN and synchronizing data
    valid_mask = ~np.isnan(normalized_trend)
    normalized_trend_clean = normalized_trend[valid_mask]
    close_clean = dataset[&class="macro">#x27;close&class="macro">#x27;].values[valid_mask]
    dataset_clean = dataset[valid_mask].copy()
    
    # Generating labels
    labels = calculate_labels_trend_different_filters(close_clean, normalized_trend_clean, threshold, markup, min_l, max_l)
    
    # Trimming the dataset and adding labels
    dataset_clean = dataset_clean.iloc[:len(labels)].copy()
    dataset_clean[&class="macro">#x27;labels&class="macro">#x27;] = labels
    
    # Filtering the results
    dataset_clean = dataset_clean.dropna()    
    class="kw">return dataset_clean

多周期趋势梯度叠加的标记逻辑

单一平滑周期做标记容易在震荡里反复打脸,把多个不同时段的同类型过滤器叠起来,只要至少一个周期发出同向信号且无反向冲突,就给这笔交易贴买/卖标签,这是把现实感知复杂化后更稳的玩法。 标记函数现在吃的是一个任意长度的周期列表,而不是写死的单个窗口。过滤器在循环里对列表里每个时段分别计算趋势梯度,所有梯度的方向都参与最终判定。 下面这段 Python(njit 加速)是核心采样器:calculate_labels_trend_multi 先拿到周期数 num_periods,再对每根 K 线随机抽一个 forward 窗口 rand(范围 min_l~max_l)。内层循环遍历每个周期 j,若归一化趋势大于 threshold 且未来价超 close+markup 则 buy_signals+1,小于 -threshold 且跌破 close-markup 则 sell_signals+1。 @njit <span class="keyword">def</span> calculate_labels_trend_multi(close, normalized_trends, threshold, markup, min_l, max_l): &nbsp;&nbsp;&nbsp;&nbsp;num_periods = normalized_trends.shape[<span class="number">0</span>]&nbsp;&nbsp;<span class="comment"># Number of periods</span> &nbsp;&nbsp;&nbsp;&nbsp;labels = np.empty(<span class="built_in">len</span>(close) - max_l, dtype=np.float64) &nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(close) - max_l): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># Select a random number of bars forward once for all periods</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rand = np.random.randint(min_l, max_l + <span class="number">1</span>) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buy_signals = <span class="number">0</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sell_signals = <span class="number">0</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># Check conditions for each period</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="background-color:rgb(255, 242, 153);"><span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(num_periods):</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> normalized_trends[j, i] &gt; threshold: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> close[i + rand] &gt;= close[i] + markup: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buy_signals += <span class="number">1</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">elif</span> normalized_trends[j, i] &lt; -threshold: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> close[i + rand] &lt;= close[i] - markup: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sell_signals += <span class="number">1</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment"># Combine signals</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> buy_signals &gt; <span class="number">0</span> <span class="keyword">and</span> sell_signals == <span class="number">0</span>: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;labels[i] = <span class="number">0.0</span>&nbsp;&nbsp;<span class="comment"># Buy</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">elif</span> sell_signals &gt; <span class="number">0</span> <span class="keyword">and</span> buy_signals == <span class="number">0</span>: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;labels[i] = <span class="number">1.0</span>&nbsp;&nbsp;<span class="comment"># Sell</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span>: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;labels[i] = <span class="number">2.0</span>&nbsp;&nbsp;<span class="comment"># No signal or conflict</span> &nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> labels <span class="keyword">def</span> get_labels_trend_with_profit_multi(dataset, method=<span class="string">'savgol'</span>, <span style="background-color:rgb(255, 242, 153);">rolling_periods=[<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>]</span>, polyorder=<span class="number">3</span>, threshold=<span class="number">0.5</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vol_window=<span class="number">50</span>, markup=<span class="number">0.5</span>, min_l=<span class="number">1</span>, max_l=<span class="number">15</span>) -&gt; pd.DataFrame: &nbsp;&nbsp;&nbsp;&nbsp;<span class="string">""" &nbsp;&nbsp;&nbsp;&nbsp;Generates labels for trading signals (Buy/Sell) based on the normalized trend, &nbsp;&nbsp;&nbsp;&nbsp;calculated for multiple smoothing periods. &nbsp;&nbsp;&nbsp;&nbsp;Args: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dataset (pd.DataFrame): DataFrame with data, containing the 'close' column. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;method (str): Smoothing method ('savgol', 'spline', 'sma', 'ema'). &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rolling_periods (list): List of smoothing window sizes. Default is [200]. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;polyorder (int): Polynomial order for 'savgol' and 'spline' methods. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;threshold (float): Threshold for the normalized trend. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vol_window (int): Window for volatility calculation. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;markup (float): Minimum profit to confirm the signal. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;min_l (int): Minimum number of bars forward. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;max_l (int): Maximum number of bars forward. &nbsp;&nbsp;&nbsp;&nbsp;Returns: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pd.DataFrame: DataFrame with added 'labels' column: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- 0.0: Buy 外层判定很直白:buy_signals>0 且 sell_signals==0 标 0.0(买),反过来标 1.0(卖),其余情况(无信号或买卖互咬)标 2.0 丢弃。默认 rolling_periods=[10,20,30]、threshold=0.5、markup=0.5、min_l=1、max_l=15,这几个数直接决定了标签的稀疏度。 别把默认参数当圣旨 threshold=0.5 在贵金属 15 分钟图上可能过于灵敏,把噪声当趋势;外汇直盘波动小,markup=0.5 按点数算或许该降到 0.2 附近才出得了标签。拿 MT5 导出的收盘价跑一遍,看 2.0 占比,占比过高说明参数组合在该品种上基本失效。

MQL5 / C++
@njit
def calculate_labels_trend_multi(close, normalized_trends, threshold, markup, min_l, max_l):
    num_periods = normalized_trends.shape[class="num">0]   # Number of periods
    labels = np.empty(len(close) - max_l, dtype=np.float64)
    for i in range(len(close) - max_l):
        # Select a random number of bars forward once for all periods
        rand = np.random.randint(min_l, max_l + class="num">1)
        buy_signals = class="num">0
        sell_signals = class="num">0
        # Check conditions for each period
        for j in range(num_periods):
            if normalized_trends[j, i] > threshold:
                if close[i + rand] >= close[i] + markup:
                    buy_signals += class="num">1
            elif normalized_trends[j, i] < -threshold:
                if close[i + rand] <= close[i] - markup:
                    sell_signals += class="num">1
        # Combine signals
        if buy_signals > class="num">0 and sell_signals == class="num">0:
            labels[i] = class="num">0.0   # Buy
        elif sell_signals > class="num">0 and buy_signals == class="num">0:
            labels[i] = class="num">1.0   # Sell
        else:
            labels[i] = class="num">2.0   # No signal or conflict
    class="kw">return labels
def get_labels_trend_with_profit_multi(dataset, method=&class="macro">#x27;savgol&class="macro">#x27;, rolling_periods=[class="num">10, class="num">20, class="num">30], polyorder=class="num">3, threshold=class="num">0.5,
                     vol_window=class="num">50, markup=class="num">0.5, min_l=class="num">1, max_l=class="num">15) -> pd.DataFrame:
    """
    Generates labels for trading signals(Buy/Sell) based on the normalized trend,
    calculated for multiple smoothing periods.
    Args:
        dataset(pd.DataFrame): DataFrame with data, containing the &class="macro">#x27;close&class="macro">#x27; column.
        method(str): Smoothing method(&class="macro">#x27;savgol&class="macro">#x27;, &class="macro">#x27;spline&class="macro">#x27;, &class="macro">#x27;sma&class="macro">#x27;, &class="macro">#x27;ema&class="macro">#x27;).
        rolling_periods(list): List of smoothing window sizes. Default is [class="num">200].
        polyorder(class="type">int): Polynomial order for &class="macro">#x27;savgol&class="macro">#x27; and &class="macro">#x27;spline&class="macro">#x27; methods.
        threshold(class="type">class="kw">float): Threshold for the normalized trend.
        vol_window(class="type">int): Window for volatility calculation.
        markup(class="type">class="kw">float): Minimum profit to confirm the signal.
        min_l(class="type">int): Minimum number of bars forward.
        max_l(class="type">int): Maximum number of bars forward.
    Returns:
        pd.DataFrame: DataFrame with added &class="macro">#x27;labels&class="macro">#x27; column:
            - class="num">0.0: Buy

「多周期标准化趋势的标签生成逻辑」

这段 Python 实现把多组平滑价格序列转成可训练标签:先对 close 价格按 savgol / spline / sma / ema 四种方式分别平滑,再用 np.gradient 取斜率,除以 vol_window=50 滚动标准差做标准化,消除波动量纲差异。 归一化趋势数组按列去 NaN 后,调用 calculate_labels_trend_multi 以 threshold=0.5 为阈值切分多空信号,原文示例输出里 1.0 对应 Sell、2.0 对应 No signal,说明标签体系至少含三类状态。 默认参数 rolling=200、polyorder=3、threshold=0.5 可直接在 MT5 导出的收盘价序列上跑;外汇与贵金属波动跳变频繁,标准化分母遇极低波动可能失真,实盘前建议把 vol_window 调到 20~30 观察信号密度变化。

MQL5 / C++
close_prices = dataset[&class="macro">#x27;close&class="macro">#x27;].values
normalized_trends = []
# Calculate normalized trend for each period
for rolling in rolling_periods:
	if method == &class="macro">#x27;savgol&class="macro">#x27;:
		smoothed_prices = savgol_filter(close_prices, window_length=rolling, polyorder=polyorder)
	elif method == &class="macro">#x27;spline&class="macro">#x27;:
		x = np.arange(len(close_prices))
		spline = UnivariateSpline(x, close_prices, k=polyorder, s=rolling)
		smoothed_prices = spline(x)
	elif method == &class="macro">#x27;sma&class="macro">#x27;:
		smoothed_series = pd.Series(close_prices).rolling(window=rolling).mean()
		smoothed_prices = smoothed_series.values
	elif method == &class="macro">#x27;ema&class="macro">#x27;:
		smoothed_series = pd.Series(close_prices).ewm(span=rolling, adjust=False).mean()
		smoothed_prices = smoothed_series.values
	else:
		raise ValueError(f"Unknown smoothing method: {method}")

	trend = np.gradient(smoothed_prices)
	vol = pd.Series(close_prices).rolling(vol_window).std().values
	normalized_trend = np.where(vol != class="num">0, trend / vol, np.nan)
	normalized_trends.append(normalized_trend)
# Transform list into 2D array
normalized_trends_array = np.vstack(normalized_trends)
# Remove rows with NaN
valid_mask = ~np.isnan(normalized_trends_array).any(axis=class="num">0)
normalized_trends_clean = normalized_trends_array[:, valid_mask]
close_clean = close_prices[valid_mask]
dataset_clean = dataset[valid_mask].copy()
# Generate labels
labels = calculate_labels_trend_multi(close_clean, normalized_trends_clean, threshold, markup, min_l, max_l)
# Trim data and add labels
dataset_clean = dataset_clean.iloc[:len(labels)].copy()
dataset_clean[&class="macro">#x27;labels&class="macro">#x27;] = labels
# Remove remaining NaN
dataset_clean = dataset_clean.dropna()
class="kw">return dataset_clean
def get_labels_trend(dataset, rolling=class="num">200, polyorder=class="num">3, threshold=class="num">0.5, vol_window=class="num">50) -> pd.DataFrame:
def get_labels_trend_with_profit(dataset, rolling=class="num">200, polyorder=class="num">3, threshold=class="num">0.5,
def get_labels_trend_with_profit_different_filters(dataset, method=&class="macro">#x27;savgol&class="macro">#x27;, rolling=class="num">200, polyorder=class="num">3, threshold=class="num">0.5,
def get_labels_trend_with_profit_multi(dataset, method=&class="macro">#x27;savgol&class="macro">#x27;, rolling_periods=[class="num">10, class="num">20, class="num">30], polyorder=class="num">3, threshold=class="num">0.5,

◍ 把无操作样本塞进第二个模型

训练逻辑整体搬进了一个独立的 processing 函数,方便在调用层控制周期与参数,不必每次改主流程。原先标记为 2.0(无操作)的样本会被直接剔除,容易在标记序列里留下空缺、丢掉上下文。 现在改用双分类器思路:第一个模型学买卖方向,第二个模型学市场状态(何时交易、何时不交易)。带 2.0 标签的样本按日期对齐后迁移进第二个模型,并在 clusters 列置零,让它顺带学习「不理想的入场点」而非被抛弃。 实跑建议直接选最新采样器,参数灵活且集成了前述改进。用 10 个训练周期、rolling=[10]、threshold=0.01、polyorder=5、vol_window=100、use_meta_dilution=True 这组设置,终端会逐次打印每次聚类的 R^2 分数,方便挑最优。 外汇与贵金属保证金交易杠杆高、回撤可能超本金,模型输出的市场状态仅作概率参考。挑出列表中 R^2 最高的模型后,调用导出函数即可推到 MT5 终端实盘验证。

MQL5 / C++
def processing(iterations = class="num">1,
                  rolling = [class="num">10],
                  threshold=class="num">0.01,
                  polyorder=class="num">5,
                  vol_window=class="num">100,  
                  use_meta_dilution = True):
    models = []
    for i in range(iterations):
        data = clustering(dataset, n_clusters=hyper_params[&class="macro">#x27;n_clusters&class="macro">#x27;])
        sorted_clusters = data[&class="macro">#x27;clusters&class="macro">#x27;].unique()
        sorted_clusters.sort()
        for clust in sorted_clusters:
            clustered_data = data[data[&class="macro">#x27;clusters&class="macro">#x27;] == clust].copy()
            if len(clustered_data) < class="num">500:
                print(&class="macro">#x27;too few samples: {}&class="macro">#x27;.format(len(clustered_data)))
                class="kw">continue
            clustered_data = get_labels_trend_with_profit_multi(
                                                clustered_data,
                                                method=&class="macro">#x27;savgol&class="macro">#x27;,
                                                rolling_periods=rolling,
                                                polyorder=polyorder,
                                                threshold=threshold,
                                                vol_window=vol_window,
                                                min_l=class="num">1,
                                                max_l=class="num">15,
把样本标注交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到滤波方向与波动率阈值的可视化,你只管调参和决策。

常见问题

均值回归标的是价格回到均值的反转概率,趋势跟踪标的是当前方向延续;前者看偏离,后者看平滑后滤波器的方向与强度。
EURUSD 相对更常出现趋势性走势,而 EURGBP 在上篇被作为区间震荡案例,两相对照能看清标注差异。
它平滑收盘价以剔除短期噪声,重点取滤波方向而非价格偏差,方向为正标买入、为负标卖出,不明确则弃用。
可以,品种页内置了滤波方向与波动率阈值的诊断视图,省去自己写 get_labels_trend 的重复劳动。
用滚动标准差把趋势强度按市况归一,避免高波动期把普通摆动误判为强趋势,过滤掉不重要的微弱方向。