update
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
#include <Trade/Trade.mqh>
|
||||||
|
|
||||||
|
enum CUSTOM_MAX_TYPE {
|
||||||
|
CM_WIN_LOSS_RATIO,
|
||||||
|
CM_WIN_PERCENT
|
||||||
|
};
|
||||||
|
|
||||||
|
class CustomMax : public CObject {
|
||||||
|
protected:
|
||||||
|
double custom_criteria;
|
||||||
|
|
||||||
|
double win_loss_ratio(int min_required_trades);
|
||||||
|
double win_percent_min_trades(int min_required_trades);
|
||||||
|
|
||||||
|
public:
|
||||||
|
double calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades = 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades) {
|
||||||
|
switch(cm_type) {
|
||||||
|
case CM_WIN_LOSS_RATIO:
|
||||||
|
custom_criteria = win_loss_ratio(min_trades);
|
||||||
|
break;
|
||||||
|
case CM_WIN_PERCENT:
|
||||||
|
custom_criteria = win_percent_min_trades(min_trades);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
custom_criteria = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return custom_criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the win/loss ratio, with min trades check
|
||||||
|
double CustomMax::win_loss_ratio(int min_required_trades) {
|
||||||
|
double wins = TesterStatistics(STAT_PROFIT_TRADES);
|
||||||
|
double losses = TesterStatistics(STAT_LOSS_TRADES);
|
||||||
|
double total_trades = TesterStatistics(STAT_TRADES);
|
||||||
|
if((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) return 0;
|
||||||
|
if(losses == 0) return 0; // Prevent division by zero
|
||||||
|
return wins / losses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the win percentage, with min trades check
|
||||||
|
double CustomMax::win_percent_min_trades(int min_required_trades) {
|
||||||
|
double wins = TesterStatistics(STAT_PROFIT_TRADES);
|
||||||
|
double total_trades = TesterStatistics(STAT_TRADES);
|
||||||
|
if((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) return 0;
|
||||||
|
double result = wins / total_trades * 100;
|
||||||
|
if(!MathIsValidNumber(result)) return 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
enum MODE_SPLIT_DATA{
|
||||||
|
NO_SPLIT,
|
||||||
|
ODD_YEARS,
|
||||||
|
EVEN_YEARS,
|
||||||
|
ODD_MONTHS,
|
||||||
|
EVEN_MONTHS,
|
||||||
|
ODD_WEEKS,
|
||||||
|
EVEN_WEEKS
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestDataSplit {
|
||||||
|
public:
|
||||||
|
bool in_test_period(MODE_SPLIT_DATA data_split_method);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool TestDataSplit::in_test_period(MODE_SPLIT_DATA data_split_method) {
|
||||||
|
string result[];
|
||||||
|
string string_tc = TimeToString(TimeCurrent());
|
||||||
|
|
||||||
|
// Extract components from datetime string (assumes YYYY.MM.DD format)
|
||||||
|
ushort u_sep = StringGetCharacter(".", 0);
|
||||||
|
StringSplit(string_tc, u_sep, result);
|
||||||
|
|
||||||
|
bool odd_year = int(result[0]) % 2;
|
||||||
|
bool odd_month = int(result[1]) % 2;
|
||||||
|
|
||||||
|
// Calculate week of the year (basic approximation)
|
||||||
|
MqlDateTime dt;
|
||||||
|
TimeToStruct(TimeCurrent(), dt);
|
||||||
|
int iDay = (dt.day_of_week + 6) % 7 + 1; // Convert to 1=Mon,...,7=Sun
|
||||||
|
int iWeek = (dt.day_of_year - iDay + 10) / 7; // Estimate ISO week number
|
||||||
|
bool odd_week = iWeek % 2;
|
||||||
|
|
||||||
|
// Split logic depending on mode
|
||||||
|
if (data_split_method == NO_SPLIT)
|
||||||
|
return true;
|
||||||
|
if (data_split_method == ODD_YEARS && odd_year)
|
||||||
|
return true;
|
||||||
|
if (data_split_method == EVEN_YEARS && !odd_year)
|
||||||
|
return true;
|
||||||
|
if (data_split_method == ODD_MONTHS && odd_month)
|
||||||
|
return true;
|
||||||
|
if (data_split_method == EVEN_MONTHS && !odd_month)
|
||||||
|
return true;
|
||||||
|
if (data_split_method == ODD_WEEKS && odd_week)
|
||||||
|
return true;
|
||||||
|
if (data_split_method == EVEN_WEEKS && !odd_week)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#property library
|
|
||||||
#include <Trade/Trade.mqh>
|
|
||||||
|
|
||||||
enum CUSTOM_MAX_TYPE{
|
|
||||||
CM_WIN_LOSS_RATIO,
|
|
||||||
CM_WIN_PERCENT,
|
|
||||||
CM_WIN_PERCENT_200T,
|
|
||||||
CM_WIN_PERCENT_300T,
|
|
||||||
CM_WIN_PERCENT_400T,
|
|
||||||
CM_WIN_PERCENT_500T,
|
|
||||||
CM_WIN_PERCENT_600T,
|
|
||||||
CM_WIN_PERCENT_700T,
|
|
||||||
CM_WIN_PERCENT_800T,
|
|
||||||
CM_WIN_PERCENT_900T,
|
|
||||||
CM_WIN_PERCENT_1000T,
|
|
||||||
};
|
|
||||||
|
|
||||||
class CustomMax : public CObject{
|
|
||||||
|
|
||||||
protected:
|
|
||||||
double custom_criteria;
|
|
||||||
|
|
||||||
double CustomMax::win_loss_ratio();
|
|
||||||
double CustomMax::win_percent();
|
|
||||||
double CustomMax::win_percent_min_trades(int min_resuired_trades);
|
|
||||||
|
|
||||||
public:
|
|
||||||
double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// CM_WIN_LOSS_RATIO,
|
|
||||||
// CM_WIN_PERCENT
|
|
||||||
double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type){
|
|
||||||
if(cm_type==CM_WIN_LOSS_RATIO){
|
|
||||||
custom_criteria = win_loss_ratio();
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT){
|
|
||||||
custom_criteria = win_percent_min_trades(0);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_200T){
|
|
||||||
custom_criteria = win_percent_min_trades(200);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_300T){
|
|
||||||
custom_criteria = win_percent_min_trades(300);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_400T){
|
|
||||||
custom_criteria = win_percent_min_trades(400);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_500T){
|
|
||||||
custom_criteria = win_percent_min_trades(500);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_600T){
|
|
||||||
custom_criteria = win_percent_min_trades(600);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_700T){
|
|
||||||
custom_criteria = win_percent_min_trades(700);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_800T){
|
|
||||||
custom_criteria = win_percent_min_trades(800);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_900T){
|
|
||||||
custom_criteria = win_percent_min_trades(900);
|
|
||||||
}
|
|
||||||
if(cm_type==CM_WIN_PERCENT_1000T){
|
|
||||||
custom_criteria = win_percent_min_trades(1000);
|
|
||||||
}
|
|
||||||
return custom_criteria;
|
|
||||||
}
|
|
||||||
|
|
||||||
double CustomMax::win_loss_ratio(){
|
|
||||||
double wins = TesterStatistics(STAT_PROFIT_TRADES);
|
|
||||||
double losses = TesterStatistics(STAT_LOSS_TRADES);
|
|
||||||
return wins/losses;
|
|
||||||
}
|
|
||||||
|
|
||||||
double CustomMax::win_percent_min_trades(int min_resuired_trades){
|
|
||||||
double wins = TesterStatistics(STAT_PROFIT_TRADES);
|
|
||||||
double total_trades = TesterStatistics(STAT_TRADES);
|
|
||||||
double result = wins / total_trades * 100;
|
|
||||||
|
|
||||||
if(!MathIsValidNumber(result) || total_trades<min_resuired_trades){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
else{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
#include <Trade/Trade.mqh>
|
||||||
|
#include <MyLibs/Utils/TimeZones.mqh>
|
||||||
|
#include <MyLibs/Utils/MarketDataUtils.mqh>
|
||||||
|
|
||||||
|
class CalculatePositionData : public CObject{
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CTrade trade;
|
||||||
|
CPositionInfo position;
|
||||||
|
MarketDataUtils mdu;
|
||||||
|
|
||||||
|
bool check_lots(double &lots, string symbol);
|
||||||
|
bool normalise_price(double price, double &normalizedPrice, string symbol);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
double calculate_stoploss(string symbol, double price, int order_side, string _sl_mode, double sl_var, ENUM_TIMEFRAMES atr_period);
|
||||||
|
double calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, ENUM_TIMEFRAMES atr_period);
|
||||||
|
double calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var);
|
||||||
|
double calculate_trading_cost(string symbol, ulong position_ticket);
|
||||||
|
};
|
||||||
|
|
||||||
|
double CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_period){
|
||||||
|
// order_side int must be 1 for BUY or 2 for
|
||||||
|
|
||||||
|
double sl=0;
|
||||||
|
|
||||||
|
if(mode_sl=="NO_STOPLOSS"){
|
||||||
|
sl=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_sl=="SL_BREAKEVEN"){
|
||||||
|
// https://www.youtube.com/watch?v=idPulZ3_iR0
|
||||||
|
Alert("Not implemented yet yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_sl=="SL_FIXED_PIPS"){
|
||||||
|
// pips/poins = https://www.mql5.com/en/forum/187757
|
||||||
|
double adj_point = mdu.adjusted_point(symbol);
|
||||||
|
|
||||||
|
if(order_side == 1){
|
||||||
|
sl = price - sl_var * adj_point;
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
sl = price + sl_var * adj_point;
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_sl=="SL_FIXED_PERCENT"){
|
||||||
|
if(order_side == 1){
|
||||||
|
sl = (-1.0 * sl_var * price / 100.00) + price;
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
sl = sl_var * price / 100.00 + price;
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_sl=="SL_ATR_MULTIPLE"){
|
||||||
|
|
||||||
|
int _atr_handle = iATR(symbol,atr_period,14);
|
||||||
|
double atr[];
|
||||||
|
ArraySetAsSeries(atr,true);
|
||||||
|
CopyBuffer(_atr_handle,MAIN_LINE,1,1,atr);
|
||||||
|
|
||||||
|
if(order_side == 1){
|
||||||
|
sl = price - (atr[0] * sl_var);
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
sl = price + (atr[0] * sl_var);
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_sl=="SL_SPECIFIED_VALUE"){
|
||||||
|
|
||||||
|
double adj_point = mdu.adjusted_point(symbol);
|
||||||
|
|
||||||
|
if(order_side == 1){
|
||||||
|
|
||||||
|
double pip_50_sl = price - 10 * adj_point;
|
||||||
|
if(sl_var >= pip_50_sl){
|
||||||
|
sl = pip_50_sl;
|
||||||
|
}
|
||||||
|
else sl = sl_var;
|
||||||
|
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
double pip_50_sl = price + 10 * adj_point;
|
||||||
|
if(sl_var <= pip_50_sl){
|
||||||
|
sl = pip_50_sl;
|
||||||
|
}
|
||||||
|
else sl = sl_var;
|
||||||
|
|
||||||
|
sl = sl = sl_var;
|
||||||
|
if(!normalise_price(sl,sl,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sl;
|
||||||
|
}
|
||||||
|
|
||||||
|
double CalculatePositionData::calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double _tp_var, ENUM_TIMEFRAMES atr_period){
|
||||||
|
// order_side int must be 1 for BUY or 2 for SELL
|
||||||
|
|
||||||
|
double tp=0;
|
||||||
|
|
||||||
|
if(mode_tp=="NO_TAKE_PROFIT"){
|
||||||
|
tp=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_tp=="TP_FIXED_PIPS"){
|
||||||
|
|
||||||
|
double adj_point = mdu.adjusted_point(symbol);
|
||||||
|
if(order_side == 1){
|
||||||
|
tp = price + _tp_var * adj_point;
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
tp = price - _tp_var * adj_point;
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_tp=="TP_FIXED_PERCENT"){
|
||||||
|
if(order_side == 1){
|
||||||
|
tp = _tp_var * price / 100.00 + price;
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
tp = (-1 * _tp_var * price / 100.00) + price;
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_tp=="TP_ATR_MULTIPLE"){
|
||||||
|
|
||||||
|
int _atr_handle = iATR(symbol,atr_period,14);
|
||||||
|
double atr[];
|
||||||
|
ArraySetAsSeries(atr,true);
|
||||||
|
CopyBuffer(_atr_handle,MAIN_LINE,1,1,atr);
|
||||||
|
|
||||||
|
if(order_side == 1){
|
||||||
|
tp = price + (atr[0] * _tp_var);
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
tp = price - (atr[0] * _tp_var);
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_tp=="TP_SL_MULTIPLE"){
|
||||||
|
if(order_side == 1){
|
||||||
|
double sl_size = price - stoploss;
|
||||||
|
tp = price + (_tp_var * sl_size);
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
double sl_size = stoploss - price;
|
||||||
|
tp = price - (_tp_var * sl_size);
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_tp=="TP_SPECIFIED_VALUE"){
|
||||||
|
|
||||||
|
if(_tp_var!=0){
|
||||||
|
double adj_point = mdu.adjusted_point(symbol);
|
||||||
|
|
||||||
|
if(order_side == 1){
|
||||||
|
double pip_limit = price + 10 * adj_point;
|
||||||
|
if(_tp_var <= pip_limit){
|
||||||
|
tp = pip_limit;
|
||||||
|
}
|
||||||
|
else tp = _tp_var;
|
||||||
|
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
if(order_side == 2){
|
||||||
|
double pip_limit = price - 10 * adj_point;
|
||||||
|
if(_tp_var >= pip_limit){
|
||||||
|
tp = pip_limit;
|
||||||
|
}
|
||||||
|
else tp = _tp_var;
|
||||||
|
tp = tp = _tp_var;
|
||||||
|
if(!normalise_price(tp,tp,symbol)){return false;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tp;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
double CalculatePositionData::calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var){
|
||||||
|
|
||||||
|
double lots = 0;
|
||||||
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||||
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||||||
|
double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||||
|
|
||||||
|
double account_value = fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE)),AccountInfoDouble(ACCOUNT_MARGIN_FREE));
|
||||||
|
double risk_money = account_value * lot_var / 100;
|
||||||
|
|
||||||
|
if(mode_lot=="LOT_MODE_FIXED"){
|
||||||
|
lots = lot_var;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_lot=="LOT_MODE_PCT_RISK"){
|
||||||
|
double money_lot_step = (sl_distance / tick_size) * tick_value * volume_step;
|
||||||
|
lots = MathFloor(risk_money/money_lot_step) * volume_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_lot=="LOT_MODE_PCT_ACCOUNT"){
|
||||||
|
double money_lot_step = (price / tick_size) * tick_value * volume_step;
|
||||||
|
lots = MathFloor(risk_money/money_lot_step) * volume_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!check_lots(lots, symbol)){return false;}
|
||||||
|
return lots;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CalculatePositionData::check_lots(double &lots, string symbol){
|
||||||
|
|
||||||
|
double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||||
|
double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||||
|
double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||||
|
|
||||||
|
if(lots<min){
|
||||||
|
Print("Lot size will be set to minimum allowed volume");
|
||||||
|
lots = min;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lots>max){
|
||||||
|
Print("Lot size greater than maximum allowed volume. lots:",lots,"max:",max);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lots = (int)MathFloor(lots/step) * step;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CalculatePositionData::normalise_price(double price, double &normalizedPrice, string symbol){
|
||||||
|
double tickSize;
|
||||||
|
if(!SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE,tickSize)){
|
||||||
|
Print("Failed to get tick size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||||
|
normalizedPrice = NormalizeDouble(MathRound(price/tickSize)*tickSize, symbol_digits);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double CalculatePositionData::calculate_trading_cost(string symbol, ulong position_ticket){
|
||||||
|
|
||||||
|
position.SelectByTicket(position_ticket);
|
||||||
|
|
||||||
|
double swap = PositionGetDouble(POSITION_SWAP);
|
||||||
|
double commission = PositionGetDouble(POSITION_COMMISSION);
|
||||||
|
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||||
|
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||||||
|
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||||
|
double lots = PositionGetDouble(POSITION_VOLUME);
|
||||||
|
double trading_cost = -1 * ((commission + swap) / tick_value * tick_size / lots);
|
||||||
|
|
||||||
|
return trading_cost;
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#include <Trade/Trade.mqh>
|
||||||
|
#include <MyLibs/Orders/CalculatePositionData.mqh>
|
||||||
|
|
||||||
|
class EntryOrders {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CTrade trade;
|
||||||
|
CalculatePositionData calc;
|
||||||
|
double stop_loss;
|
||||||
|
double take_profit;
|
||||||
|
int total_open_buy_orders;
|
||||||
|
int total_open_sell_orders;
|
||||||
|
double current_price;
|
||||||
|
|
||||||
|
int count_open_positions(string symbol, int order_side, long magic_number);
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var,string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number);
|
||||||
|
bool open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number);
|
||||||
|
bool open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation,ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number);
|
||||||
|
bool open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation,ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var,string _lot_mode, double lot_var, long magic_number);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int EntryOrders::count_open_positions(string symbol, int order_side, long magic_number) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
ulong ticket = PositionGetTicket(i);
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||||
|
PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
if ((order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ||
|
||||||
|
(order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode,
|
||||||
|
double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
|
||||||
|
long magic_number) {
|
||||||
|
if (condition) {
|
||||||
|
current_price = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
||||||
|
total_open_buy_orders = count_open_positions(symbol, 1, magic_number);
|
||||||
|
if (total_open_buy_orders == 0) {
|
||||||
|
stop_loss = calc.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period);
|
||||||
|
take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
|
||||||
|
double sl_distance = current_price - stop_loss;
|
||||||
|
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
|
||||||
|
trade.SetExpertMagicNumber(magic_number);
|
||||||
|
string comment = "Magic Number: " + IntegerToString(magic_number);
|
||||||
|
trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, take_profit, comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode,
|
||||||
|
double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
|
||||||
|
long magic_number) {
|
||||||
|
if (condition) {
|
||||||
|
current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||||
|
total_open_sell_orders = count_open_positions(symbol, 2, magic_number);
|
||||||
|
if (total_open_sell_orders == 0) {
|
||||||
|
stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period);
|
||||||
|
take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
|
||||||
|
double sl_distance = stop_loss - current_price;
|
||||||
|
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
|
||||||
|
trade.SetExpertMagicNumber(magic_number);
|
||||||
|
string comment = "Magic Number: " + IntegerToString(magic_number);
|
||||||
|
trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, take_profit, comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation,
|
||||||
|
ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode,
|
||||||
|
double tp_var, string _lot_mode, double lot_var, long magic_number) {
|
||||||
|
if (condition) {
|
||||||
|
total_open_buy_orders = count_open_positions(symbol, 1, magic_number);
|
||||||
|
if (total_open_buy_orders == 0) {
|
||||||
|
stop_loss = calc.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period);
|
||||||
|
take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
|
||||||
|
double sl_distance = entry_price - stop_loss;
|
||||||
|
double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var);
|
||||||
|
trade.SetExpertMagicNumber(magic_number);
|
||||||
|
string comment = "Magic Number: " + IntegerToString(magic_number);
|
||||||
|
trade.BuyStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation,
|
||||||
|
ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode,
|
||||||
|
double tp_var, string _lot_mode, double lot_var, long magic_number) {
|
||||||
|
if (condition) {
|
||||||
|
total_open_sell_orders = count_open_positions(symbol, 2, magic_number);
|
||||||
|
if (total_open_sell_orders == 0) {
|
||||||
|
stop_loss = calc.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period);
|
||||||
|
take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
|
||||||
|
double sl_distance = stop_loss - entry_price;
|
||||||
|
double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var);
|
||||||
|
trade.SetExpertMagicNumber(magic_number);
|
||||||
|
string comment = "Magic Number: " + IntegerToString(magic_number);
|
||||||
|
trade.SellStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#include <Trade/Trade.mqh>
|
||||||
|
#include <MyLibs/Utils/TimeZones.mqh>
|
||||||
|
#include <MyLibs/Orders/CalculatePositionData.mqh>
|
||||||
|
|
||||||
|
class ExitOrders {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CTrade trade;
|
||||||
|
TimeZones tz;
|
||||||
|
CalculatePositionData cpd;
|
||||||
|
|
||||||
|
ulong posTicket;
|
||||||
|
long position_open_time;
|
||||||
|
long first_allowed_close_time;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,long magic_number);
|
||||||
|
bool close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,long magic_number);
|
||||||
|
bool daily_timed_exit(string symbol, datetime exit_time, int delay_days, long magic_number);
|
||||||
|
bool daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long magic_number);
|
||||||
|
bool first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool ExitOrders::close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,
|
||||||
|
long magic_number) {
|
||||||
|
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
posTicket = PositionGetTicket(i);
|
||||||
|
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
|
||||||
|
|
||||||
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
|
||||||
|
if (condition || (close_bars > 0 && time_difference >= close_bars)) {
|
||||||
|
trade.PositionClose(posTicket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ExitOrders::close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,
|
||||||
|
long magic_number) {
|
||||||
|
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
posTicket = PositionGetTicket(i);
|
||||||
|
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
|
||||||
|
|
||||||
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
|
||||||
|
if (condition || (close_bars > 0 && time_difference >= close_bars)) {
|
||||||
|
trade.PositionClose(posTicket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ExitOrders::daily_timed_exit(string symbol, datetime exit_time, int delay_days, long magic_number) {
|
||||||
|
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
posTicket = PositionGetTicket(i);
|
||||||
|
position_open_time = PositionGetInteger(POSITION_TIME);
|
||||||
|
|
||||||
|
if ((int)position_open_time > 0) {
|
||||||
|
first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1));
|
||||||
|
|
||||||
|
if (TimeCurrent() > first_allowed_close_time && TimeCurrent() >= exit_time) {
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
trade.PositionClose(posTicket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ExitOrders::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time,
|
||||||
|
string cw_tzone, int delay_days, long magic_number) {
|
||||||
|
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
posTicket = PositionGetTicket(i);
|
||||||
|
position_open_time = PositionGetInteger(POSITION_TIME);
|
||||||
|
|
||||||
|
if ((int)position_open_time > 0) {
|
||||||
|
first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1));
|
||||||
|
|
||||||
|
if (TimeCurrent() > first_allowed_close_time) {
|
||||||
|
datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker");
|
||||||
|
|
||||||
|
if (TimeCurrent() >= broker_close_time &&
|
||||||
|
PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||||
|
PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
|
||||||
|
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||||
|
double spread = SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||||
|
double bar_close = iClose(_Symbol, close_bar_period, 1); // shift 1 because 0 is live candle
|
||||||
|
double trading_cost = cpd.calculate_trading_cost(symbol, posTicket);
|
||||||
|
|
||||||
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
|
||||||
|
bar_close > (position_open_price + spread + trading_cost)) {
|
||||||
|
trade.PositionClose(posTicket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
|
||||||
|
bar_close < (position_open_price - spread - trading_cost)) {
|
||||||
|
trade.PositionClose(posTicket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ExitOrders::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number) {
|
||||||
|
|
||||||
|
position_open_time = PositionGetInteger(POSITION_TIME);
|
||||||
|
first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period);
|
||||||
|
|
||||||
|
if ((int)position_open_time > 0 && TimeCurrent() > first_allowed_close_time) {
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
posTicket = PositionGetTicket(i);
|
||||||
|
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||||
|
double spread = SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID);
|
||||||
|
double bar_close = iClose(_Symbol, close_bar_period, 1);
|
||||||
|
double trading_cost = cpd.calculate_trading_cost(symbol, posTicket);
|
||||||
|
|
||||||
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
|
||||||
|
bar_close > (position_open_price + spread + trading_cost)) {
|
||||||
|
trade.PositionClose(posTicket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
|
||||||
|
bar_close < (position_open_price - spread - trading_cost)) {
|
||||||
|
trade.PositionClose(posTicket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#include <Trade/OrderInfo.mqh>
|
||||||
|
#include <Trade/PositionInfo.mqh>
|
||||||
|
|
||||||
|
class OrderTracker {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
COrderInfo m_order;
|
||||||
|
CPositionInfo m_position;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int count_open_positions(string symbol, int order_side, long magic_number);
|
||||||
|
int count_all_positions(string symbol, long magic_number);
|
||||||
|
int count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic);
|
||||||
|
};
|
||||||
|
|
||||||
|
int OrderTracker::count_open_positions(string symbol, int order_side, long magic_number) {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
ulong ticket = PositionGetTicket(i);
|
||||||
|
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||||
|
PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
|
||||||
|
if (order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int OrderTracker::count_all_positions(string symbol, long magic_number) {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
ulong ticket = PositionGetTicket(i);
|
||||||
|
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||||
|
PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int OrderTracker::count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic) {
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = OrdersTotal() - 1; i >= 0; i--) {
|
||||||
|
if (m_order.SelectByIndex(i)) {
|
||||||
|
if (OrderGetInteger(ORDER_MAGIC) == magic && OrderGetString(ORDER_SYMBOL) == symbol) {
|
||||||
|
if (m_order.OrderType() == order_type) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
class StopLogic {
|
||||||
|
public:
|
||||||
|
double sl_specified_value_switch(string sl_mode, double inp_sl_var, double value);
|
||||||
|
double tp_specified_value_switch(string tp_mode, double inp_tp_var, double value);
|
||||||
|
};
|
||||||
|
|
||||||
|
double StopLogic::sl_specified_value_switch(string sl_mode, double inp_sl_var, double value) {
|
||||||
|
if (sl_mode == "SL_SPECIFIED_VALUE") {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return inp_sl_var;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double StopLogic::tp_specified_value_switch(string tp_mode, double inp_tp_var, double value) {
|
||||||
|
if (tp_mode == "SL_SPECIFIED_VALUE") {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return inp_tp_var;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
#include <Trade/Trade.mqh>
|
||||||
|
|
||||||
|
class TrailingLogic {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CTrade trade;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer);
|
||||||
|
void nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void TrailingLogic::break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer) {
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
|
||||||
|
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||||
|
double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||||
|
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), symbol_digits);
|
||||||
|
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), symbol_digits);
|
||||||
|
|
||||||
|
if (be_trigger_points != 0) {
|
||||||
|
ulong ticket = PositionGetTicket(i);
|
||||||
|
if (PositionSelectByTicket(ticket)) {
|
||||||
|
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||||
|
double position_volume = PositionGetDouble(POSITION_VOLUME);
|
||||||
|
double position_sl = PositionGetDouble(POSITION_SL);
|
||||||
|
double position_tp = PositionGetDouble(POSITION_TP);
|
||||||
|
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||||
|
|
||||||
|
if (position_type == POSITION_TYPE_BUY &&
|
||||||
|
bid > position_open_price + be_trigger_points * symbol_point) {
|
||||||
|
|
||||||
|
double sl = NormalizeDouble(position_open_price + be_puffer * symbol_point, symbol_digits);
|
||||||
|
if (sl > position_sl) {
|
||||||
|
trade.PositionModify(ticket, sl, position_tp);
|
||||||
|
Print("-----------------------------------Stop moved to break even");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position_type == POSITION_TYPE_SELL &&
|
||||||
|
ask < position_open_price - be_trigger_points * symbol_point) {
|
||||||
|
|
||||||
|
double sl = NormalizeDouble(position_open_price - be_puffer * symbol_point, symbol_digits);
|
||||||
|
if (sl < position_sl) {
|
||||||
|
trade.PositionModify(ticket, sl, position_tp);
|
||||||
|
Print("-----------------------------------Stop moved to break even");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TrailingLogic::nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number) {
|
||||||
|
|
||||||
|
for (int i = PositionsTotal() - 1; i >= 0; i--) {
|
||||||
|
ulong ticket = PositionGetTicket(i);
|
||||||
|
if (PositionSelectByTicket(ticket)) {
|
||||||
|
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
|
||||||
|
|
||||||
|
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||||
|
double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||||
|
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), symbol_digits);
|
||||||
|
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), symbol_digits);
|
||||||
|
|
||||||
|
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||||
|
double position_sl = PositionGetDouble(POSITION_SL);
|
||||||
|
double position_tp = PositionGetDouble(POSITION_TP);
|
||||||
|
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||||
|
|
||||||
|
if (position_type == POSITION_TYPE_BUY &&
|
||||||
|
bid > position_open_price + (atr_value * tp_var)) {
|
||||||
|
|
||||||
|
double sl = NormalizeDouble(bid - (atr_value * sl_var), symbol_digits);
|
||||||
|
if (sl > (position_sl + (atr_value * 0.5))) {
|
||||||
|
trade.PositionModify(ticket, sl, position_tp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position_type == POSITION_TYPE_SELL &&
|
||||||
|
ask < position_open_price - (atr_value * tp_var)) {
|
||||||
|
|
||||||
|
double sl = NormalizeDouble(ask + (atr_value * sl_var), symbol_digits);
|
||||||
|
if (sl < (position_sl + (atr_value * 0.5))) {
|
||||||
|
trade.PositionModify(ticket, sl, position_tp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
#property library
|
#property library
|
||||||
#include <Trade/Trade.mqh>
|
#include <Trade/Trade.mqh>
|
||||||
#include <MyLibs/MyFunctions.mqh>
|
#include <MyLibs/Utils/BarUtils.mqh>
|
||||||
|
|
||||||
class DrawdownControl : public CObject {
|
class DrawdownControl : public CObject {
|
||||||
protected:
|
protected:
|
||||||
CTrade trade;
|
CTrade trade;
|
||||||
MyFunctions mf;
|
BarUtils bar_utils;
|
||||||
|
|
||||||
string data_file;
|
string data_file;
|
||||||
double daily_max_dd_per;
|
double daily_max_dd_per;
|
||||||
@@ -167,7 +167,7 @@ double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min
|
|||||||
}
|
}
|
||||||
|
|
||||||
// back-up to file every hour:
|
// back-up to file every hour:
|
||||||
if(mf.is_new_bar(_Symbol, PERIOD_H1) == true){
|
if(bar_utils.is_new_bar(_Symbol, PERIOD_H1) == true){
|
||||||
write_global_var_data();
|
write_global_var_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
|
||||||
|
class EntryState {
|
||||||
|
public:
|
||||||
|
int last_trigger_bar_long;
|
||||||
|
int last_trigger_bar_short;
|
||||||
|
int last_bl_cross_long;
|
||||||
|
int last_bl_cross_short;
|
||||||
|
int last_entry_long;
|
||||||
|
int last_entry_short;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
EntryState() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
last_trigger_bar_long = -1000;
|
||||||
|
last_trigger_bar_short = -1000;
|
||||||
|
last_bl_cross_long = -1000;
|
||||||
|
last_bl_cross_short = -1000;
|
||||||
|
last_entry_long = -1000;
|
||||||
|
last_entry_short = -1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_trigger(bool trig_long, bool trig_short, int curr_bar) {
|
||||||
|
if (trig_long) last_trigger_bar_long = curr_bar;
|
||||||
|
if (trig_short) last_trigger_bar_short = curr_bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_baseline_cross(int curr_bar, double price, double baseline, double prev_price, double prev_baseline) {
|
||||||
|
if (prev_price < prev_baseline && price > baseline)
|
||||||
|
last_bl_cross_long = curr_bar;
|
||||||
|
if (prev_price > prev_baseline && price < baseline)
|
||||||
|
last_bl_cross_short = curr_bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_entry(bool is_long, int curr_bar) {
|
||||||
|
if (is_long)
|
||||||
|
last_entry_long = curr_bar;
|
||||||
|
else
|
||||||
|
last_entry_short = curr_bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_last_trigger(bool is_long) {
|
||||||
|
return is_long ? last_trigger_bar_long : last_trigger_bar_short;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_last_cross(bool is_long) {
|
||||||
|
return is_long ? last_bl_cross_long : last_bl_cross_short;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_last_entry(bool is_long) {
|
||||||
|
return is_long ? last_entry_long : last_entry_short;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Snapshot of conditions for a potential entry signal
|
||||||
|
struct EntryContext {
|
||||||
|
bool trigger;
|
||||||
|
bool confirm;
|
||||||
|
bool volume;
|
||||||
|
bool recent;
|
||||||
|
bool base_ok;
|
||||||
|
bool near;
|
||||||
|
bool far;
|
||||||
|
int last_entry;
|
||||||
|
int last_cross;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utility: check if signal occurred recently
|
||||||
|
bool is_recent(int signal_bar, int curr_bar, int lookback) {
|
||||||
|
return (curr_bar - signal_bar) < lookback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility: build current entry condition context
|
||||||
|
EntryContext build_entry_context(bool is_long, double price, double baseline, double atr,
|
||||||
|
int last_cross, int last_entry,
|
||||||
|
bool trigger, bool confirm, bool volume, bool recent) {
|
||||||
|
EntryContext ctx;
|
||||||
|
ctx.trigger = trigger;
|
||||||
|
ctx.confirm = confirm;
|
||||||
|
ctx.volume = volume;
|
||||||
|
ctx.recent = recent;
|
||||||
|
ctx.base_ok = is_long ? (price > baseline) : (price < baseline);
|
||||||
|
ctx.near = MathAbs(price - baseline) <= atr;
|
||||||
|
ctx.far = MathAbs(price - baseline) > atr;
|
||||||
|
ctx.last_entry = last_entry;
|
||||||
|
ctx.last_cross = last_cross;
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class wrapper for entry logic
|
||||||
|
class EntryLogic {
|
||||||
|
public:
|
||||||
|
bool is_standard_entry(const EntryContext &ctx) {
|
||||||
|
return ctx.trigger && ctx.confirm && ctx.volume && ctx.recent && ctx.base_ok && ctx.near;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_pullback_entry(const EntryContext &ctx, int curr_bar) {
|
||||||
|
return (curr_bar - ctx.last_cross <= 2) && ctx.confirm && ctx.volume && ctx.base_ok && ctx.far;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_baseline_cross_entry(const EntryContext &ctx, double prev_price, double prev_baseline,double price, double baseline, bool is_long){
|
||||||
|
bool crossed = is_long ? (prev_price < prev_baseline && price > baseline): (prev_price > prev_baseline && price < baseline);
|
||||||
|
return crossed && ctx.confirm && ctx.volume && ctx.near;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_continuation_entry(const EntryContext &ctx, int curr_bar, int look_back) {
|
||||||
|
return (curr_bar - ctx.last_entry <= look_back) && ctx.last_entry > ctx.last_cross && ctx.trigger && ctx.confirm && ctx.base_ok;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#property library
|
#property library
|
||||||
#include <Trade/Trade.mqh>
|
#include <Trade/Trade.mqh>
|
||||||
#include <MyLibs/TimeZones.mqh>
|
#include <MyLibs/Utils/TimeZones.mqh>
|
||||||
|
|
||||||
class RangeCalculator : public CObject{
|
class RangeCalculator : public CObject{
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#include <Object.mqh>
|
||||||
|
|
||||||
|
class ChartUtils : public CObject {
|
||||||
|
|
||||||
|
public:
|
||||||
|
void draw_line(double value, string name, color clr = clrBlack);
|
||||||
|
};
|
||||||
|
|
||||||
|
void ChartUtils::draw_line(double value, string name, color clr) {
|
||||||
|
if (ObjectFind(0, name) < 0) {
|
||||||
|
ResetLastError();
|
||||||
|
|
||||||
|
if (!ObjectCreate(0, name, OBJ_HLINE, 0, 0, value)) {
|
||||||
|
Print(__FUNCTION__, ": failed to create a horizontal line! Error code = ", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
||||||
|
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
|
||||||
|
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetLastError();
|
||||||
|
|
||||||
|
if (!ObjectMove(0, name, 0, 0, value)) {
|
||||||
|
Print(__FUNCTION__, ": failed to move the horizontal line! Error code = ", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChartRedraw();
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
#property library
|
|
||||||
|
|
||||||
enum LOT_MODE{
|
enum LOT_MODE{
|
||||||
LOT_MODE_FIXED, // Fixed Lot Size
|
LOT_MODE_FIXED, // Fixed Lot Size
|
||||||
LOT_MODE_PCT_ACCOUNT, // Percent of Account (fixed)
|
LOT_MODE_PCT_ACCOUNT, // Percent of Account (fixed)
|
||||||
@@ -41,13 +39,3 @@ enum MULTI_SYM_MODE{
|
|||||||
MULTI_SYM_FX_B5, // FX Benchmark 5
|
MULTI_SYM_FX_B5, // FX Benchmark 5
|
||||||
MULTI_SYM_FX_28 // FX 28 Majors
|
MULTI_SYM_FX_28 // FX 28 Majors
|
||||||
};
|
};
|
||||||
// used to generate in and out of sample data sets
|
|
||||||
enum MODE_SPLIT_DATA{
|
|
||||||
NO_SPLIT,
|
|
||||||
ODD_YEARS,
|
|
||||||
EVEN_YEARS,
|
|
||||||
ODD_MONTHS,
|
|
||||||
EVEN_MONTHS,
|
|
||||||
ODD_WEEKS,
|
|
||||||
EVEN_WEEKS
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
#include <MyLibs/Myfunctions.mqh>
|
#include <MyLibs/Myfunctions.mqh>
|
||||||
#include <MyLibs/OrderManagement.mqh>
|
#include <MyLibs/OrderManagement.mqh>
|
||||||
#include <MyLibs/MyEnums.mqh>
|
#include <MyLibs/Utils/MyEnums.mqh>
|
||||||
#include <MyLibs/CustomMax.mqh>
|
#include <MyLibs/BacktestUtils/CustomMax.mqh>
|
||||||
CustomMax cm;
|
#include <MyLibs/BacktestUtils/TestDataSplit.mqh>
|
||||||
MyFunctions mf;
|
CustomMax c_max;
|
||||||
OrderManagment om;
|
// MyFunctions mf;
|
||||||
|
// OrderManagment om;
|
||||||
//---
|
//---
|
||||||
string SymbolsArray[];
|
|
||||||
input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Lot Size Mode
|
input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Lot Size Mode
|
||||||
input double inp_lot_var = 2; // Lot Size Var
|
input double inp_lot_var = 2; // Lot Size Var
|
||||||
input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop-loss Mode
|
input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop-loss Mode
|
||||||
@@ -16,8 +16,8 @@ input double inp_tp_var = 1; // Take-Profit Var
|
|||||||
string lot_mode = EnumToString(inp_lot_mode);
|
string lot_mode = EnumToString(inp_lot_mode);
|
||||||
string sl_mode = EnumToString(inp_sl_mode);
|
string sl_mode = EnumToString(inp_sl_mode);
|
||||||
string tp_mode = EnumToString(inp_tp_mode);
|
string tp_mode = EnumToString(inp_tp_mode);
|
||||||
input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT_200T;
|
input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT;
|
||||||
input MULTI_SYM_MODE inp_sym_mode = MULTI_SYM_FX_B5;
|
input int inp_opt_min_trades = 0; // 0/off
|
||||||
input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT;
|
input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT;
|
||||||
input int inp_force_opt = 1;
|
input int inp_force_opt = 1;
|
||||||
input group "-----------------------------------------"
|
input group "-----------------------------------------"
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
class MarketDataUtils {
|
||||||
|
public:
|
||||||
|
bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time = "00:10");
|
||||||
|
double get_latest_buffer_value(int handle);
|
||||||
|
double get_buffer_value(int handle, int shift);
|
||||||
|
double adjusted_point(string symbol);
|
||||||
|
double get_bid_ask_price(string symbol, int price_side);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
datetime previousTime; // Stores the last recorded bar open time
|
||||||
|
datetime bar_open_time; // Stores the current bar's open time
|
||||||
|
};
|
||||||
|
|
||||||
|
// Checks if a new bar has opened on the given timeframe and symbol
|
||||||
|
bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) {
|
||||||
|
bar_open_time = iTime(symbol, time_frame, 0); // Current open time
|
||||||
|
|
||||||
|
if (previousTime != bar_open_time) {
|
||||||
|
// For daily timeframe, wait for specific time (e.g., 00:10) before triggering
|
||||||
|
if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) {
|
||||||
|
if (TimeCurrent() > StringToTime(daily_start_time)) {
|
||||||
|
previousTime = bar_open_time;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
previousTime = bar_open_time;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // No new bar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the latest value from an indicator buffer (shift 0)
|
||||||
|
double MarketDataUtils::get_latest_buffer_value(int handle) {
|
||||||
|
double val[];
|
||||||
|
ArraySetAsSeries(val, true); // Aligns array with bar indexing (0 = latest)
|
||||||
|
|
||||||
|
if (CopyBuffer(handle, 0, 0, 1, val) == 1)
|
||||||
|
return val[0]; // Latest value at shift 0
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves a historical buffer value at specified shift
|
||||||
|
double MarketDataUtils::get_buffer_value(int handle, int shift) {
|
||||||
|
double val[];
|
||||||
|
ArraySetAsSeries(val, true);
|
||||||
|
|
||||||
|
if (CopyBuffer(handle, 0, shift, 1, val) == 1)
|
||||||
|
return val[0]; // Historical value at given shift
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjusts the point value for symbol to account for fractional pips (e.g., 5-digit brokers)
|
||||||
|
double MarketDataUtils::adjusted_point(string symbol) {
|
||||||
|
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||||
|
int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1;
|
||||||
|
double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||||
|
return point_val * digits_adjust; // Adjusted pip value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns current Bid or Ask price for a symbol based on side (1 = Ask, 2 = Bid)
|
||||||
|
double MarketDataUtils::get_bid_ask_price(string symbol, int price_side) {
|
||||||
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||||
|
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits);
|
||||||
|
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits);
|
||||||
|
|
||||||
|
if (price_side == 1) return ask;
|
||||||
|
if (price_side == 2) return bid;
|
||||||
|
|
||||||
|
return 0.0; // Invalid input
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#property library
|
#property library
|
||||||
#include <Trade/Trade.mqh>
|
#include <Trade/Trade.mqh>
|
||||||
#include <MyLibs/DealingWithTime.mqh>
|
#include <MyLibs/Utils/DealingWithTime.mqh>
|
||||||
|
|
||||||
class TimeZones: public CObject{
|
class TimeZones: public CObject{
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#include <MyLibs/Utils/TimeZones.mqh>
|
||||||
|
|
||||||
|
class TradeSessionUtils {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TimeZones tz; // For handling timezone conversion
|
||||||
|
bool in_window; // Whether the current time is in the allowed window
|
||||||
|
datetime start_time; // Session start time (converted to Broker time)
|
||||||
|
datetime end_time; // Session end time (converted to Broker time)
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool trade_window(string t1, string t2, string time_zone = "Broker", bool plot_range_inp = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool TradeSessionUtils::trade_window(string t1, string t2, string time_zone, bool plot_range_inp) {
|
||||||
|
datetime _t1 = StringToTime(t1); // Convert string to datetime
|
||||||
|
datetime _t2 = StringToTime(t2); // Convert string to datetime
|
||||||
|
|
||||||
|
// Handle overnight windows (e.g. 22:00–01:00)
|
||||||
|
if (_t1 > _t2) {
|
||||||
|
_t2 = _t2 + PeriodSeconds(PERIOD_D1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int w_duration = (int)(_t2 - _t1); // Duration of the session in seconds
|
||||||
|
|
||||||
|
// Check if we're currently within the window
|
||||||
|
if (TimeCurrent() >= start_time && TimeCurrent() <= end_time) {
|
||||||
|
in_window = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've moved beyond the previous window, define a new one
|
||||||
|
if (TimeCurrent() >= end_time) {
|
||||||
|
in_window = false;
|
||||||
|
|
||||||
|
// Convert start time to broker time based on user timezone input
|
||||||
|
start_time = tz.timezone_conversions(time_zone, StringToTime(t1), "Broker");
|
||||||
|
|
||||||
|
// If we've already passed today's start time, push it to tomorrow
|
||||||
|
if (TimeCurrent() >= start_time) {
|
||||||
|
start_time += PeriodSeconds(PERIOD_D1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End time is relative to updated start time
|
||||||
|
end_time = start_time + w_duration;
|
||||||
|
|
||||||
|
// Plot vertical lines if requested
|
||||||
|
if (plot_range_inp) {
|
||||||
|
string name = "Start Time" + (string)start_time;
|
||||||
|
if (start_time > 0) {
|
||||||
|
ObjectCreate(NULL, name, OBJ_VLINE, 0, start_time, 0);
|
||||||
|
ObjectSetInteger(NULL, name, OBJPROP_COLOR, clrBlue);
|
||||||
|
ObjectSetInteger(NULL, name, OBJPROP_BACK, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = "End Time" + (string)end_time;
|
||||||
|
if (end_time > 0) {
|
||||||
|
ObjectCreate(NULL, name, OBJ_VLINE, 0, end_time, 0);
|
||||||
|
ObjectSetInteger(NULL, name, OBJPROP_COLOR, C'56,108,26');
|
||||||
|
ObjectSetInteger(NULL, name, OBJPROP_BACK, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChartRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_window;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
class BarUtils {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
datetime previousTime; // Stores the previous bar open time to detect new bars
|
||||||
|
datetime bar_open_time; // Current bar open time
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time = "00:10");
|
||||||
|
};
|
||||||
|
|
||||||
|
bool BarUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) {
|
||||||
|
// Get the open time of the current bar
|
||||||
|
bar_open_time = iTime(symbol, time_frame, 0);
|
||||||
|
|
||||||
|
// Check if it's different from the last seen time — this implies a new bar has formed
|
||||||
|
if (previousTime != bar_open_time) {
|
||||||
|
|
||||||
|
// Special logic for daily bars: wait until a specific time-of-day threshold
|
||||||
|
if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) {
|
||||||
|
// Don't trigger on midnight, wait until configured daily_start_time (e.g., "00:10")
|
||||||
|
if (TimeCurrent() > StringToTime(daily_start_time)) {
|
||||||
|
previousTime = bar_open_time; // Update the marker
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For all non-daily timeframes, treat any change in bar time as new bar
|
||||||
|
previousTime = bar_open_time;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No new bar detected
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
class BufferUtils {
|
||||||
|
|
||||||
|
public:
|
||||||
|
double get_latest_buffer_value(int handle);
|
||||||
|
double get_buffer_value(int handle, int shift);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ---- IMPLEMENTATION BELOW ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the most recent value from the specified indicator buffer.
|
||||||
|
*
|
||||||
|
* param handle: Indicator handle (must be valid and previously created)
|
||||||
|
* return: Most recent buffer value (shift 0), or 0.0 if retrieval fails
|
||||||
|
*/
|
||||||
|
double BufferUtils::get_latest_buffer_value(int handle) {
|
||||||
|
double val[];
|
||||||
|
ArraySetAsSeries(val, true);
|
||||||
|
|
||||||
|
if (CopyBuffer(handle, 0, 0, 1, val) == 1)
|
||||||
|
return val[0];
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific historical value from the specified indicator buffer.
|
||||||
|
*
|
||||||
|
* param handle: Indicator handle
|
||||||
|
* param shift: Bar index (0 = current, 1 = previous, etc.)
|
||||||
|
* return: Buffer value at shift, or 0.0 if retrieval fails
|
||||||
|
*/
|
||||||
|
double BufferUtils::get_buffer_value(int handle, int shift) {
|
||||||
|
double val[];
|
||||||
|
ArraySetAsSeries(val, true);
|
||||||
|
|
||||||
|
if (CopyBuffer(handle, 0, shift, 1, val) == 1)
|
||||||
|
return val[0];
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
class SymbolUtils {
|
||||||
|
|
||||||
|
public:
|
||||||
|
double adjusted_point(string symbol);
|
||||||
|
double get_bid_ask_price(string symbol, int price_side);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the point size for symbols with 3 or 5 digits (e.g. JPY pairs or fractional pips).
|
||||||
|
* Example: if symbol has 5 digits, 1 pip = 10 points.
|
||||||
|
*/
|
||||||
|
double SymbolUtils::adjusted_point(string symbol) {
|
||||||
|
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||||
|
int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1;
|
||||||
|
|
||||||
|
double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||||
|
return point_val * digits_adjust;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns either bid or ask price for a symbol, normalised to correct digits.
|
||||||
|
*
|
||||||
|
* param price_side: 1 for ASK, 2 for BID
|
||||||
|
*/
|
||||||
|
double SymbolUtils::get_bid_ask_price(string symbol, int price_side) {
|
||||||
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||||
|
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits);
|
||||||
|
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits);
|
||||||
|
|
||||||
|
if (price_side == 1) return ask;
|
||||||
|
if (price_side == 2) return bid;
|
||||||
|
|
||||||
|
return 0.0; // fallback if invalid side passed
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user