//+------------------------------------------------------------------+ //| StrategyBase.mqh | //| Copyright 2025, Leo. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Leo." #property link "https://www.mql5.com" #property strict #ifndef MQLARTICLES_STRATEGY_CORE_BASE_MQH #define MQLARTICLES_STRATEGY_CORE_BASE_MQH //+------------------------------------------------------------------+ //| Notas | //+------------------------------------------------------------------+ /* ADVERTENCIA: Dado que StrategyBase hereda de CAccountGestor (la clase base que permite crear clases que necesiten conocer el estado de las posiciones, por ejemplo saber cuándo una posición se abrió o se cerró), Es necesario que, si se crea una instancia o un puntero de CStrategyBase, Este se agregue al array de ítems que luego se recorre en un bucle para ejecutar las funciones virtuales de CAccountGestor en todos los elementos registrados. Para agregar una clase, simplemente se debe llamar a: account_status.AddItem() Con esto se vincula la instancia o puntero al array de ítems de account_status, y como consecuencia, en esa clase quedarán activas las funciones virtuales heredadas de CAccountGestor. Un punto importante a tener en cuenta es que, cuando finalice la ejecución del programa (bot/EA), se llamará al destructor de account_status, y dentro de este destructor se eliminarán los ítems registrados en el array. Esto significa que cualquier clase agregada será eliminada mediante delete, pero únicamente si es un puntero dinámico creado con new. Por lo tanto, es fundamental tener precaución: si se vincula un puntero que no fue creado dinámicamente, o si más adelante se intenta eliminar por separado, se produciría un doble delete, lo que generaría un error fatal. */ //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include "Defines.mqh" #include "..\\Utils\\YearFilter.mqh" #include "..\\Utils\\DailyFilter.mqh" //+------------------------------------------------------------------+ //| Clase base para las estrategias de trading | //+------------------------------------------------------------------+ class CStrategyBase : public CAccountGestor { protected: //--- Variables // Generales ENUM_TIMEFRAMES m_timeframe; //Timeframe de la estrategia string m_symbol; int m_subwindow; long m_chart_id; int m_period_in_seconds; string m_strategy_name; string m_obj_code; // Parametros generales bool m_is_enable_buy; bool m_is_enable_sell; ulong m_max_deviation; ulong m_magic_number; // Info del simbolo MqlTick m_tick; double m_point_value; int8_t m_digits; double m_vol_min; double m_vol_max; double m_vol_step; double m_stops_leevel; // Apertura de operaciones double m_TP_POINTS; double m_SL_POINTS; double m_LOT_SIZE; CTrade* m_trade; ENUM_TYPE_TP_SL_FIXED m_tp_sl_mode; // Atr optimizado para el calculo del tp, sl e uso interno CAtrUltraOptimized* m_atr_pointer; double m_atr_multiplier_tp; double m_atr_multiplier_sl; //--- Funciones // TP\SL basico double GetSL(double entry_price, ENUM_POSITION_TYPE position_type, datetime curr_time); double GetTP(double entry_price, ENUM_POSITION_TYPE position_type, datetime curr_time); // Utilidades extra inline int DistanceToPoints(double dist) { return (m_point_value == 0.00) ? 0 : (int)::MathRound(dist / m_point_value); } inline double GetDiff(ENUM_MODE_DIFF mode, double val, datetime curr_time) const { return mode == MODE_DIFF_BY_ATR ? (m_atr_pointer.base.GetAtrValue(curr_time) * val) : (val * m_point_value); } public: CStrategyBase(ulong magic_number_, string symbol_, ENUM_TIMEFRAMES timeframe_ = PERIOD_CURRENT, long chart_id_ = 0, int subwindow_ = 0, ulong max_deviation_ = NO_MAX_DEVIATION_DEFINED, string name = "Strategy"); ~CStrategyBase(); //--- Funciones para establecer valores de la clase void SetTP_SL(long new_sl_value, long new_tp_value); void SetAtrTP_SL(CAtrUltraOptimized* atr_ptr, double atr_mult_tp_, double atr_mult_sl_); void SetOperateMode(ENUM_TYPE_TRADE trade_type, ENUM_TYPE_TP_SL_FIXED tp_sl_mode_); //--- Funciones getter y setter // General getters inline ENUM_TIMEFRAMES Timeframe() const { return m_timeframe; } inline long ChartId() const { return m_chart_id; } inline int Subwin() const { return m_subwindow; } inline string Symbol() const { return m_symbol; } inline int TimeframeInSeconds() const { return m_period_in_seconds; } inline ulong MagicNumber() const { return m_magic_number; } inline string Name() const { return m_strategy_name; } // Codigo de objeto inline string ObjCode() const { return m_obj_code; } void ObjCode(const string& new_value) { m_obj_code = new_value; } __forceinline string ObjCodeConc(string str) const { return (m_obj_code + str); } // Puntero de CTrade utilizado const CTrade* const TradePointer() const { return m_trade; } // Evitamos la modificacion de dirreicon y del obejto y de las variabes miembro de la clase // Tamaño de lote inline double FixedLotSize() const { return m_LOT_SIZE; } void FixedLotSize(double new_lot); // Ventas y commpras habilitadas bool EnableBuyTrades() const { return m_is_enable_buy; } bool EnableSellTrades() const { return m_is_enable_sell; } //--- Funciones de ejecucion virtual void OnNewBar(const datetime &curr_time) = 0; // Funcion que se ejecutara cada nueva vela virtual void OnTick() = 0; // Funcion que se ejecutara cada nuevo tick // Funcion que se llama anted de detener el llamado a OnTick y OnNewBar // La estrategia tomara sus acciones como que en la siguiente llamada a OnNewBar o OnTick hacer un recalculo completo // Esto depende de la estrategia habra unas que no lo requieren otras que si etc virtual void OnInterupcion() {} //--- Funciones para el maneje de posiciones, solo si se requiere void OnOpenClosePosition(const ROnOpenClosePosition &pos) override { } // Funcion heredada de CAccountGestor }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CStrategyBase::CStrategyBase(ulong magic_number_, string symbol_, ENUM_TIMEFRAMES timeframe_ = PERIOD_CURRENT, long chart_id_ = 0, int subwindow_ = 0, ulong max_deviation_ = NO_MAX_DEVIATION_DEFINED, string name = "Strategy") : m_TP_POINTS(0), m_SL_POINTS(0), m_LOT_SIZE(0.00), m_atr_multiplier_sl(2.0), m_atr_multiplier_tp(4.0), m_is_enable_buy(true), m_is_enable_sell(true), m_atr_pointer(NULL) { //--- //AddLogFlags(LOG_LEVEL_WARNING); m_trade = new CTrade(); //--- m_magic_number = magic_number_; m_symbol = symbol_; m_chart_id = chart_id_; m_subwindow = subwindow_; m_timeframe = timeframe_; //--- m_trade.LogLevel(LOG_LEVEL_NO); m_trade.SetExpertMagicNumber(m_magic_number); m_period_in_seconds = ::PeriodSeconds(m_timeframe); //--- m_digits = (int8_t)::SymbolInfoInteger(symbol_, SYMBOL_DIGITS); m_vol_max = ::SymbolInfoDouble(symbol_, SYMBOL_VOLUME_MAX); m_vol_step = ::SymbolInfoDouble(symbol_, SYMBOL_VOLUME_STEP); m_vol_min = ::SymbolInfoDouble(symbol_, SYMBOL_VOLUME_MIN); m_point_value = ::SymbolInfoDouble(m_symbol, SYMBOL_POINT); m_stops_leevel = (::SymbolInfoInteger(symbol_, SYMBOL_TRADE_STOPS_LEVEL) * m_point_value) + (m_point_value * 10); //--- if(max_deviation_ > NO_MAX_DEVIATION_DEFINED) m_trade.SetDeviationInPoints(max_deviation_); m_max_deviation = max_deviation_; //--- ::MathSrand(::GetTickCount()); //--- m_strategy_name = name; m_obj_code = ::StringFormat("%s_%s_%d_", name, AbreviarPeriodo(m_timeframe), ::rand()); } //+------------------------------------------------------------------+ CStrategyBase::~CStrategyBase() /* ADVERTENCIA SI SE AÑADEN PUNTEROS EN ITEMS ENTONCES ESTOS SE LIMPIARAN NO HACE FALTA HACERLO EN EL DESTRUCTOE DEL HIJO*/ { //Se Elimina el atr ultra optimizado CleanItems("StrategyBase"); //Limpiamos todos los punteros de CLogger, en este caso solo se limpiaria el "base", el atr_pointer lo hacemos manual: if(::CheckPointer(m_atr_pointer) == POINTER_DYNAMIC) delete m_atr_pointer; if(::CheckPointer(m_trade) == POINTER_DYNAMIC) delete m_trade; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ // Nota: Tick debede de ser cargado //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double CStrategyBase::GetTP(double entry_price, ENUM_POSITION_TYPE position_type, datetime curr_time) { double val = m_point_value; double val_to = m_TP_POINTS; if(m_tp_sl_mode == TP_SL_ATR) { val_to = m_atr_multiplier_tp; val = m_atr_pointer.base.GetAtrValue(curr_time); } const double spr = (m_tick.ask - m_tick.bid); const double to_sum = ::fmax(m_stops_leevel + spr, (val * val_to)); return position_type == POSITION_TYPE_BUY ? entry_price + to_sum : entry_price - to_sum; } //+------------------------------------------------------------------+ double CStrategyBase::GetSL(double entry_price, ENUM_POSITION_TYPE position_type, datetime curr_time) { double val = m_point_value; double val_to = m_SL_POINTS; if(m_tp_sl_mode == TP_SL_ATR) { val_to = m_atr_multiplier_sl; val = m_atr_pointer.base.GetAtrValue(curr_time); } const double spr = (m_tick.ask - m_tick.bid); const double to_sum = ::fmax(m_stops_leevel + spr, (val * val_to)); return position_type == POSITION_TYPE_BUY ? entry_price - to_sum : entry_price + to_sum; } //+------------------------------------------------------------------+ //| Setters | //+------------------------------------------------------------------+ void CStrategyBase::FixedLotSize(double new_lot) { if(new_lot <= STRATEGY_BASE_EMPTY_VALUE) return; if(new_lot < m_vol_min) { LogWarning(::StringFormat("El nuevo lote %f es menor al minimo volumen permitido, se ajustara al valor permitido mas cercano", new_lot), FUNCION_ACTUAL); new_lot = m_vol_min; } if(new_lot > m_vol_max) { LogWarning(::StringFormat("El nuevo lote %f es mayor al maximo volumen permitido, se ajustara al valor permitido mas cercano", new_lot), FUNCION_ACTUAL); new_lot = m_vol_max; } m_LOT_SIZE = new_lot; } //+------------------------------------------------------------------+ void CStrategyBase::SetAtrTP_SL(CAtrUltraOptimized* atr_ptr, double atr_mult_tp_, double atr_mult_sl_) { if(atr_ptr != NULL) { if(m_atr_pointer != NULL) //Si no es invalido eliminamos el anteior solo si es dinamico { RemoveLogger(m_atr_pointer.base); if(::CheckPointer(m_atr_pointer) == POINTER_DYNAMIC) { delete m_atr_pointer; m_atr_pointer = NULL; LogWarning("Se ha eliminado el puntero anteior a CAtrUltraOptimized*", FUNCION_ACTUAL); } } m_atr_pointer = atr_ptr; AddLogger(m_atr_pointer.base); } else { if(::CheckPointer(m_atr_pointer) == POINTER_INVALID || ::CheckPointer(m_atr_pointer.base) == POINTER_INVALID) { LogError("El puntero a CAtrUltraOptimized es invalido, corregir ahora", FUNCION_ACTUAL); Remover(); } } if(atr_mult_sl_ > STRATEGY_BASE_EMPTY_VALUE) m_atr_multiplier_sl = atr_mult_sl_; if(atr_mult_tp_ > STRATEGY_BASE_EMPTY_VALUE) m_atr_multiplier_tp = atr_mult_tp_; } //+------------------------------------------------------------------+ void CStrategyBase::SetTP_SL(long new_sl_value, long new_tp_value) { if(new_sl_value > STRATEGY_BASE_EMPTY_VALUE) { if(new_sl_value < 1) { LogWarning(::StringFormat("Los puntos del stoploss %d son menores a 1 los proximos trades no tendran stoploss", new_sl_value), FUNCION_ACTUAL); new_sl_value = 0; } m_SL_POINTS = (double)new_sl_value; } if(new_tp_value > STRATEGY_BASE_EMPTY_VALUE) { if(new_tp_value < 1) { LogWarning(::StringFormat("Los puntos del takeprofit %d son menores a 1, los proximos trades no tendran takeprofit", new_tp_value), FUNCION_ACTUAL); new_tp_value = 0; } m_TP_POINTS = (double)new_tp_value; } } //+------------------------------------------------------------------+ void CStrategyBase::SetOperateMode(ENUM_TYPE_TRADE trade_type, ENUM_TYPE_TP_SL_FIXED tp_sl_mode_) { if(trade_type != WRONG_VALUE) { if(trade_type == TR_BUY_SELL) { m_is_enable_sell = true; m_is_enable_buy = true; } else if(trade_type == TR_BUY) { m_is_enable_sell = false; m_is_enable_buy = true; } else { m_is_enable_sell = true; m_is_enable_buy = false; } } if(tp_sl_mode_ != WRONG_VALUE) m_tp_sl_mode = tp_sl_mode_; } #endif // MQLARTICLES_STRATEGY_CORE_BASE_MQH //+------------------------------------------------------------------+` /* //+------------------------------------------------------------------+ //| Open Order Default | //+------------------------------------------------------------------+ void CEstrategia::OpenOrder(ENUM_POSITION_TYPE type) { ::SymbolInfoTick(m_symbol, m_tick); if(type == POSITION_TYPE_BUY) { double entry = m_tick.ask; double sl = GetSL(entry, type, m_tick.time); double tp = GetTP(entry, type, m_tick.time); risk.SetStopLoss(entry - sl); double l = m_LOT_SIZE > 0.00 ? m_LOT_SIZE : risk.GetLote(ORDER_TYPE_BUY, entry, m_max_deviation, 0); m_trade.Buy(l, m_symbol, entry, sl, tp, "Ea buy"); } else if(type == POSITION_TYPE_SELL) { double entry = m_tick.bid; double sl = GetSL(entry, type, m_tick.time); double tp = GetTP(entry, type, m_tick.time); risk.SetStopLoss(sl - entry); double l = m_LOT_SIZE > 0.00 ? m_LOT_SIZE : risk.GetLote(ORDER_TYPE_SELL, entry, m_max_deviation, 0); m_trade.Sell(l, m_symbol, entry, sl, tp, "Ea sell"); } Clean(); } */ //+------------------------------------------------------------------+