MQL5 物件導向程式設計(OOP)入門:Class、繼承與封裝

📌 本文重點
學習 MQL5 的物件導向程式設計:定義 Class、建構函式、繼承、封裝與多型。讓你的 EA 更模組化、易維護。

為什麼需要 OOP?

當 EA 邏輯複雜化,單純函數式寫法難以維護。MQL5 完整支援 C++ 風格的 OOP,讓你將數據與行為封裝在一起,大幅提升可重複使用性。

定義與使用 Class

class CMovingAverage
{
private:
    int    m_handle;
    int    m_period;
    string m_symbol;

public:
    CMovingAverage(int period, string symbol = NULL)
    {
        m_period = period;
        m_symbol = (symbol == NULL) ? _Symbol : symbol;
        m_handle = iMA(m_symbol, _Period, m_period, 0, MODE_SMA, PRICE_CLOSE);
    }

    ~CMovingAverage()
    {
        if (m_handle != INVALID_HANDLE)
            IndicatorRelease(m_handle);
    }

    double GetValue(int index = 1)
    {
        double buf[1];
        ArraySetAsSeries(buf, true);
        if (CopyBuffer(m_handle, 0, index, 1, buf) < 1) return EMPTY_VALUE;
        return buf[0];
    }

    bool IsValid()   { return m_handle != INVALID_HANDLE; }
    int  GetPeriod() { return m_period; }
};

// 在 EA 中使用
CMovingAverage *g_fastMA = NULL;
CMovingAverage *g_slowMA = NULL;

int OnInit()
{
    g_fastMA = new CMovingAverage(10);
    g_slowMA = new CMovingAverage(30);
    if (!g_fastMA.IsValid() || !g_slowMA.IsValid()) return INIT_FAILED;
    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
    delete g_fastMA;
    delete g_slowMA;
}

void OnTick()
{
    double fast = g_fastMA.GetValue(1), slow = g_slowMA.GetValue(1);
    double fastPrev = g_fastMA.GetValue(2), slowPrev = g_slowMA.GetValue(2);
    if (fastPrev <= slowPrev && fast > slow) Print("金叉!");
    if (fastPrev >= slowPrev && fast < slow) Print("死叉!");
}

繼承(Inheritance)

// 基礎策略類別
class CBaseStrategy
{
protected:
    string m_symbol;
    int    m_magic;
    double m_lots;

public:
    CBaseStrategy(string symbol, int magic, double lots)
        : m_symbol(symbol), m_magic(magic), m_lots(lots) {}

    virtual int GetSignal() { return 0; }  // 子類別覆寫

    bool OpenBuy(double sl=0, double tp=0)
    {
        MqlTradeRequest req={}; MqlTradeResult res={};
        req.action=TRADE_ACTION_DEAL; req.symbol=m_symbol;
        req.volume=m_lots; req.type=ORDER_TYPE_BUY;
        req.price=SymbolInfoDouble(m_symbol,SYMBOL_ASK);
        req.sl=sl; req.tp=tp; req.magic=m_magic;
        return OrderSend(req,res);
    }
};

// 繼承並實作具體策略
class CMACrossStrategy : public CBaseStrategy
{
private:
    CMovingAverage *m_fast, *m_slow;

public:
    CMACrossStrategy(string sym, int magic, double lots, int f, int s)
        : CBaseStrategy(sym, magic, lots)
    {
        m_fast = new CMovingAverage(f, sym);
        m_slow = new CMovingAverage(s, sym);
    }
    ~CMACrossStrategy() { delete m_fast; delete m_slow; }

    virtual int GetSignal() override
    {
        double f1=m_fast.GetValue(1), f2=m_fast.GetValue(2);
        double s1=m_slow.GetValue(1), s2=m_slow.GetValue(2);
        if (f2<=s2 && f1>s1) return  1;
        if (f2>=s2 && f1<s1) return -1;
        return 0;
    }
};

封裝原則

private:只有類別內部可存取。protected:類別與子類別可存取。public:對外介面。

class CRiskManager
{
private:
    double m_maxRiskPct;
public:
    CRiskManager(double maxRiskPct = 1.0) : m_maxRiskPct(maxRiskPct) {}

    double CalculateLots(int slPips)
    {
        double riskAmt  = AccountInfoDouble(ACCOUNT_BALANCE) * m_maxRiskPct / 100.0;
        double tickVal  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
        double tickSz   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
        double pointVal = tickVal * (tickSz / SymbolInfoDouble(_Symbol, SYMBOL_POINT));
        if (pointVal <= 0 || slPips <= 0) return 0.01;
        return NormalizeDouble(riskAmt / (slPips * pointVal), 2);
    }

    void SetMaxRisk(double pct) { if (pct>0 && pct<=10) m_maxRiskPct=pct; }
    double GetMaxRisk()         { return m_maxRiskPct; }
};

重要提醒

  • new 建立物件後,務必在 OnDeinit()delete,避免記憶體洩漏
  • 善用 virtual 讓子類別覆寫行為
  • 每個類別只負責一個職責(單一責任原則)

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

Similar Posts

發佈留言

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