获取市场优势的秘诀(第二部分):预测技术指标·进阶篇
📘

获取市场优势的秘诀(第二部分):预测技术指标·进阶篇

第 2/2 篇

「预测均线变化比猜收盘价靠谱」

直接拿模型去猜收盘价,结果惨不忍睹:逻辑回归 0.5220、LDA 0.5192、XGB 0.5120、简单神经网络 0.5235、大型神经网络 0.5187,全挤在 50% 出头,基本等于抛硬币。 换目标去预测移动平均的变化,准确率立刻抬到 69% 附近:逻辑回归 0.6927、LDA 0.6964、XGB 0.6933、简单神经网络 0.6948、大型神经网络 0.6965。大型神经网络数字最高,但性能图里有两个远低于均值的离群点,生产环境不敢用。 结论很直接——在现有数据上,理想模型应当比逻辑回归复杂、比大型神经网络简单。我们挑了小型神经网络(隐藏层 20×10 做训练对比,实盘用 5×2)落地信号,因为它最稳。 下面这段是把对比实验和 MT5 交易骨架拼起来的原型代码,注意 VOLUME 初始为 0,必须在 symbol 循环里被 volume_min 覆盖,否则发单必失败。外汇与贵金属波动剧烈,迷你模型信号仅作辅助,实盘前请在模拟盘验证稳定性。

MQL5 / C++
for i in enumerate(np.arange(class="num">0,error_close_df.shape[class="num">1])):
  print(error_close_df.columns[i[class="num">0]]," ", error_close_df.iloc[:,i[class="num">0]].max())
class="macro">#Training each model to predict changes in a technical indicator(in this example simple moving average) instead of close price.
for i,(train,test) in enumerate(tscv.split(csv)):
    model= MLPClassifier(solver=&class="macro">#x27;lbfgs&class="macro">#x27;,alpha=class="num">1e-5,hidden_layer_sizes=(class="num">20, class="num">10), random_state=class="num">1)
    model.fit(csv_reduced.loc[train[class="num">0]:train[-class="num">1],:],csv.loc[train[class="num">0]:train[-class="num">1],"Target MA"])
    error_ma_df.iloc[i,class="num">4] = accuracy_score(csv.loc[test[class="num">0]:test[-class="num">1],"Target MA"],model.predict(csv_reduced.loc[test[class="num">0]:test[-class="num">1],:]))
for i in enumerate(np.arrange(class="num">0,error_ma_df.shape[class="num">1])):
  print(error_ma_df.columns[i[class="num">0]]," ", error_ma_df.iloc[:,i[class="num">0]].max())
class="macro">#Import the libraries we need
class="kw">import MetaTrader5 as mt5
class="kw">import pandas_ta as ta
class="kw">import pandas as pd
class="macro">#Trading global variables
MARKET_SYMBOL = &class="macro">#x27;Volatility class="num">75 Index&class="macro">#x27;
class="macro">#This data frame will store the most recent price update
last_close = pd.DataFrame()
class="macro">#We may not always enter at the price we want, how much deviation can we tolerate?
DEVIATION = class="num">10000
class="macro">#We will always enter at the minimum volume
VOLUME = class="num">0
class="macro">#How many times the minimum volume should our positions be
LOT_MUTLIPLE = class="num">1
class="macro">#What timeframe are we working on?
TIMEFRAME = mt5.TIMEFRAME_M1
class="macro">#Which model have we decided to work with?
neural_network_model= MLPClassifier(solver=&class="macro">#x27;lbfgs&class="macro">#x27;,alpha=class="num">1e-5,hidden_layer_sizes=(class="num">5, class="num">2), random_state=class="num">1)
class="macro">#Determine the minimum volume
for index,symbol in enumerate(symbols):
    if symbol.name == MARKET_SYMBOL:
        print(f"{symbol.name} has minimum volume: {symbol.volume_min}")
        VOLUME = symbol.volume_min * LOT_MULTIPLE
# function to send a market order
def market_order(symbol, volume, order_type, **kwargs):
    class="macro">#Fetching the current bid and ask prices
    tick = mt5.symbol_info_tick(symbol)
    
    class="macro">#Creating a dictionary to keep track of order direction
    order_dict = {&class="macro">#x27;buy&class="macro">#x27;: class="num">0, &class="macro">#x27;sell&class="macro">#x27;: class="num">1}
    price_dict = {&class="macro">#x27;buy&class="macro">#x27;: tick.ask, &class="macro">#x27;sell&class="macro">#x27;: tick.bid}
    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": symbol,
        "volume": volume,
        "type": order_dict[order_type],
        "price": price_dict[order_type],
        "deviation": DEVIATION,
        "magic": class="num">100,
        "comment": "Indicator Forecast Market Order",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_FOK,
    }
    order_result = mt5.order_send(request)
    print(order_result)
    class="kw">return order_result
# Closing our order based on ticket id
def close_order(ticket):
    positions = mt5.positions_get()
    for pos in positions:

用神经网络信号反手平掉持仓

平仓单的核心是把原持仓方向反过来吃对手价。代码里 type_dict 把 0 映射成 1、1 映射成 0,配合 price_dict 取 tick.bid 或 tick.ask,等于买仓用 ask 价卖、卖仓用 bid 价买,滑点控制在 DEVIATION 常量内。 订单结构体里 type_filling 用了 ORDER_FILLING_FOK,意味着整笔必须在当时报价全成,否则不成交;外汇与贵金属杠杆高,FOK 在流动性薄的时段可能直接废单,实盘前建议在 MT5 策略测试器跑一遍观察 reject 率。 信号端用 60 周期 SMA 做特征,target 定义为下一根 close 高于当前 close 则标 1,模型 predict 出 1 解读为 buy、0 为 sell。回测区间写死从 2024-01-01 到 datetime.now(),换品种时这段日期和 TIMEFRAME 都要手动改,否则拿到的样本量会偏短。 主循环是 while True 死循环轮询 ai_signal(),每轮 print 出方向。真要挂机得加 try/except 和 mt5 连接心跳,不然 MT5 掉线后脚本会空转打印报错。

MQL5 / C++
tick = mt5.symbol_info_tick(pos.symbol) class="macro">#validating that the order is for this symbol
type_dict = {class="num">0: class="num">1, class="num">1: class="num">0}  # class="num">0 represents buy, class="num">1 represents sell - inverting order_type to close the position
price_dict = {class="num">0: tick.ask, class="num">1: tick.bid} class="macro">#bid ask prices
if pos.ticket == ticket:
    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "position": pos.ticket,
        "symbol": pos.symbol,
        "volume": pos.volume,
        "type": type_dict[pos.type],
        "price": price_dict[pos.type],
        "deviation": DEVIATION,
        "magic": class="num">10000,
        "comment": "Indicator Forecast Market Order",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_FOK,
    }
    order_result = mt5.order_send(request)
    print(order_result)
    class="kw">return order_result
class="kw">return &class="macro">#x27;Ticket does not exist&class="macro">#x27;
class="macro">#Update our date from and date to
date_from = class="type">class="kw">datetime(class="num">2024,class="num">1,class="num">1)
date_to = class="type">class="kw">datetime.now()
class="macro">#Let&class="macro">#x27;s create a function to preprocess our data
def preprocess(df):
    class="macro">#Calculating class="num">60 period Simple Moving Average
    df.ta.sma(length=class="num">60,append=True)
    class="macro">#Drop any rows that have missing values
    df.dropna(axis=class="num">0,inplace=True)
class="macro">#Get signals from our model
def ai_signal():
    class="macro">#Fetch OHLC data
    df = pd.DataFrame(mt5.copy_rates_range(market_symbol,TIMEFRAME,date_from,date_to))
    class="macro">#Process the data
    df[&class="macro">#x27;time&class="macro">#x27;] = pd.to_datetime(df[&class="macro">#x27;time&class="macro">#x27;],unit=&class="macro">#x27;s&class="macro">#x27;)
    df[&class="macro">#x27;target&class="macro">#x27;] = (df[&class="macro">#x27;close&class="macro">#x27;].shift(-class="num">1) > df[&class="macro">#x27;close&class="macro">#x27;]).astype(class="type">int)
    preprocess(df)
    class="macro">#Select the last row
    last_close = df.iloc[-class="num">1:,class="num">1:]
    class="macro">#Remove the target column
    last_close.pop(&class="macro">#x27;target&class="macro">#x27;)
    class="macro">#Use the last row to generate a forecast from our moving average forecast model
    class="macro">#Remember class="num">1 means buy and class="num">0 means sell
    forecast = neural_network_model.predict(last_close)
    class="kw">return forecast[class="num">0]
class="macro">#if __name__ == &class="macro">#x27;__main__&class="macro">#x27;:
    class="macro">#We&class="macro">#x27;ll use an infinite loop to keep the program running
    class="kw">while True:
        class="macro">#Fetching model prediction
        signal = ai_signal()
        
        class="macro">#Decoding model prediction into an action
        if signal == class="num">1:
            direction = &class="macro">#x27;buy&class="macro">#x27;
        elif signal == class="num">0:
            direction = &class="macro">#x27;sell&class="macro">#x27;
        
        print(f&class="macro">#x27;AI Forecast: {direction}&class="macro">#x27;)
        
        class="macro">#Opening A Buy Trade

◍ 同品种反手前的持仓清理逻辑

在 MT5 上跑单边信号时,最容易被忽略的是同符号反向单的残留。上面这段控制流先判断方向,若信号为 buy,就遍历 positions_get() 返回的持仓,把 type==1(即卖出持仓)逐个用 close_order(ticket) 平掉,避免新旧方向相反互相打架。 清理完反向仓后,代码用 mt5.positions_totoal()(原文拼写如此,实应 positions_total)判断当前是否零持仓,确认空仓才调用 market_order 以 VOLUME 手数进场。卖信号分支完全对称:扫 type==0 的买仓平仓,再检查 positions_get() 为空后开卖。 循环末尾 sleep(60) 代表每 60 秒轮询一次信号与持仓,print 出 datetime.now() 方便你直接在终端看每次触发的时间戳。外汇与贵金属杠杆高,这种强制平反手单的机制能降低多空叠加的敞口风险,但是否执行仍取决于你的风控参数。 开 MT5 把这段贴进 Python 环境跑之前,先核对 positions_totoal 这个拼写错误——原样复制会直接抛异常,改回 positions_total 才能验证清理逻辑是否如预期。

MQL5 / C++
if direction == &class="macro">#x27;buy&class="macro">#x27;:
    class="macro">#Close any sell positions
    for pos in mt5.positions_get():
        if pos.type == class="num">1:
            class="macro">#This is an open sell order, and we need to close it
            close_order(pos.ticket)

        if not mt5.positions_totoal():
            class="macro">#We have no open positions
            market_order(MARKET_SYMBOL,VOLUME,direction)

    class="macro">#Opening A Sell Trade
    elif direction == &class="macro">#x27;sell&class="macro">#x27;:
        class="macro">#Close any buy positions
        for pos in mt5.positions_get():
            if pos.type == class="num">0:
                class="macro">#This is an open buy order, and we need to close it
                close_order(pos.ticket)

        if not mt5.positions_get():
            class="macro">#We have no open positions
            market_order(MARKET_SYMBOL,VOLUME,direction)

    print(&class="macro">#x27;time: &class="macro">#x27;, class="type">class="kw">datetime.now())
    print(&class="macro">#x27;-------\n&class="macro">#x27;)
    time.sleep(class="num">60)

「把技术指标当目标才绕开伪回归坑」

做预测时,技术指标的变化往往比收盘价更好猜,原因很硬:MFM 这类指标的完整公式我们全知道,输入只涉及收盘价、最高价、最低价,预测它等于解一道已知方程的题。图12 里说的很清楚——技术指标有精确数学描述,收盘价却没有。 反观收盘价,没有任何公式列明哪些变量驱动它。用一串价格加技术指标去反推价格,本质是假设指标能影响价格,这逻辑从根上就站不住,指标永远只是价格的派生量。 机器学习里这种「目标底层函数未知」的活法,容易掉进伪回归。作者举的例子很直白:山顶隔着雾喊「那边有狗」,走近发现是灌木后真有狗。输入(模糊形状)和输出(狗)其实独立,只是凑巧答对,模型什么真实关系都没学到,验证集还可能给个骗人的低错误率。 外汇和贵金属这种高波动品种,伪回归一旦带进 EA,实盘亏穿比回测漂亮更常见。既然没有现成公式框定价格输入,与其硬磕收盘价,不如拿有已知输入关系的指标当学习目标,至少能少造一批「看起来会、实则瞎蒙」的模型。

指标可预测,但滞后始终是硬伤

抛开直接预测收盘价那条路,转向预测技术指标变化在实测中确实更站得住脚。不过别迷信高准确率——移动平均线预测得再准,它本身和价格就存在滞后,可能出现均线向下而价格向上的背离,这在外汇和贵金属这种高杠杆市场里容易误判进出场。 下一步值得在 MT5 里继续挖:除了 MA,震荡类指标是否具备更高预测精度,社区里已经有人用标准 MQL5 功能跑通了 Tick 成交量预测作为例证。 这类方法终究只是概率优势,不是确定性信号;真要落地,建议先把自己写的指标缓冲区预测逻辑在策略测试器里跑一遍,看残差分布再决定参数。

常见问题

均线是对价格的平滑,变化节奏比单根收盘价稳定,提前算下一根均线位置可更早察觉趋势拐点,避免被单根毛刺骗线。
同品种反手前必须清理原方向持仓,否则新旧订单对冲占用保证金且信号逻辑冲突,应在收到反向信号时先市价平仓再开新仓。
小布可在识别到神经网络反向信号时自动提示当前持仓并一键平仓,再把反手逻辑推送到你的看板,省去手动核对。
以指标值而非价格为回归目标,可过滤价格随机噪声导致的假突破,只在指标真正穿越阈值时才认定结构变化。
用短周期指标做预判、长周期确认,并只把它当概率参考;外汇贵金属波动剧烈,任何信号都需严控仓位。