多品種 EA 開發:同時交易多個貨幣對

📌 本文重點
學習如何開發可同時交易多個貨幣對的 MQL5 EA:多品種指標初始化、Magic Number 管理、跨品種持倉查詢與資金分配。

多品種 EA 的設計思路

一個 EA 可以在單一圖表上同時監控並交易多個品種。關鍵是為每個品種建立獨立的指標句柄,並用不同的 Magic Number 區分持倉。

#include <Trade\Trade.mqh>
CTrade g_trade;

input string InpSymbols   = "EURUSD,GBPUSD,USDJPY,AUDUSD"; // 交易品種(逗號分隔)
input int    InpFastPeriod= 10;
input int    InpSlowPeriod= 30;
input double InpRiskPct   = 0.5;  // 每個品種風險(帳戶餘額%)
input int    InpBaseMagic = 30000; // 基礎 Magic Number(每個品種 +1)

// 每個品種的數據結構
struct SymbolData
{
    string symbol;
    int    fastHandle;
    int    slowHandle;
    int    atrHandle;
    int    magic;
    datetime lastBar;
};

SymbolData g_symbols[];
int        g_symbolCount = 0;

int OnInit()
{
    g_trade.SetDeviations(10);

    // 解析品種列表
    string symbolList[];
    int count = StringSplit(InpSymbols, ',', symbolList);

    ArrayResize(g_symbols, count);
    g_symbolCount = 0;

    for (int i = 0; i < count; i++)
    {
        string sym = symbolList[i];
        StringTrimLeft(sym); StringTrimRight(sym);

        if (!SymbolSelect(sym, true)) { Print("無法選擇品種: ", sym); continue; }

        g_symbols[g_symbolCount].symbol      = sym;
        g_symbols[g_symbolCount].fastHandle  = iMA(sym, _Period, InpFastPeriod, 0, MODE_SMA, PRICE_CLOSE);
        g_symbols[g_symbolCount].slowHandle  = iMA(sym, _Period, InpSlowPeriod, 0, MODE_SMA, PRICE_CLOSE);
        g_symbols[g_symbolCount].atrHandle   = iATR(sym, _Period, 14);
        g_symbols[g_symbolCount].magic       = InpBaseMagic + i;
        g_symbols[g_symbolCount].lastBar     = 0;

        if (g_symbols[g_symbolCount].fastHandle == INVALID_HANDLE ||
            g_symbols[g_symbolCount].slowHandle == INVALID_HANDLE)
        {
            Print("指標初始化失敗: ", sym);
            continue;
        }

        Print("已初始化品種: ", sym, " Magic=", g_symbols[g_symbolCount].magic);
        g_symbolCount++;
    }

    if (g_symbolCount == 0) return INIT_FAILED;
    EventSetTimer(1);
    return INIT_SUCCEEDED;
}

void OnDeinit(const int r)
{
    EventKillTimer();
    for (int i = 0; i < g_symbolCount; i++)
    {
        IndicatorRelease(g_symbols[i].fastHandle);
        IndicatorRelease(g_symbols[i].slowHandle);
        IndicatorRelease(g_symbols[i].atrHandle);
    }
}

void OnTimer()
{
    for (int i = 0; i < g_symbolCount; i++)
        ProcessSymbol(g_symbols[i]);
}

void ProcessSymbol(SymbolData &sd)
{
    // 只在新K線時處理
    datetime curBar = iTime(sd.symbol, _Period, 0);
    if (curBar == sd.lastBar) return;
    sd.lastBar = curBar;

    double fast[3], slow[3], atr[2];
    ArraySetAsSeries(fast, true); ArraySetAsSeries(slow, true); ArraySetAsSeries(atr, true);
    if (CopyBuffer(sd.fastHandle, 0, 0, 3, fast) < 3) return;
    if (CopyBuffer(sd.slowHandle, 0, 0, 3, slow) < 3) return;
    if (CopyBuffer(sd.atrHandle,  0, 0, 2, atr)  < 2) return;

    bool golden = fast[2]<=slow[2] && fast[1]>slow[1];
    bool death  = fast[2]>=slow[2] && fast[1]<slow[1];
    int  pos    = CountPositions(sd.symbol, sd.magic);

    if (golden && pos == 0)
    {
        double ask  = SymbolInfoDouble(sd.symbol, SYMBOL_ASK);
        double pt   = SymbolInfoDouble(sd.symbol, SYMBOL_POINT);
        double sl   = ask - atr[1] * 2;
        double tp   = ask + atr[1] * 4;
        int slPips  = (int)((ask-sl)/pt);
        g_trade.SetExpertMagicNumber(sd.magic);
        g_trade.Buy(CalcLots(sd.symbol, slPips), sd.symbol, ask, sl, tp, "Multi "+sd.symbol);
        Print("開倉: ", sd.symbol, " Magic=", sd.magic);
    }

    if (death && pos > 0)
    {
        g_trade.SetExpertMagicNumber(sd.magic);
        for (int i=PositionsTotal()-1; i>=0; i--)
            if (PositionGetSymbol(i)==sd.symbol && PositionGetInteger(POSITION_MAGIC)==sd.magic)
                g_trade.PositionClose(PositionGetInteger(POSITION_TICKET));
    }
}

int CountPositions(string sym, int magic)
{
    int n=0;
    for (int i=PositionsTotal()-1; i>=0; i--)
        if (PositionGetSymbol(i)==sym && PositionGetInteger(POSITION_MAGIC)==magic) n++;
    return n;
}

double CalcLots(string sym, int slPips)
{
    double r=AccountInfoDouble(ACCOUNT_BALANCE)*InpRiskPct/100.0;
    double tv=SymbolInfoDouble(sym,SYMBOL_TRADE_TICK_VALUE);
    double ts=SymbolInfoDouble(sym,SYMBOL_TRADE_TICK_SIZE);
    double pv=tv*(ts/SymbolInfoDouble(sym,SYMBOL_POINT));
    if(pv<=0||slPips<=0) return SymbolInfoDouble(sym,SYMBOL_VOLUME_MIN);
    double l=r/(slPips*pv);
    double mn=SymbolInfoDouble(sym,SYMBOL_VOLUME_MIN),mx=SymbolInfoDouble(sym,SYMBOL_VOLUME_MAX),st=SymbolInfoDouble(sym,SYMBOL_VOLUME_STEP);
    return NormalizeDouble(MathRound(MathMax(MathMin(l,mx),mn)/st)*st,2);
}

本文由 James Lee 撰寫。內容僅供教育目的,不構成投資建議。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *