Что такое CopyFX
CopyFX — это инновационный инвестиционный сервис от RoboForex, разработанный для копирования сделок успешных трейдеров. Платформа…
Вторая статья из цикла «Автоматизация Forex-трейдинга в MetaTrader4/5 - Category foreks avtomatizacziya ttrejdinga» посвящена проектированию архитектуры торговых советников(начало здесь - ). Рассматриваются принципы модульного проектирования, паттерны проектирования для алгоритмического трейдинга, система управления состоянием и обработки ошибок, а также лучшие практики разработки поддерживаемого кода.
Оглавление
Традиционный подход (монолитный):
// ПРИМЕР: Наивная реализация (антипаттерн)
void OnTick()
{
double maFast = iMA(_Symbol, _Period, 14, 0, MODE_SMA, PRICE_CLOSE, 0);
double maSlow = iMA(_Symbol, _Period, 28, 0, MODE_SMA, PRICE_CLOSE, 0);
if(maFast > maSlow && !HasPosition())
{
OrderSend(_Symbol, OP_BUY, 0.1, Ask, 3, 0, 0, "MA Cross", 0, 0, clrGreen);
}
}
Проблемы монолитного подхода:
// S - Single Responsibility Principle
class CTradeManager
{
public:
bool OpenPosition(ENUM_ORDER_TYPE type, double volume);
bool ClosePosition(ulong ticket);
double CalculatePositionSize();
};
class CRiskManager
{
public:
bool ValidateRisk(double volume);
double CalculateMaxVolume();
double GetAccountRiskPercent();
};
class CSignalGenerator
{
public:
bool GetBuySignal();
bool GetSellSignal();
void UpdateIndicators();
};
┌─────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Input Parameters, GUI) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Business Logic │
│ (Trading Strategy, Signal Generation) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Service Layer │
│ (Trade Execution, Risk Management) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Data Access Layer │
│ (Market Data, Indicators, History) │
└─────────────────────────────────────────────────┘
// Основной класс советника
class CExpertAdvisor : public CObject
{
private:
CMarketData *m_marketData;
CSignalGenerator *m_signalGenerator;
CRiskManager *m_riskManager;
CTradeExecutor *m_tradeExecutor;
CJournal *m_journal;
public:
bool OnInit();
void OnTick();
void OnDeinit(const int reason);
// Конструктор, деструктор
CExpertAdvisor();
~CExpertAdvisor();
};
// Инициализация компонентов
bool CExpertAdvisor::OnInit()
{
m_journal = new CJournal("EA_Log");
m_marketData = new CMarketData(m_journal);
m_riskManager = new CRiskManager(2.0, m_journal); // 2% risk
m_signalGenerator = new CSignalGenerator(m_marketData, m_journal);
m_tradeExecutor = new CTradeExecutor(m_riskManager, m_journal);
return true;
}
class CMarketData : public CObject
{
private:
CJournal *m_log;
string m_symbol;
ENUM_TIMEFRAME m_timeframe;
public:
double GetPrice(ENUM_PRICE_TYPE priceType);
double GetIndicatorValue(string indicatorName, int shift);
bool IsNewBar();
bool IsMarketOpen();
int GetSpread();
CMarketData(CJournal *logger);
};
// Реализация методов
double CMarketData::GetPrice(ENUM_PRICE_TYPE priceType)
{
MqlTick tick;
if(SymbolInfoTick(m_symbol, tick))
{
switch(priceType)
{
case PRICE_ASK: return tick.ask;
case PRICE_BID: return tick.bid;
case PRICE_LAST: return tick.last;
}
}
m_log.Error("Failed to get price for " + m_symbol);
return 0;
}
class CSignalGenerator : public CObject
{
private:
CMarketData *m_marketData;
CJournal *m_log;
public:
enum ENUM_SIGNAL
{
SIGNAL_NONE = 0,
SIGNAL_BUY = 1,
SIGNAL_SELL = 2,
SIGNAL_CLOSE = 3
};
ENUM_SIGNAL GetSignal();
double GetSignalStrength();
CSignalGenerator(CMarketData *marketData, CJournal *logger);
};
// Реализация стратегии MA Cross
ENUM_SIGNAL CSignalGenerator::GetSignal()
{
double maFast = m_marketData->GetIndicatorValue("MA", 0);
double maSlow = m_marketData->GetIndicatorValue("MA", 1);
double maPrevFast = m_marketData->GetIndicatorValue("MA", 1);
double maPrevSlow = m_marketData->GetIndicatorValue("MA", 2);
// Определение пересечения
bool crossUp = (maFast > maSlow) && (maPrevFast <= maPrevSlow);
bool crossDown = (maFast < maSlow) && (maPrevFast >= maPrevSlow);
if(crossUp) return SIGNAL_BUY;
if(crossDown) return SIGNAL_SELL;
return SIGNAL_NONE;
}
class CRiskManager : public CObject
{
private:
double m_riskPercent;
CJournal *m_log;
public:
struct RiskParams
{
double volume;
double stopLoss;
double takeProfit;
double riskRewardRatio;
};
RiskParams CalculatePositionParams(ENUM_ORDER_TYPE type);
bool ValidatePositionSize(double volume);
double CalculateOptimalVolume(double stopLossPips);
double GetAccountFreeMarginPercent();
CRiskManager(double riskPercent, CJournal *logger);
};
// Расчет объема позиции
double CRiskManager::CalculateOptimalVolume(double stopLossPips)
{
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmount = accountBalance * m_riskPercent / 100.0;
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE_LOT);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(stopLossPips == 0 || tickValue == 0 || tickSize == 0)
{
m_log.Error("Invalid parameters for volume calculation");
return 0.1; // default volume
}
double volume = riskAmount / (stopLossPips * tickValue * (tickSize / _Point));
return NormalizeDouble(volume, 2);
}
class CTradeExecutor : public CObject
{
private:
CRiskManager *m_riskManager;
CJournal *m_log;
CTrade m_trade;
public:
bool OpenPosition(ENUM_ORDER_TYPE type, double volume, string comment = "");
bool ClosePosition(ulong ticket);
bool ModifyPosition(ulong ticket, double sl, double tp);
int GetOpenPositionsCount();
CTradeExecutor(CRiskManager *riskManager, CJournal *logger);
};
// Открытие позиции с обработкой ошибок
bool CTradeExecutor::OpenPosition(ENUM_ORDER_TYPE type, double volume, string comment)
{
double price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
CRiskManager::RiskParams params = m_riskManager->CalculatePositionParams(type);
if(!m_riskManager->ValidatePositionSize(volume))
{
m_log.Warning("Position size validation failed: " + DoubleToString(volume));
return false;
}
bool result = m_trade.Buy(volume, _Symbol, 0, params.stopLoss, params.takeProfit, comment);
if(!result)
{
m_log.Error("Failed to open position: " + IntegerToString(m_trade.ResultRetcode()));
return false;
}
m_log.Info("Position opened: " + comment + ", Volume: " + DoubleToString(volume));
return true;
}
- enum ENUM_EA_STATE
{
STATE_INIT = 0,
STATE_READY = 1,
STATE_WAITING_SIGNAL = 2,
STATE_POSITION_OPEN = 3,
STATE_POSITION_MANAGEMENT = 4,
STATE_ERROR = 5,
STATE_SHUTDOWN = 6
};
class CStateMachine : public CObject
{
private:
ENUM_EA_STATE m_currentState;
CJournal *m_log;
public:
bool TransitionTo(ENUM_EA_STATE newState);
ENUM_EA_STATE GetCurrentState() const { return m_currentState; }
string StateToString(ENUM_EA_STATE state);
CStateMachine(CJournal *logger);
};
// Управление переходами состояний
bool CStateMachine::TransitionTo(ENUM_EA_STATE newState)
{
// Валидация перехода
bool isValidTransition = false;
switch(m_currentState)
{
case STATE_INIT:
isValidTransition = (newState == STATE_READY || newState == STATE_ERROR);
break;
case STATE_READY:
isValidTransition = (newState == STATE_WAITING_SIGNAL || newState == STATE_ERROR);
break;
// ... другие правила переходов
}
if(isValidTransition)
{
m_log.Info("State transition: " + StateToString(m_currentState) +
" -> " + StateToString(newState));
m_currentState = newState;
return true;
}
m_log.Error("Invalid state transition: " + StateToString(m_currentState) +
" -> " + StateToString(newState));
return false;
}
enum ENUM_LOG_LEVEL
{
LOG_LEVEL_ERROR = 0,
LOG_LEVEL_WARNING = 1,
LOG_LEVEL_INFO = 2,
LOG_LEVEL_DEBUG = 3
};
class CJournal : public CObject
{
private:
string m_name;
ENUM_LOG_LEVEL m_logLevel;
string m_logFile;
public:
void Error(string message);
void Warning(string message);
void Info(string message);
void Debug(string message);
void Trade(string message, double price, double volume);
CJournal(string name, ENUM_LOG_LEVEL level = LOG_LEVEL_INFO);
};
// Реализация логирования
void CJournal::Error(string message)
{
if(m_logLevel >= LOG_LEVEL_ERROR)
{
string logMessage = TimeToString(TimeCurrent()) + " [ERROR] " + m_name + ": " + message;
Print(logMessage);
WriteToFile(logMessage);
SendNotification(logMessage); // Отправка уведомления
}
}
class ITradingStrategy : public CObject
{
public:
virtual bool GetSignal() = 0;
virtual string GetName() = 0;
virtual void Update() = 0;
};
class CMovingAverageStrategy : public ITradingStrategy
{
private:
CMarketData *m_marketData;
public:
bool GetSignal() override
{
// Реализация стратегии MA
return false;
}
string GetName() override { return "Moving Average Strategy"; }
void Update() override {}
};
class CRsiStrategy : public ITradingStrategy
{
public:
bool GetSignal() override
{
// Реализация стратегии RSI
return false;
}
string GetName() override { return "RSI Strategy"; }
void Update() override {}
};
class IMarketObserver
{
public:
virtual void OnNewTick() = 0;
virtual void OnNewBar() = 0;
virtual void OnPriceAlert(string symbol, double price) = 0;
};
class CMarketEventManager : public CObject
{
private:
IMarketObserver *m_observers[];
public:
void AddObserver(IMarketObserver *observer);
void RemoveObserver(IMarketObserver *observer);
void NotifyNewTick();
void NotifyNewBar();
};
// Главный файл советника
CExpertAdvisor *ea;
int OnInit()
{
ea = new CExpertAdvisor();
return ea.OnInit() ? INIT_SUCCEEDED : INIT_FAILED;
}
void OnTick()
{
ea.OnTick();
}
void OnDeinit(const int reason)
{
delete ea;
}
// Реализация основного цикла
void CExpertAdvisor::OnTick()
{
// Обновление данных
m_marketData->Update();
// Проверка состояния
if(m_stateMachine.GetCurrentState() == STATE_WAITING_SIGNAL)
{
// Получение сигнала
CSignalGenerator::ENUM_SIGNAL signal = m_signalGenerator->GetSignal();
if(signal == CSignalGenerator::SIGNAL_BUY)
{
CRiskManager::RiskParams params = m_riskManager->CalculatePositionParams(ORDER_TYPE_BUY);
m_tradeExecutor->OpenPosition(ORDER_TYPE_BUY, params.volume, "Buy Signal");
m_stateMachine.TransitionTo(STATE_POSITION_OPEN);
}
}
// Логирование состояния
m_journal->Debug("EA State: " + m_stateMachine.StateToString(m_stateMachine.GetCurrentState()));
}
Правильно спроектированная архитектура — основа успешного торгового советника. Модульный подход позволяет:
В следующей статье цикла мы рассмотрим:
«Продвинутые техники управления рисками и капиталом»:
Спроектируйте архитектуру для собственного торгового советника на основе изученных принципов. Создайте диаграмму классов и реализуйте базовые интерфейсы для основных компонентов системы.
Готовы к углублению в управление рисками? Продолжаем совершенствовать наши торговые системы! 💪