380 lines
31 KiB
MQL5
380 lines
31 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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();
|
|
}
|
|
*/
|
|
//+------------------------------------------------------------------+
|