added in MyLibs from private Mt5 repo

This commit is contained in:
Matt Corcoran
2025-07-12 15:53:31 +02:00
parent 136522ef1c
commit 1222236ded
14 changed files with 3357 additions and 0 deletions
+282
View File
@@ -0,0 +1,282 @@
//+------------------------------------------------------------------+
//| CalculatePositionData.mqh |
//| xMattC |
//+------------------------------------------------------------------+
#property library
#include <Trade/Trade.mqh>
#include <MyLibs/TimeZones.mqh>
#include <MyLibs/Myfunctions.mqh>
class CalculatePositionData : public CObject{
protected:
CTrade trade;
TimeZones tz;
CPositionInfo position;
MyFunctions mf;
bool check_lots(double &lots, string symbol);
bool normalise_price(double price, double &normalizedPrice, string symbol);
// double adjusted_point(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 = mf.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 = mf.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 = mf.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 = mf.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;
}
+209
View File
@@ -0,0 +1,209 @@
//+------------------------------------------------------------------+
//| DrawdownControl.mqh |
//| xMattC |
//+------------------------------------------------------------------+
#property library
#include <Trade/Trade.mqh>
#include <MyLibs/MyFunctions.mqh>
class DrawdownControl : public CObject {
protected:
CTrade trade;
MyFunctions mf;
string data_file;
double daily_max_dd_per;
string daily_reset_time;
bool print_statments;
double acc_max_dd_per;
double equaty_control_high;
double equaty_control_low;
double daily_equity_start;
double daily_max_dd_target;
bool daily_dd_limit_reached;
bool write_global_var_data();
bool print_messages();
public:
void init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true);
bool determine_daily_dd_limit();
double lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20);
double lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor);
};
void DrawdownControl::init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true) {
data_file = inp_data_file;
acc_max_dd_per = inp_acc_max_dd_per;
daily_max_dd_per = inp_daily_max_dd_per;
daily_reset_time = inp_daily_reset_time;
print_statments = inp_print_statments;
// If no data file exisits, create one and set global vairiables:
if(FileIsExist(data_file) == false) {
daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY);
daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100));
daily_dd_limit_reached = false;
equaty_control_high = 9999999;
equaty_control_low = 0;
write_global_var_data();
}
// If file exisits read file:
if(FileIsExist(data_file) == true) {
int file_handle = FileOpen(data_file, FILE_READ | FILE_ANSI | FILE_TXT);
if(file_handle == INVALID_HANDLE) {
Print("Error opening file: ", data_file);
}
// If data file is older than 24h 10min create a new file and reset global vars:
long modifided_date = FileGetInteger(file_handle, FILE_MODIFY_DATE);
long time_delta = ((long)TimeCurrent() - modifided_date) / 60;
if(time_delta >= 1450) {
daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY);
daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100));
daily_dd_limit_reached = false;
equaty_control_high = equaty_control_high;
equaty_control_low = equaty_control_low;
write_global_var_data();
Print(data_file, " is older than 24h and 10min; global vars reset!");
}
// If data file is younger than 24h+10 min read data and set global vars:
else {
daily_equity_start = (double)FileReadString(file_handle, 0);
daily_max_dd_target = (double)FileReadString(file_handle, 1);
daily_dd_limit_reached = FileReadBool(file_handle);
equaty_control_high = (double)FileReadString(file_handle, 3);
equaty_control_low = (double)FileReadString(file_handle, 4);;
}
FileClose(file_handle);
}
print_messages();
}
bool DrawdownControl::determine_daily_dd_limit() {
// Reset max equity at the start of each day:
string ct = TimeToString(TimeCurrent(), TIME_MINUTES);
if(ct == daily_reset_time) {
daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY);
daily_max_dd_target = (daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)));
daily_dd_limit_reached = false;
write_global_var_data();
print_messages();
}
// If in drawdown close all positions and delete orders
if(daily_dd_limit_reached || AccountInfoDouble(ACCOUNT_EQUITY) <= daily_max_dd_target) {
if(daily_dd_limit_reached == false) {
daily_dd_limit_reached = true;
write_global_var_data();
print_messages();
}
for(int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
trade.PositionClose(ticket);
}
for(int i = OrdersTotal() - 1; i >= 0; i--) {
ulong ticket = OrderGetTicket(i);
trade.OrderDelete(ticket);
}
}
return daily_dd_limit_reached;
}
// Reduces lot size as account apporchaes max allowed drawdown limit.
double DrawdownControl::lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20) {
double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE));
double lot_factor;
// Interpolate to find lot factor between given min and max values.
if (account_value < acc_equity_start){
double acc_equity_min = acc_equity_start - (acc_equity_start * (acc_max_dd_per / 100));
double y1 = min_lot_factor;
double y2 = max_lot_factor;
double x1 = acc_equity_min;
double x = account_value;
double x2 = acc_equity_start;
lot_factor = y1 + (x - x1) * ((y2 - y1) / (x2 - x1));
}
else if(account_value >= acc_equity_start) {
if(dynm_lot_factor=true){
lot_factor = lot_correction_dynamic(dlf_trail_per, min_lot_factor, max_lot_factor);
}
else {
lot_factor = max_lot_factor;
}
}
return max_lot_factor;
}
double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor) {
double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE));
double trail_point = account_value - (account_value * (acc_dd_percent / 100));
if(equaty_control_low < trail_point){
equaty_control_low = trail_point;
}
if(equaty_control_high < account_value){
equaty_control_high = account_value;
}
if(account_value < equaty_control_low){
equaty_control_low = account_value;
equaty_control_high = account_value + (account_value * (acc_dd_percent / 100));
}
// back-up to file every hour:
if(mf.is_new_bar(_Symbol, PERIOD_H1) == true){
write_global_var_data();
}
// Linear interpolation:
double y1 = min_lot_factor;
double y2 = max_lot_factor;
double x1 = equaty_control_low;
double x = account_value;
double x2 = equaty_control_high;
double y = y1 + (x - x1) * ((y2 - y1) / (x2 - x1));
return y;
}
bool DrawdownControl::write_global_var_data() {
int file_handle = FileOpen(data_file, FILE_WRITE | FILE_ANSI | FILE_TXT);
FileWrite(file_handle, daily_equity_start);
FileWrite(file_handle, daily_max_dd_target);
FileWrite(file_handle, daily_dd_limit_reached);
FileClose(file_handle);
Print(data_file, " written");
return true;
}
bool DrawdownControl::print_messages() {
if(print_statments == true) {
Print("TimeCurrent(): ", TimeToString(TimeCurrent()));
Print("Daily Equity Start: ", (int)daily_equity_start);
Print("Current Equity: ", (int)AccountInfoDouble(ACCOUNT_EQUITY));
Print("Daily Drawdown Limit: ", (int)daily_max_dd_target, " (", daily_max_dd_per, "%) of DES");
Print("Daily Drawdown Limit Hit: ", daily_dd_limit_reached);
}
return true;
}
+216
View File
@@ -0,0 +1,216 @@
//+------------------------------------------------------------------+
//| MyFunctions.mqh |
//| xMattC |
//+------------------------------------------------------------------+
#property library
#include <Trade/Trade.mqh>
#include <MyLibs/TradingWindow.mqh>
#include <MyLibs/MyEnums.mqh>
class MyFunctions : public CObject{
protected:
CTrade trade;
TradingWindow tw;
datetime previousTime;
datetime bar_open_time;
public:
void draw_line(double value, string name,color clr);
bool check_indicator_handles(int &indicator_handles[]);
double adjusted_point(string symbol);
double get_bid_ask_price(string symbol, int price_side);
bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10");
bool trade_window(string t1, string t2, string time_zone, bool plot_range_inp=true);
bool in_test_period(MODE_SPLIT_DATA data_period);
void get_white_list(MULTI_SYM_MODE mode, string& DataArray[]);
};
void MyFunctions::get_white_list(MULTI_SYM_MODE mode, string& DataArray[]){
if(mode==MULTI_SYM_CHART){
string a[] = {_Symbol};
ArrayResize(DataArray, ArraySize(a));
for(int i = 0; i < ArraySize(DataArray); i++){
DataArray[i]=a[i];
}
}
if(mode==MULTI_SYM_FX_B5){
string a[] = {"EURUSD", "AUDNZD", "EURGBP", "AUDCAD", "CHFJPY"};
ArrayResize(DataArray, ArraySize(a));
for(int i = 0; i < ArraySize(DataArray); i++){
DataArray[i]=a[i];
}
}
if(mode==MULTI_SYM_FX_28){
string a[] = {"EURUSD","AUDNZD","AUDUSD","AUDJPY","EURCHF","EURGBP","EURJPY","GBPCHF","GBPJPY","GBPUSD","NZDUSD","USDCAD","USDCHF","USDJPY","CADJPY","EURAUD","CHFJPY","EURCAD","AUDCAD","AUDCHF","CADCHF","EURNZD","GBPAUD","GBPCAD","GBPNZD","NZDCAD","NZDCHF","NZDJPY",};
ArrayResize(DataArray, ArraySize(a));
for(int i = 0; i < ArraySize(DataArray); i++){
DataArray[i]=a[i];
}
}
}
bool MyFunctions::trade_window(string t1, string t2, string time_zone="Broker", bool plot_range_inp=true){
bool in_window = tw.define_window(t1, t2, time_zone, plot_range_inp);
return in_window;
}
//if(!mf.is_new_bar(symbol, PERIOD_D1, "00:06")){return;}
bool MyFunctions::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10"){
bar_open_time = iTime(symbol, time_frame, 0);
if(previousTime!=bar_open_time){
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;
}
//if(!mf.in_test_period(data_split_method){return;}
bool MyFunctions::in_test_period(MODE_SPLIT_DATA data_split_method){
string result[];
string string_tc = TimeToString(TimeCurrent());
ushort u_sep = StringGetCharacter(".",0);
int split_string = StringSplit(string_tc, u_sep, result);
bool odd_year = int(result[0]) % 2;
bool odd_month = int(result[1]) % 2;
// get week of the year. rough estimate can be late the first week of jan:
MqlDateTime dt;
TimeToStruct(TimeCurrent(),dt);
int iDay = (dt.day_of_week + 6 ) % 7 + 1; // convert day to standard index (1=Mon,...,7=Sun)
int iWeek = (dt.day_of_year - iDay + 10 ) / 7; // calculate standard week number
bool odd_week = iWeek % 2;
if(data_split_method==NO_SPLIT){
return true;
}
if(data_split_method==ODD_YEARS){
if (odd_year){
return true;
}
}
if(data_split_method==EVEN_YEARS){
if (!odd_year){
return true;
}
}
if(data_split_method==ODD_MONTHS){
if (odd_month){
return true;
}
}
if(data_split_method==EVEN_MONTHS){
if (!odd_month){
return true;
}
}
if(data_split_method==ODD_WEEKS){
if (odd_week){
return true;
}
}
if(data_split_method==EVEN_WEEKS){
if (!odd_week){
return true;
}
}
return false;
}
void MyFunctions::draw_line(double value, string name,color clr=clrBlack){
// EG:
// ArrayResize(bar,1000);
// ArraySetAsSeries(bar, true);
// CopyRates(symbol,PERIOD_CURRENT,1,1000,bar);
// double close = bar[0].close;
// draw_line(close,"CLOSE",clrBlue);
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();
}
double MyFunctions::adjusted_point(string symbol){
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
int digits_adjust=1;
if(symbol_digits==3 || symbol_digits==5){
digits_adjust=10;
}
double symbol_point_val = SymbolInfoDouble(symbol,SYMBOL_POINT);
double m_adjusted_point;
m_adjusted_point = symbol_point_val * digits_adjust;
return m_adjusted_point;
}
// price side - 1 for the ask price and 2 for the bid price
double MyFunctions::get_bid_ask_price(string symbol, int price_side){
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
ask = NormalizeDouble(ask, symbol_digits);
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
bid = NormalizeDouble(bid, symbol_digits);
double price = 0;
if(price_side==1){
price = ask;
}
else if(price_side==2){
price = bid;
}
return price;
}
+560
View File
@@ -0,0 +1,560 @@
//+------------------------------------------------------------------+
//| OrderManagement.mqh |
//| xMattC |
//+------------------------------------------------------------------+
#property library
#include <Trade/Trade.mqh>
#include <MyLibs/TimeZones.mqh>
#include <MyLibs/MyEnums.mqh>
#include <MyLibs/CalculatePositionData.mqh>
#include <Trade/PositionInfo.mqh>
#include <Trade/OrderInfo.mqh>
#include <MyLibs/Myfunctions.mqh>
class OrderManagment : public CObject{
protected:
CTrade trade;
TimeZones tz;
CalculatePositionData cpd;
CPositionInfo m_position;
COrderInfo m_order;
double stop_loss;
double take_profit;
ulong posTicket;
int time_difference;
int total_open_buy_orders;
int total_open_sell_orders;
double current_price;
int total_pos;
long position_open_time;
long first_allowed_close_time;
datetime current_bar_open_time;
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_nnfx_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_nnfx_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);
bool close_buy_orders(string symbol, bool buy_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number);
bool close_sell_orders(string symbol, bool sell_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number);
bool first_profitable_close_exit(string symbol, 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 tz, int delay_days, long magic_number);
int count_all_positions(string symbol, long magic_number);
int count_pending_orders(string symbol, ENUM_ORDER_TYPE pendingType, long magic);
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);
int count_open_positions(string symbol,int order_side, long magic_number);
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);
};
bool OrderManagment::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 == true){
current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side
total_open_buy_orders = count_open_positions(symbol, 1, magic_number);
if(total_open_buy_orders == 0){
stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period);
take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
double sl_distance = current_price-stop_loss;
double lots = cpd.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 OrderManagment::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 == true){
// if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;}
current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side
total_open_sell_orders = count_open_positions(symbol, 2, magic_number);
if(total_open_sell_orders == 0){
stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period);
take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
double sl_distance = stop_loss-current_price;
double lots = cpd.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 OrderManagment::open_nnfx_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 == true){
current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side
total_open_buy_orders = count_open_positions(symbol, 1, magic_number);
if(total_open_buy_orders == 0){
stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period);
take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
double sl_distance = current_price-stop_loss;
double lots = cpd.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var/2);
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);
trade.PositionOpen(symbol,ORDER_TYPE_BUY,lots,current_price,stop_loss,0,comment);
}
}
return true;
}
bool OrderManagment::open_nnfx_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 == true){
// if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;}
current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side
total_open_sell_orders = count_open_positions(symbol, 2, magic_number);
if(total_open_sell_orders == 0){
stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period);
take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
double sl_distance = stop_loss-current_price;
double lots = cpd.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);
trade.PositionOpen(symbol,ORDER_TYPE_SELL,lots,current_price,stop_loss,0,comment);
}
}
return true;
}
// some usfull comment here
bool OrderManagment::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 == true){
total_open_buy_orders = count_open_positions(symbol, 1, magic_number);
if(total_open_buy_orders == 0){
stop_loss = cpd.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period);
take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
double sl_distance = entry_price-stop_loss;
double lots = cpd.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 OrderManagment::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 == true){
total_open_sell_orders = count_open_positions(symbol, 2, magic_number);
if(total_open_sell_orders == 0){
stop_loss = cpd.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period);
take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
double sl_distance = stop_loss-entry_price;
double lots = cpd.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;
}
bool OrderManagment::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){
time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
if(condition){
trade.PositionClose(posTicket);
}
if(close_bars > 0){
if(time_difference >= close_bars){
trade.PositionClose(posTicket);
}
}
}
}
}
return true;
}
bool OrderManagment::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){
time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
if(condition){trade.PositionClose(posTicket);}
if(close_bars > 0){
if(time_difference >= close_bars){
trade.PositionClose(posTicket);
}
}
}
}
}
return true;
}
// order_side int must be 1 for BUY or 2 for SELL
int OrderManagment::count_open_positions(string symbol,int order_side, long magic_number){
int count = 0;
bool match = (PositionGetInteger(POSITION_MAGIC)==magic_number);
for(int i = PositionsTotal()-1; i >=0; i--){
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC)==magic_number){
// Count only Buy orders:
if(order_side == 1){
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
count = count + 1;
}
}
// Count only Sell orders:
if(order_side == 2){
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
count = count + 1;
}
}
}
}
return count;
}
int OrderManagment::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 = count + 1;
}
}
return count;
}
bool OrderManagment::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){
// datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker");
if(TimeCurrent()>= exit_time){
if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
trade.PositionClose(posTicket);
}
// Sell orders:
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
trade.PositionClose(posTicket);
}
}
}
}
}
}
return true;
}
bool OrderManagment::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long magic_number){
// om.daily_timed_profit_exit(_Symbol, PERIOD_CURRENT, "16:45", "17:00", "NY", 1, inp_magic);
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){
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); // shift 1 because 0 = live candle.
double trading_cost = cpd.calculate_trading_cost(symbol, posTicket);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
if(bar_close > (position_open_price + spread + trading_cost)){
trade.PositionClose(posTicket);
}
}
// Sell orders:
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
if(bar_close < position_open_price - spread - trading_cost){
trade.PositionClose(posTicket);
}
}
}
}
}
}
}
return true;
}
bool OrderManagment::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number){
// om.first_profitable_close_exit(_Symbol, PERIOD_CURRENT, inp_magic);
position_open_time = PositionGetInteger(POSITION_TIME);
first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period);
if((int)position_open_time>0){
if(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); // shift 1 because 0 = live candle.
double trading_cost = cpd.calculate_trading_cost(symbol, posTicket);
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
if(bar_close > (position_open_price + spread + trading_cost)){
trade.PositionClose(posTicket);
}
}
// Sell orders:
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
if(bar_close < position_open_price - spread - trading_cost){
trade.PositionClose(posTicket);
}
}
}
}
}
}
return true;
}
// e.g. int buy_stop_count = om.count_pending_orders(symbol, ORDER_TYPE_BUY_STOP, inp_magic);
// order types: ORDER_TYPE_BUY_LIMIT, ORDER_TYPE_SELL_LIMIT, ORDER_TYPE_BUY_STOP, ORDER_TYPE_SELL_STOP
int OrderManagment::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);
}
void OrderManagment::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 = SymbolInfoDouble(symbol, SYMBOL_ASK);
ask = NormalizeDouble(ask, symbol_digits);
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
bid = NormalizeDouble(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){
if(bid > position_open_price + be_trigger_points * symbol_point){
double sl = position_open_price + be_puffer * symbol_point;
sl = NormalizeDouble(sl, symbol_digits);
if(sl > position_sl){
if(trade.PositionModify(ticket, sl, position_tp)){
Print("-----------------------------------Stop moved to break even");
}
}
}
}
else if(position_type == POSITION_TYPE_SELL){
if(ask < position_open_price - be_trigger_points * symbol_point){
double sl = position_open_price - be_puffer * symbol_point;
sl = NormalizeDouble(sl, symbol_digits);
if(sl < position_sl){
if(trade.PositionModify(ticket, sl, position_tp)){
Print("-----------------------------------Stop moved to break even");
}
}
}
}
}
}
}
}
}
void OrderManagment::nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number){
MyFunctions mf3;
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 = SymbolInfoDouble(symbol, SYMBOL_ASK);
ask = NormalizeDouble(ask, symbol_digits);
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
bid = NormalizeDouble(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){
if(bid > position_open_price + (atr_value * tp_var)){
double sl = bid - (atr_value * sl_var);
sl = NormalizeDouble(sl, symbol_digits);
if(sl > (position_sl + (atr_value * 0.5))){
if(trade.PositionModify(ticket, sl, position_tp)){
}
}
}
}
else if(position_type == POSITION_TYPE_SELL){
if(ask < position_open_price - (atr_value * tp_var)){
double sl = ask + (atr_value * sl_var);
sl = NormalizeDouble(sl, symbol_digits);
if(sl < (position_sl + (atr_value * 0.5))){
if(trade.PositionModify(ticket, sl, position_tp)){
}
}
}
}
}
}
}
}
double OrderManagment::sl_specified_value_switch(string _sl_mode, double _inp_sl_var, double value){
double sl = 0;
if(_sl_mode=="SL_SPECIFIED_VALUE"){sl = value;}
if(_sl_mode!="SL_SPECIFIED_VALUE"){sl = _inp_sl_var;}
return sl;
}
double OrderManagment::tp_specified_value_switch(string _tp_mode, double _inp_tp_var, double value){
double tp = 0;
if(_tp_mode=="SL_SPECIFIED_VALUE"){tp = value;}
if(_tp_mode!="SL_SPECIFIED_VALUE"){tp = _inp_tp_var;}
return tp;
}
+148
View File
@@ -0,0 +1,148 @@
//+------------------------------------------------------------------+
//| TimeZones.mqh |
//| xMattC |
//+------------------------------------------------------------------+
#property library
#include <Trade/Trade.mqh>
#include <MyLibs/DealingWithTime.mqh>
class TimeZones: public CObject{
protected:
string dt_s;
int len;
string dt_string;
datetime tC, tGMT, tNY, tLon, tFfm, tMosc, tSyd, tTok;
datetime tz_time;
string tz_date;
datetime time_start;
datetime time_end;
bool is_time;
datetime tGIVEN;
datetime tREQ;
datetime tzt;
datetime tz_req;
double ny_daily_close_protected(string symbol, int shift_days, bool print_data=false);
double required_close;
public:
string get_date_string_from_datetime(datetime dt);
datetime get_timezone_time(string time_zone, bool print_time);
datetime timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required);
double ny_daily_close(string symbol, int shift_days, bool print_data=false);
};
string TimeZones::get_date_string_from_datetime(datetime dt){
dt_s = TimeToString(dt);
len = StringLen(dt_s);
dt_string = StringSubstr(dt_s, 0, len-5);
return dt_string;
}
datetime TimeZones::get_timezone_time(string time_zone, bool print_time){
// https://www.mql5.com/en/code/45287
// https://www.mql5.com/en/articles/9926
// https://www.mql5.com/en/articles/9929
checkTimeOffset(TimeCurrent()); // check changes of DST
// cto();
tC = TimeCurrent();
tGMT = TimeCurrent() + OffsetBroker.actOffset; // GMT
tNY = tGMT - (NYShift+DST_USD); // time in New York (EST)
tLon = tGMT - (LondonShift+DST_EUR); // time in London
tFfm = tGMT - (FfmShift+DST_EUR); // time in Frankfurt
tSyd = tGMT - (SidneyShift+DST_AUD); // time in Sidney
tMosc = tGMT - (MoskwaShift+DST_RUS); // time in Moscow
tTok = tGMT - (TokyoShift); // time in Tokyo - no DST
if(print_time==true){
Print("----------------------------------");
Print("Broker: ", tC);
Print("GMT: ", tGMT);
Print("time in New York: ", tNY);
Print("time in London: ", tLon);
Print("time in Frankfurt: ", tFfm);
Print("time in Sidney: ", tSyd);
Print("time in Moscow: ", tMosc);
Print("time in Tokyo: ", tTok);
}
if(time_zone=="NY"){return tNY;}
if(time_zone=="Lon"){return tLon;}
if(time_zone=="Ffm"){return tFfm;}
if(time_zone=="Syd"){return tSyd;}
if(time_zone=="Mosc"){return tMosc;}
if(time_zone=="Tok"){return tTok;}
return NULL;
}
datetime TimeZones::timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required){
// https://www.mql5.com/en/code/45287
// https://www.mql5.com/en/articles/9926
// https://www.mql5.com/en/articles/9929
tGIVEN = time_given; //StringToTime(time_given);
checkTimeOffset(tGIVEN); // check changes of DST
// Get GMT:
if(time_zone_known=="GMT" ){tGMT = tGIVEN;}
if(time_zone_known=="Broker" ){tGMT = tGIVEN + OffsetBroker.actOffset;}
if(time_zone_known=="NY" ){tGMT = tGIVEN + (NYShift+DST_USD);}
if(time_zone_known=="Lon" ){tGMT = tGIVEN + (LondonShift+DST_EUR);}
if(time_zone_known=="Ffm" ){tGMT = tGIVEN + (FfmShift+DST_EUR);}
if(time_zone_known=="Syd" ){tGMT = tGIVEN + (SidneyShift+DST_AUD);}
if(time_zone_known=="Mosc" ){tGMT = tGIVEN + (MoskwaShift+DST_RUS);}
if(time_zone_known=="Tok" ){tGMT = tGIVEN + (TokyoShift);}
// define the required time:
tREQ = NULL;
if(time_zone_required=="GMT" ){tREQ = tGMT;}
if(time_zone_required=="Broker" ){tREQ = tGMT - OffsetBroker.actOffset;}
if(time_zone_required=="NY" ){tREQ = tGMT - (NYShift+DST_USD);}
if(time_zone_required=="Lon" ){tREQ = tGMT - (LondonShift+DST_EUR);}
if(time_zone_required=="Ffm" ){tREQ = tGMT - (FfmShift+DST_EUR);}
if(time_zone_required=="Syd" ){tREQ = tGMT - (SidneyShift+DST_AUD) ;}
if(time_zone_required=="Mosc" ){tREQ = tGMT - (MoskwaShift+DST_RUS);}
if(time_zone_required=="Tok" ){tREQ = tGMT - (TokyoShift);}
return tREQ;
}
// Calculte NY close time:
double TimeZones::ny_daily_close(string symbol, int shift_days, bool print_data=false){
required_close = ny_daily_close_protected(symbol, shift_days, print_data);
return required_close;
}
double TimeZones::ny_daily_close_protected(string symbol, int shift_days, bool print_data=false){
// Get the brokers times for when NY openend today and tomorrow:
datetime time_5pm = iTime(symbol, PERIOD_D1 , 0) - (PeriodSeconds(PERIOD_H1) * 7);
datetime ny_close_in_brokers_time = timezone_conversions("NY", time_5pm, "Broker");
datetime ny_close_time = ny_close_in_brokers_time + PeriodSeconds(PERIOD_D1); // ny close tomorrow
if(TimeCurrent()<ny_close_time){
ny_close_time = ny_close_time - PeriodSeconds(PERIOD_D1); // ny close today
}
// Get the number of hours since NY closed:
int shift = iBarShift(symbol, PERIOD_H1, ny_close_time, false) + 1;
shift = shift + (24 * (shift_days - 1)); // shift days if required:
double ny_close = iClose(symbol,PERIOD_H1, shift);
double br_close = iClose(symbol,PERIOD_H1, 1);
if(print_data==true){
Print("shift ",shift);
Print("time_5pm ",time_5pm);
Print("ny_close_in_brokers_time ",ny_close_in_brokers_time);
Print("ny_close_time ",ny_close_time);
Print("ny_close ", ny_close);
Print("br_close ",br_close);
}
return ny_close;
}
@@ -0,0 +1 @@
.8448951187990853
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
import os
import logging
from datetime import datetime, timedelta
from time import sleep
import pandas as pd
from binance import Client
from forex_python.converter import CurrencyRates
import telegram
import schedule
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
# Environment variables for sensitive information
API_KEY = os.getenv("BINANCE_API_KEY")
API_SECRET = os.getenv("BINANCE_API_SECRET")
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
BOT_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
# Constants
MAX_TRADES = 5
RESET_STAKE_AMOUNT = 10 # minutes
DATA_DIR = "data"
# Ensure data directory exists
os.makedirs(DATA_DIR, exist_ok=True)
def telegram_send_message(bot_token, bot_chat_id, message):
"""
Send a message via Telegram bot.
"""
try:
bot = telegram.Bot(token=bot_token)
bot.send_message(chat_id=bot_chat_id, text=message)
except Exception as e:
logging.error(f"Failed to send Telegram message: {e}")
def update_df(gbp, usd, btc, exchange_rate):
"""
Update the HDF5 file with new balance data.
"""
try:
file_path = os.path.join(DATA_DIR, "balances.h5")
time_now = pd.to_datetime(datetime.now().replace(microsecond=0))
new_data = pd.DataFrame({
"date_time": [time_now],
"£": [gbp],
"$": [usd],
"BTC": [btc],
"Ex-rate": [exchange_rate],
})
with pd.HDFStore(file_path) as store:
if "df" in store:
df = store["df"]
df = pd.concat([df, new_data]).drop_duplicates(subset="date_time", keep="first")
else:
df = new_data.set_index("date_time")
store["df"] = df
logging.info("Updated DataFrame successfully.")
except Exception as e:
logging.error(f"Could not update DataFrame: {e}")
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not update DataFrame.")
def trim_df():
"""
Trim the DataFrame to the last 24 hours.
"""
try:
file_path = os.path.join(DATA_DIR, "balances.h5")
cut_before = datetime.now() - timedelta(hours=25)
with pd.HDFStore(file_path) as store:
if "df" in store:
df = store["df"]
df = df[df.index >= cut_before]
store["df"] = df
logging.info("Trimmed DataFrame to the last 24 hours.")
except Exception as e:
logging.error(f"Could not trim DataFrame: {e}")
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not trim DataFrame.")
def update_exchange_rate():
"""
Fetch the latest USD to GBP exchange rate and store it.
"""
cr = CurrencyRates()
file_path = os.path.join(DATA_DIR, "USDGBP_exchange_rate.txt")
try:
exchange_rate = cr.get_rate("USD", "GBP")
with open(file_path, "w") as f:
f.write(str(exchange_rate))
logging.info("Updated USD to GBP exchange rate.")
except Exception as e:
logging.error(f"Could not update exchange rate: {e}")
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not update exchange rate.")
def convert_usd_to_gbp(usd):
"""
Convert USD to GBP using the stored exchange rate.
"""
file_path = os.path.join(DATA_DIR, "USDGBP_exchange_rate.txt")
try:
with open(file_path, "r") as f:
exchange_rate = float(f.read())
gbp = usd * exchange_rate
return gbp, exchange_rate
except Exception as e:
logging.error(f"Could not convert USD to GBP: {e}")
return usd, 1.0 # Fallback to 1:1 conversion
def get_balance():
"""
Retrieve account balances from Binance and update the stake amount.
"""
try:
client = Client(API_KEY, API_SECRET)
account_info = client.get_account()
balances = account_info["balances"]
usdt = 0.0
for balance in balances:
asset = balance["asset"]
free = float(balance["free"])
locked = float(balance["locked"])
total = free + locked
if total > 0:
if asset == "USDT":
usdt += total
else:
try:
price = float(client.get_symbol_ticker(symbol=f"{asset}USDT")["price"])
usdt += total * price
except Exception:
pass
btc_price = float(client.get_symbol_ticker(symbol="BTCUSDT")["price"])
btc = usdt / btc_price
gbp, exchange_rate = convert_usd_to_gbp(usdt)
stake_amount = round(usdt / MAX_TRADES)
with open(os.path.join(DATA_DIR, "stake_amount.txt"), "w") as f:
f.write(str(stake_amount))
update_df(gbp, usdt, btc, exchange_rate)
except Exception as e:
logging.error(f"Could not fetch balances: {e}")
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, "Could not fetch Binance balances.")
def run_code():
"""
Main function to schedule tasks and run the bot.
"""
update_exchange_rate()
get_balance()
schedule.every(RESET_STAKE_AMOUNT).minutes.do(get_balance)
schedule.every().day.at("11:45").do(update_exchange_rate)
schedule.every(4).hours.do(trim_df)
while True:
schedule.run_pending()
sleep(1)
if __name__ == "__main__":
run_code()
@@ -0,0 +1,263 @@
#!/usr/bin/env python3
import os
import time
import shutil
import warnings
import pandas as pd
import matplotlib.pyplot as plt # pip install matplotlib
import matplotlib.dates as mdates
from datetime import datetime, timedelta, date
import telegram
import schedule # pip install schedule
warnings.simplefilter(action='ignore', category=FutureWarning)
# Constants
LONG_PLOT_DAYS = -1 # 60 # -1 all days in df
LONG_PLOT_CURRENCY = '£'
PLOT_CURRENCY = '$'
# Environment variables for sensitive information
API_KEY = os.getenv("BINANCE_API_KEY")
API_SECRET = os.getenv("BINANCE_API_SECRET")
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
BOT_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
date_today = date.today()
TODAY = date_today.strftime("%Y_%m_%d")
def telegram_send_image(bot_token, bot_chat_id, image_path):
"""Send an image to the specified Telegram chat."""
bot = telegram.Bot(token=bot_token)
with open(image_path, 'rb') as photo:
bot.send_photo(chat_id=bot_chat_id, photo=photo)
return ()
def telegram_send_message(bot_token, bot_chat_id, message):
"""Send a message to the specified Telegram chat."""
bot = telegram.Bot(token=bot_token)
bot.send_message(chat_id=bot_chat_id, text=message)
return ()
def plot_and_send_image(df, currency, plot_title, file_name):
"""Generate plot and send it as an image to Telegram."""
plt.rcParams.update({'font.size': 14, 'font.family': 'STIXGeneral', 'mathtext.fontset': 'stix'})
fig, axs = plt.subplots(figsize=(7, 4))
axs.xaxis.set_major_formatter(mdates.DateFormatter("%d %b"))
df[currency].plot.line(ax=axs, color="darkgreen", linewidth=1.50)
delta_y = int(df[currency].max()) - int(df[currency].min())
y_min = int(df[currency].min()) - (delta_y * 0.05)
y_max = int(df[currency].max()) + (delta_y * 0.05)
x_max = datetime.now()
x_min = datetime.now() - timedelta(days=len(df))
axs.set_title(plot_title)
axs.set_ylim(y_min, y_max)
axs.set_xlim(x_min, x_max)
axs.set_ylabel("")
axs.set_xlabel("")
axs.grid(color='grey', alpha=0.5, linestyle='dashed', linewidth=0.5)
axs.yaxis.set_major_formatter(f"{currency} {{x:1.0f}}")
plt.savefig(file_name)
plt.cla()
plt.close(fig)
# Send image to Telegram
telegram_send_image(BOT_TOKEN, BOT_CHAT_ID, file_name)
return ()
def plot_long(period=LONG_PLOT_DAYS, currency=LONG_PLOT_CURRENCY):
"""Plot long-term data."""
try:
if os.path.exists('balances_24h.h5'):
balances_24h = pd.HDFStore('balances_24h.h5')
df = balances_24h['df_24h'].iloc[1:, :]
balances_24h.close()
if period == -1:
no_of_days = len(df)
else:
no_of_days = period
df = df.tail(no_of_days)
plot_and_send_image(df, currency, f"{no_of_days} days plot", "plot_long.png")
else:
message = "No balances_24h.h5 file"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
except Exception:
message = "Could not generate long plot"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
def plot_30days(currency=PLOT_CURRENCY):
"""Plot 30 days data."""
try:
if os.path.exists('balances_4h.h5'):
balances_4h = pd.HDFStore('balances_4h.h5')
df = balances_4h['df_4h'].iloc[1:, :]
balances_4h.close()
df = df.tail(180)
plot_and_send_image(df, currency, "30 Day Balances", "30_days.png")
else:
message = "No balances_4h.h5 file"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
except Exception:
message = "Could not generate 30 day plot"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
def plot_7days(currency=PLOT_CURRENCY):
"""Plot 7 days data."""
try:
if os.path.exists('balances_1h.h5'):
balances_1h = pd.HDFStore('balances_1h.h5')
df = balances_1h['df_1h']
end_date = datetime.now().replace(microsecond=0)
cut_before_date = end_date - timedelta(days=7)
df = df.loc[df.index >= cut_before_date]
plot_and_send_image(df, currency, "7 Day Balances", "7_days.png")
else:
message = "No balances_1h.h5 file"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
except Exception:
message = "Could not generate 7 day plot"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
def plot_24h(currency=PLOT_CURRENCY):
"""Plot 24 hours data."""
try:
if os.path.exists('balances.h5'):
balances = pd.HDFStore('balances.h5')
df = balances['df'].iloc[1:, :]
df['date_time'] = pd.to_datetime(df['date_time'])
balances.close()
df = df.set_index('date_time')
plot_and_send_image(df, currency, TODAY, "24_hour.png")
else:
message = "No balances.h5 file"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
except Exception:
message = "Could not generate 24h plot"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
def send_plots():
"""Send all generated plots to Telegram."""
try:
balances = pd.HDFStore('balances.h5')
df = balances['df']
balances.close()
GBP = df['£'].iloc[-1]
USDT = df['$'].iloc[-1]
BTC = df['BTC'].iloc[-1]
message = f"Balance:\n GBP £ {round(GBP, 2)}\n USD $ {round(USDT, 2)}\n BTC {round(BTC, 6)}"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
# Send all plots
for file_name in ["plot_long.png", "30_days.png", "7_days.png", "24_hour.png"]:
if os.path.exists(file_name):
telegram_send_image(BOT_TOKEN, BOT_CHAT_ID, file_name)
else:
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, f"Could not find {file_name}")
print(f"{datetime.now().replace(microsecond=0)} - Sent plots to Telegram.")
except Exception:
message = "Could not send plots."
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
def resample_data():
"""Resample data to 1h, 4h, and 24h."""
try:
if not os.path.exists("balances_1h.h5"):
balances = pd.HDFStore('balances.h5')
df = balances['df']
df['date_time'] = pd.to_datetime(df['date_time'])
df = df.set_index('date_time')
balances.close()
# Resample to 1h
data_1h = df.resample("H").mean()
balances_1h = pd.HDFStore('balances_1h.h5')
balances_1h['df_1h'] = data_1h
balances_1h.close()
if not os.path.exists("balances_4h.h5"):
balances_1h = pd.HDFStore('balances_1h.h5')
df_1h = balances_1h['df_1h']
balances_1h.close()
# Resample to 4h
data_4h = df_1h.resample("4H").mean()
balances_4h = pd.HDFStore('balances_4h.h5')
balances_4h['df_4h'] = data_4h
balances_4h.close()
if not os.path.exists("balances_24h.h5"):
balances_4h = pd.HDFStore('balances_4h.h5')
df_4h = balances_4h['df_4h']
balances_4h.close()
# Resample to 24h
data_24h = df_4h.resample("24H").mean()
balances_24h = pd.HDFStore('balances_24h.h5')
balances_24h['df_24h'] = data_24h
balances_24h.close()
except Exception:
message = "Error in resampling data"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
def delete_old_files():
"""Delete old files in the directory."""
try:
directory = "path_to_your_directory"
for file_name in os.listdir(directory):
file_path = os.path.join(directory, file_name)
if os.path.getmtime(file_path) < time.time() - 7 * 86400:
os.remove(file_path)
except Exception:
message = "Error in deleting old files"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
def archive_data():
"""Archive old data to a zip file."""
try:
archive_name = f"archive_data_{TODAY}.zip"
if not os.path.exists('archive_data'):
os.makedirs('archive_data')
shutil.make_archive(f'archive_data/{archive_name}', 'zip', 'path_to_your_directory')
except Exception:
message = "Error in archiving data"
telegram_send_message(BOT_TOKEN, BOT_CHAT_ID, message)
return ()
# Scheduling tasks
schedule.every().day.at("00:00").do(archive_data)
schedule.every().day.at("01:00").do(delete_old_files)
schedule.every().day.at("02:00").do(resample_data)
schedule.every().day.at("02:30").do(plot_long)
schedule.every().day.at("03:00").do(plot_30days)
schedule.every().day.at("03:30").do(plot_7days)
schedule.every().day.at("04:00").do(plot_24h)
schedule.every().day.at("05:00").do(send_plots)
# Main loop
while True:
schedule.run_pending()
time.sleep(1)
@@ -0,0 +1 @@
4164
@@ -0,0 +1,699 @@
"""
MattC - 2025
This code is a working prototype and is intended for initial testing and development purposes. Some Python
standards, including but not limited to PEP 8 compliance, error handling, and code optimization, are yet to be fully
implemented. Further refactoring and enhancements are planned to improve readability, maintainability,
and efficiency.
"""
import os
from os import walk
import json
from datetime import datetime, timedelta
import pandas as pd
import shutil
from pathlib import Path
import logging
import matplotlib.pyplot as plt # pip install matplotlib
import matplotlib.dates as mdates
import warnings
import urllib
from urllib.request import urlopen
import time
warnings.simplefilter(action='ignore', category=FutureWarning)
logger = logging.getLogger(__name__)
# TODO sort out download data
class WalkForward(object):
def __init__(self, strategy_path, strategy, config, output_dir, wf_start, wf_finish, anchored_start, min_trades,
in_sample_days, out_sample_days, loss_function, cpu, epochs, wallet="2500", fee="0.002",
pre_live=False, re_opt=False, re_opt_t="5m 1h 1d"):
self.in_sample_days = in_sample_days
self.out_sample_days = out_sample_days
self.loss_function = loss_function
self.re_opt = re_opt
self.output_dir = self.create_run_dir(output_dir)
self.config = self.copy_input_config(config)
self.strategy_path = strategy_path
self.strategy_name = strategy
self.strategy = self.copy_input_strategy(strategy_path, self.strategy_name)
self.wf_start = wf_start
self.wf_finish = wf_finish
self.anchored_start = anchored_start
self.is_start = self.in_sample_start()
self.min_trades = min_trades
self.cpu = cpu
self.epochs = epochs
self.wallet = wallet
self.fee = fee
self.pre_live = pre_live
self.re_opt_t = re_opt_t
self.start_log()
def run_walk_forward(self):
stages = self.generate_wf_stages()
no_stages = len(stages)
start_wallet = self.wallet
start_time = datetime.now()
logger.info(f'Running Walk-forward for {no_stages} stages')
for count, value in enumerate(stages, 1):
stage_start_time = datetime.now()
logger.info(f'{"-" * 79}')
hyperopt_time = value[0]
backtest_time = value[1]
full_time_period = f"{hyperopt_time.split('-')[0]}-{backtest_time.split('-')[1]}"
logger.info(f'Walk-forward optimization for stage {count} of {no_stages}')
stage_dir = self.create_stage_dir(self.output_dir, count, full_time_period)
# Run Hyperopt and process required data
logger.info(f'Hyperopting for {hyperopt_time}')
cpu = self.set_cpu(hyperopt_time)
self.run_hyperopt(hyperopt_time, stage_dir, self.epochs, cpu)
hy_start = datetime.strptime(hyperopt_time.split('-')[0], "%Y%m%d")
hy_finish = datetime.strptime(hyperopt_time.split('-')[1], "%Y%m%d")
hy_delta = hy_start - hy_finish
hy_days = int(hy_delta.days)
logger.info(f'Running Hyperopt Backtest for period {hyperopt_time} ({hy_days} days)')
result_op, bt_file_op = self.run_backtest(hyperopt_time, "op_bt", start_wallet, stage_dir)
self.save_bt_file_data(stage_dir, bt_file_op, "op_bt")
df_op = self.update_df(result_op, "op_bt", count, hyperopt_time, start_wallet)
# Walk forward testing constant starting wallet:
logger.info(f'Running WF Backtest for period {backtest_time} ({self.out_sample_days} days)')
result_bt, bt_file_bt = self.run_backtest(backtest_time, "wf_bt", start_wallet, stage_dir)
self.save_bt_file_data(stage_dir, bt_file_bt, "wf_bt")
df_wf = self.update_df(result_bt, "wf_bt", count, backtest_time, start_wallet)
plot_equity_curve(df_wf, self.output_dir, save_fig=True)
self.combine_data(hyperopt_time, df_op, df_wf)
stage_run_time = str(datetime.now() - stage_start_time)
logger.info(f'Stage {count} walk-forward analysis Duration: {stage_run_time.split(".")[0]}')
if self.pre_live:
self.pre_live_optimise()
if self.re_opt:
self.re_optimise()
run_time = str(datetime.now() - start_time)
logger.info(f'Total walk-forward analysis Duration: {run_time.split(".")[0]}')
def re_optimise(self):
# Download data
# time_periods = "5m 15m 1h 4h 12h 1d"
time_periods = self.re_opt_t
self.download_data(time_periods)
end_date = datetime.strptime(self.wf_finish, "%Y%m%d")
start_date = end_date - timedelta(int(self.in_sample_days))
t1 = start_date.strftime("%Y%m%d")
t2 = end_date.strftime("%Y%m%d")
hyperopt_time = t1 + "-" + t2
full_time_period = f"{hyperopt_time}"
stage_dir = self.create_stage_dir(self.output_dir, "re_optimise", full_time_period)
epochs = f"{int(self.epochs)}"
self.run_hyperopt(hyperopt_time, stage_dir, epochs, self.cpu)
logger.info(f'Running Hyperopt Backtest for period {hyperopt_time} ({self.in_sample_days} days)')
_ = self.run_backtest(hyperopt_time, "re_optimise", self.wallet, stage_dir)
def save_bt_file_data(self, stage_dir, bt_file_op, file_id):
with open(f'{stage_dir}/{bt_file_op}') as f:
data1 = json.load(f)
df1 = pd.DataFrame.from_dict(data1["strategy"][self.strategy_name]["results_per_pair"])
csv_filepath = f"{stage_dir}/{file_id}_bt_results.csv"
df1.to_csv(csv_filepath)
def update_df(self, result, file_id, wf_stage, time_range, start_wallet):
h5_string = f"{self.output_dir}/{file_id}.h5"
if not os.path.exists(h5_string):
cols = ["profit_mean", "profit_mean_pct", "profit_sum", "profit_sum_pct", "profit_total_abs",
"profit_total_pct" "profit_total", "wins", "draws", "losses", "wf-stage", "bt_time_period",
"start-balance", "final-balance", "%_profit_pa", "acc-start", "acc-finish"]
df = pd.DataFrame(columns=cols)
df.to_hdf(h5_string, 'data')
df = pd.read_hdf(h5_string, 'data')
if file_id == "op_bt":
is_start = datetime.strptime(time_range.split('-')[0], "%Y%m%d")
is_finish = datetime.strptime(time_range.split('-')[1], "%Y%m%d")
delta = is_start - is_finish
sample_days = int(delta.days)
else:
sample_days = self.out_sample_days
data = result[0]
data.pop('key')
data["%_profit_pa"] = data["profit_total_pct"] / float(sample_days) * 365 # percent profit year
data["wf-stage"] = wf_stage
data["start-balance"] = start_wallet
data["final-balance"] = float(start_wallet) + float(data["profit_total_abs"])
data["bt_time_period"] = time_range
if len(df) == 0:
data["acc-start"] = float(self.wallet)
else:
data["acc-start"] = df["acc-finish"].iloc[-1]
data["acc-finish"] = (data["acc-start"] * data["profit_total_pct"] / 100) + data["acc-start"]
df2 = pd.DataFrame(data, index=[0])
df_new = df.copy()
df_new = df_new.append([df2], ignore_index=True)
csv_filepath = f"{self.output_dir}/{file_id}.csv"
df_new.to_csv(csv_filepath)
df_new.to_hdf(h5_string, 'data')
return df_new
def pre_live_optimise(self):
end_date = datetime.strptime(self.wf_finish, "%Y%m%d")
start_date = end_date - timedelta(int(self.in_sample_days))
t1 = start_date.strftime("%Y%m%d")
t2 = end_date.strftime("%Y%m%d")
hyperopt_time = t1 + "-" + t2
full_time_period = f"{hyperopt_time}"
stage_dir = self.create_stage_dir(self.output_dir, "pre_live", full_time_period)
epochs = f"{int(self.epochs) * 2}"
self.run_hyperopt(hyperopt_time, stage_dir, epochs, self.cpu)
logger.info(f'Running Hyperopt Backtest for period {hyperopt_time} ({self.in_sample_days} days)')
result_op = self.run_backtest(hyperopt_time, "pre_live", self.wallet, stage_dir)
def combine_data(self, hyp_time_range, df_op, df_wf):
is_start = datetime.strptime(hyp_time_range.split('-')[0], "%Y%m%d")
is_finish = datetime.strptime(hyp_time_range.split('-')[1], "%Y%m%d")
delta = is_start - is_finish
days_in = int(delta.days)
days_out = int(self.out_sample_days)
try:
a = pd.Series(df_op["profit_mean_pct"], name='op_profit_av')
b = pd.Series(df_wf["profit_mean_pct"], name='wf_profit_av')
c = pd.Series(df_op["wins"] / df_op["losses"], name='op_wl%')
d = pd.Series(df_wf["wins"] / df_wf["losses"], name='wf_wl%')
e = pd.Series(df_op['trades'].apply(lambda x: x / days_in), name='op_trades_per_day')
f = pd.Series(df_wf['trades'].apply(lambda x: x / days_out), name='wf_trades_per_day')
g = pd.Series(df_op["profit_total"].apply(lambda x: (x / days_in) * 365), name='op_ppa')
h = pd.Series(df_wf["profit_total"].apply(lambda x: (x / days_out) * 365), name='wf_ppa')
i = pd.Series(df_op["%_profit_pa"], name='op_%ppa')
j = pd.Series(df_wf["%_profit_pa"], name='wf_%ppa')
df_combined = pd.concat([a, b, c, d, e, f, g, h, i, j], axis=1)
filepath = f"{self.output_dir}/combined.csv"
df_combined.to_csv(filepath)
except:
pass
return
def generate_wf_stages(self):
is_start = datetime.strptime(self.is_start, "%Y%m%d")
oos_start = datetime.strptime(self.wf_start, "%Y%m%d")
end_date = datetime.strptime(self.wf_finish, "%Y%m%d")
oos_end = oos_start + timedelta(int(self.out_sample_days))
stages = []
while True:
t1 = is_start.strftime("%Y%m%d")
t2 = oos_start.strftime("%Y%m%d")
t3 = oos_end.strftime("%Y%m%d")
# define stage:
insample_timframe = t1 + "-" + t2
outsample_timframe = t2 + "-" + t3
stage = [insample_timframe, outsample_timframe]
stages.append(stage)
oos_start = oos_start + timedelta(int(self.out_sample_days))
oos_end = oos_start + timedelta(int(self.out_sample_days))
if not self.in_sample_days == "anchored":
is_start = is_start + timedelta(int(self.out_sample_days))
if oos_end > end_date:
break
return stages
def run_hyperopt(self, time_range, stage_dir, epochs, cpu):
"""
:param stage_number:
:param time_range:
:param epochs:
:param loss_function: SortinoHyperOptLoss,
:param fee:
:param cpu:
:return:
"""
self.wait_for_internet_connection()
start_time = datetime.now()
os.system(
"freqtrade hyperopt" +
" --min-trades " + self.min_trades +
" -j " + cpu +
" -e " + epochs +
" --spaces buy " +
" --fee " + self.fee +
" --logfile " + stage_dir + "/op_log" +
" --timerange " + time_range +
" --hyperopt-loss " + self.loss_function +
" --strategy " + self.strategy +
" --strategy-path " + self.output_dir +
" --config " + self.config +
" --dry-run-wallet " + self.wallet
)
self.wait_for_internet_connection()
os.system(
"freqtrade hyperopt-list" +
" --no-details " +
" --export-csv " + stage_dir + "/op.csv"
)
# # Copy the best optimisation results to "stage output directory":
src = Path(f"{self.output_dir}/{self.strategy}.json")
dst = f"{stage_dir}/op_result.json"
shutil.copyfile(str(src), dst)
run_time = str(datetime.now() - start_time)
logger.info(f'Hyperopt Duration: {run_time.split(".")[0]}')
with open(dst) as f:
data = json.load(f)
results = data["params"]["buy"]
for i in results:
logger.info(f'Hyperopt result: {i}: {results[i]}')
def run_backtest(self, time_range, file_id, wallet, stage_dir):
self.wait_for_internet_connection()
start_time = datetime.now()
os.system(
"freqtrade backtesting" +
" --export trades " +
" --fee " + self.fee +
f" --logfile {stage_dir}/{file_id}_log.txt"
" --timerange " + time_range +
" --strategy " + self.strategy +
" --strategy-path " + self.output_dir +
" --config " + self.config +
" --dry-run-wallet " + wallet +
f" --export-filename {stage_dir}/{file_id}_result.json"
)
result, bt_file = self.get_backtest_data(stage_dir, file_id)
run_time = str(datetime.now() - start_time)
if not file_id == "wf_acc_bt":
logger.info(f'Backtest Duration: {run_time.split(".")[0]}')
self.log_bt_results(time_range, result, wallet, file_id)
self.plot_bt_profit(time_range, stage_dir, bt_file, file_id)
return result, bt_file
def download_data(self, time_periods, days="4000"):
self.wait_for_internet_connection()
logger.info(f'downloading data')
os.system(
"freqtrade download-data" +
" -t " + time_periods +
" --exchange binance " +
" --pairs .*/USDT " +
" --new-pairs-days " + days +
" --include-inactive-pairs "
)
logger.info(f'finished downloading data')
return
@staticmethod
def wait_for_internet_connection():
start_time = datetime.now()
switch = True
while True:
try:
urlopen('https://www.google.com', timeout=1)
if not switch:
offline_time = str(datetime.now() - start_time)
logger.warning(f"Disconnected time: {offline_time.split('.')[0]}")
logger.warning("#############################")
return
except urllib.error.URLError:
if switch:
logger.warning("#############################")
logger.warning("NO INTERNET")
switch = False
time.sleep(2)
pass
def log_bt_results(self, time_range, result, wallet, file_id):
data = result[0].copy()
final_balance = float(wallet) + data["profit_total_abs"]
w_start = round(float(wallet), 2)
w_finish = round(final_balance, 2)
percent_prof = round(data["profit_total_pct"], 1)
if file_id == "op_bt":
is_start = datetime.strptime(time_range.split('-')[0], "%Y%m%d")
is_finish = datetime.strptime(time_range.split('-')[1], "%Y%m%d")
delta = is_start - is_finish
sample_days = int(delta.days)
else:
sample_days = self.out_sample_days
pppa = round((percent_prof / float(sample_days) * 365), 2) # percent profit per year
logger.info(f'Backtest result - Balance £{w_start} --> £{w_finish} ({percent_prof}%): {pppa} %profit pa')
# Trade stats
win = data['wins']
loss = data['losses']
draw = data['draws']
trades = data['trades']
logger.info(f"Backtest result - Wins: {win}, Draws: {draw}, Losses: {loss}, trades: {trades}")
# Average tade profits:
mean_p = round(float(data['profit_mean']), 2)
mp_percent = round(float(data['profit_mean_pct']), 2)
logger.info(f"Backtest result - mean trade profit £{mean_p}, {mp_percent}%")
# Account draw-down:
dd_percent = round((data['max_drawdown_account'] * 100), 2)
dd_abs = round(float(data['max_drawdown_abs']), 2)
logger.info(f"Backtest result - Max dd: {dd_percent}%, £{dd_abs}")
def plot_bt_profit(self, time_range, stage_dir, bt_file, file_id):
os.system(
"freqtrade plot-profit "
" --timeframe 1d "
" --timerange " + time_range +
" --strategy " + self.strategy +
" --strategy-path " + self.output_dir +
" --config " + self.config +
f" --export-filename {stage_dir}/{bt_file}"
)
# TODO relative path required:
src = Path(f"/home/matt/freqtrade/user_data/plot/freqtrade-profit-plot.html")
dst = f"{stage_dir}/{file_id}_profit-plot.html"
shutil.copyfile(str(src), dst)
@staticmethod
def get_backtest_data(stage_dir, file_id):
f = []
for (dirpath, dirnames, filenames) in walk(stage_dir):
f.extend(filenames)
break
# Find the backtest file for wf stage
for file in f:
# not meta.json:
if file[-9:-5] != "meta":
my_file = file_id + "_result"
if file.split("-")[0] == my_file:
# change to while open:
f = open(stage_dir + "/" + file)
data = json.load(f)
result = data["strategy_comparison"]
backtest_file = file
return result, backtest_file
def create_run_dir(self, path):
_time = datetime.now().strftime('%Y%m%d_%I:%M%p')
if self.re_opt:
directory = f"{path}/{_time}_{self.loss_function}_re-optimise_{self.in_sample_days}"
else:
directory = f"{path}/{_time}_{self.loss_function}_in_{self.in_sample_days}_out_{self.out_sample_days}"
if not os.path.exists(path):
os.makedirs(path)
try:
os.makedirs(directory)
except:
pass
return directory
@staticmethod
def create_stage_dir(path, stage, full_time_period):
dir_string = f"{path}/stage_{stage}_{full_time_period}"
if not os.path.exists(path):
os.makedirs(path)
try:
os.makedirs(dir_string)
except Exception as e:
pass
return dir_string
def copy_input_config(self, in_config):
# Copy config:
config = f"{in_config}"
dst_config = f"{self.output_dir}/{config.split('/')[-1]}"
shutil.copyfile(config, dst_config)
return dst_config
def copy_input_strategy(self, in_strategy_path, strategy):
# Copy Strategy
src = f"{in_strategy_path}/{strategy}.py"
dst_strategy = f"{self.output_dir}/{strategy}.py"
shutil.copyfile(src, dst_strategy)
return strategy
def in_sample_start(self):
oos_start = datetime.strptime(self.wf_start, "%Y%m%d")
if self.in_sample_days == "anchored":
is_start = datetime.strptime(self.anchored_start, "%Y%m%d") # + oos days
else:
is_start = oos_start - timedelta(int(self.in_sample_days))
is_start = is_start.strftime("%Y%m%d")
return is_start
def set_cpu(self, hyperopt_time):
is_start = datetime.strptime(hyperopt_time.split('-')[0], "%Y%m%d")
is_finish = datetime.strptime(hyperopt_time.split('-')[1], "%Y%m%d")
delta = is_finish - is_start
days = int(delta.days)
if self.in_sample_days == "anchored":
if days < 50:
cpu = "-1"
if days > 50:
cpu = "-2"
if days > 100:
cpu = "-4"
if days > 150:
cpu = "-6"
if days > 200:
cpu = "-8"
if days > 250:
cpu = "-10"
if days > 300:
cpu = "-11"
if days > 350:
cpu = "-12"
if days > 400:
cpu = "-13"
if days > 450:
cpu = "-14"
if days > 500:
cpu = "-14"
if days > 550:
cpu = "-16"
if days > 600:
cpu = "-16"
if days > 650:
cpu = "-17"
if days > 700:
cpu = "-17"
if days > 750:
cpu = "-17"
if days > 800:
cpu = "-18"
if days > 850:
cpu = "-18"
if days > 900:
cpu = "-18"
if days > 950:
cpu = "-19"
if days > 1000:
cpu = "-19"
if days > 1100:
cpu = "-20"
else:
d1 = datetime.strptime("20220601", "%Y%m%d")
d2 = datetime.strptime("20210101", "%Y%m%d")
d3 = datetime.strptime("20200101", "%Y%m%d")
d4 = datetime.strptime("20190101", "%Y%m%d")
d5 = datetime.strptime("20180101", "%Y%m%d")
if is_finish > d1:
cpu = self.cpu
if d2 < is_finish < d1:
cpu = int(self.cpu) + 1
if d3 < is_finish < d2:
cpu = int(self.cpu) + 2
if d4 < is_finish < d3:
cpu = int(self.cpu) + 3
if d5 < is_finish < d4:
cpu = int(self.cpu) + 4
if is_finish < d5:
cpu = int(self.cpu) + 5
return str(cpu)
def start_log(self):
# Set format and level:
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - WF - %(levelname)s - %(message)s')
# Remove old handlers:
while logger.handlers:
logger.handlers.pop()
# Define file handler:
file_handler = logging.FileHandler(f'{self.output_dir}/wf.log')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# Define console handler:
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# Startup logs:
_cpu = 20 + int(self.cpu) + 1
start_date = datetime.strptime(self.is_start, '%Y%m%d').date()
end_date = datetime.strptime(self.wf_finish, '%Y%m%d').date()
run_time = str(end_date - start_date).split(",")[0]
logger.info('Code Initiated')
logger.info(f'Strategy:{self.strategy_path}/{self.strategy}')
logger.info(f'Config:{self.config}')
logger.info(f'Loss Function: {self.loss_function}')
logger.info(f'Time Frame:{self.is_start}-{self.wf_finish} ({run_time})')
logger.info(f'IS-days:{self.in_sample_days}, OOS-days:{self.out_sample_days}')
logger.info(f'CPUs:{_cpu}, Epochs:{self.epochs}, Wallet:{"2500"}, Fee:{"0.002"}, Min-trades:{self.min_trades}')
def plot_equity_curve(df, output_dir, save_fig=False):
df = df.copy()
pd.set_option('display.max_columns', None)
fig, axs = plt.subplots(figsize=(7, 4))
axs.xaxis.set_major_formatter(mdates.DateFormatter("%d %b"))
df['dates'] = df["bt_time_period"].apply(lambda i: i.split("-")[1])
df['dt'] = df['dates'].apply(lambda i: datetime.strptime(i, '%Y%m%d'))
df.set_index('dt')
df.plot(ax=axs, x="dt", y="acc-finish")
dela_y = int(df["acc-finish"].max()) - int(df["acc-finish"].min())
y_min = int(df["acc-finish"].min()) - (dela_y * 0.05)
y_max = int(df["acc-finish"].max()) + (dela_y * 0.05)
axs.set_title('Walk-Forward equity curve')
axs.set_ylim(y_min, y_max)
axs.set_ylabel("")
axs.set_xlabel("")
axs.grid(color='grey', alpha=0.5, linestyle='dashed', linewidth=0.5)
axs.yaxis.set_major_formatter("£" + '{x:1.0f}')
# plt.show()
if save_fig:
try:
plt.savefig(f"{output_dir}/wf_equity_curve.png")
except:
pass
def walk_forward(path, strategy, config, output_dir, wf_start, wf_finish, anchored_start, pre_live=False, re_opt=False):
is_list = ["730"]
oos_list = ["30"]
n_trades = ["100"]
cpu_list = ["-15"]
ep = "100"
loss_f = "SharpeHyperOptLoss"
for count, is_days in enumerate(is_list):
oos_days = oos_list[count]
nt = n_trades[count]
cores = cpu_list[count]
wf = WalkForward(strategy_path=path, strategy=strategy, config=config, output_dir=output_dir,
wf_start=wf_start, wf_finish=wf_finish, anchored_start=anchored_start, epochs=ep,
loss_function=loss_f, in_sample_days=is_days, out_sample_days=oos_days, min_trades=nt,
cpu=cores, pre_live=pre_live, re_opt=re_opt)
wf.run_walk_forward()
return
def re_optimise(path, strategy, config, output_dir, cpu="-19"):
today = datetime.now().strftime("%Y%m%d")
loss_f = "SortinoHyperOptLoss"
in_sample_days = "730"
n_trades = "100"
ep = "200"
download_data_t = "5m 1h 1d"
wf = WalkForward(strategy_path=path, strategy=strategy, config=config, output_dir=output_dir, wf_start=today,
wf_finish=today, anchored_start=today, epochs=ep, loss_function=loss_f,
in_sample_days=in_sample_days, out_sample_days="1", min_trades=n_trades, cpu=cpu,
pre_live=False, re_opt=True, re_opt_t=download_data_t)
wf.re_optimise()
if __name__ == "__main__":
WF_START = "20200101" # 2023-03-25
WF_END = "20230910"
ANCHORED_START = "20200101" # only used if in_sample_days == "anchored"
STRATEGY_PATH_THOR = "/home/matt/freqtrade/user_data/strategies/Thor"
STRATEGY_THOR = "Optimise_Thor_BuySig_RiskReward"
CONFIG_THOR = "/home/matt/freqtrade/user_data/strategies/Thor/config_Thor_WF.json"
OUTPUT_DIR_THOR = "/home/matt/freqtrade/user_data/strategies/Thor/walk_forward"
# -------------------------------------------------------------
walk_forward(STRATEGY_PATH_THOR, STRATEGY_THOR, CONFIG_THOR, OUTPUT_DIR_THOR, WF_START, WF_END, ANCHORED_START)
# -------------------------------------------------------------
re_optimise(STRATEGY_PATH_THOR, STRATEGY_THOR, CONFIG_THOR, OUTPUT_DIR_THOR)
+55
View File
@@ -0,0 +1,55 @@
# MT5_Python_Strategy_Framework
This project provides an experimental framework for integrating MetaTrader 5 (MT5) custom indicators and trading logic with Python-based data processing and strategy testing using [Freqtrade](https://www.freqtrade.io/).
## Features
- 🧠 **Custom MT5 Libraries**: Modular `.mqh` files to handle position sizing, drawdown control, order management, and utility functions.
- 🐍 **Python Scripts**:
- `pre_process.py`: Prepares or cleans data before indicator processing.
- `process_entry_indicators.py`: Extracts and processes entry signals.
- `post_process_test.py`: Analyses backtest output or result data.
- 📦 **Freqtrade-Compatible Module**: Python strategies and helpers located in `Python_freqtrade/` for integration with the Freqtrade framework.
- 🛠️ **Project Structure Support**: Includes `.idea/` and `.vscode/` folders for JetBrains and VSCode IDE configurations.
## Project Structure
```
MT5_Python_Strategy_Framework/
├── My_MQL5_Libs/ # Custom MQL5 include files
├── Python_freqtrade/ # Freqtrade strategy components
├── pre_process.py # Data pre-processing script
├── process_entry_indicators.py # Entry signal extraction logic
├── post_process_test.py # Backtest result post-processing
├── .idea/, .vscode/ # IDE configs (optional)
└── README.md # Project documentation
```
## Getting Started
### Requirements
- MetaTrader 5 with access to `terminal64.exe`
- Python 3.8+
- Optional: Freqtrade installed (`pip install freqtrade`)
### Running Scripts
```bash
python pre_process.py
python process_entry_indicators.py
python post_process_test.py
```
### MT5 Library Usage
Place the `.mqh` files from `My_MQL5_Libs/` into your `MQL5/Include` folder to use them in your Expert Advisors or custom indicators.
## Notes
- This project is a scaffold for connecting MQL5 strategies to Python-based optimisation and analysis tools.
- Actual EA logic, data formats, and strategy specifics should be customised to your use case.
## License
This project is provided for educational and prototyping purposes. Please adapt and extend as needed for production environments.
+223
View File
@@ -0,0 +1,223 @@
import pandas as pd
from pathlib import Path
import logging
from xml.sax import ContentHandler, parse
from typing import List, Tuple
from pandas import DataFrame
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class PostProcessData:
"""
Class for post-processing and combining results from multiple indicator testing files.
"""
def __init__(self, results_dir: Path, print_outputs: bool = True, output_file: str = '1_combined_results.csv'):
"""
Initializes the PostProcessData class with the provided directory and output file.
Args:
results_dir (Path): The directory where the result files are located.
print_outputs (bool): Whether to print the final output.
output_file (str): The name of the combined output CSV file.
"""
self.results_dir = results_dir
self.print_outputs = print_outputs
self.output_file = output_file
self.run()
def run(self):
"""
Processes result files, calculates statistics, and saves the combined results.
"""
df_combined = self.process_results_files()
if df_combined is not None:
# Save the combined results as a CSV file
output_path = self.results_dir / self.output_file
df_combined.to_csv(output_path, index=False)
self.combine_opt_results()
if self.print_outputs:
print(df_combined)
else:
logging.info("Results processing complete.")
else:
logging.warning("No valid results to process.")
def process_results_files(self):
"""
Processes each indicator result file (ins.xml and out.xml) and computes the statistics.
Returns:
pd.DataFrame: Combined results DataFrame.
"""
df_combined = []
failed_post_process_list = []
for file in self.results_dir.iterdir():
if file.suffix == ".xml" and file.name.endswith("ins.xml"):
file_prefix = file.stem[:-4] # Remove '_ins' suffix
try:
df_combined.append(self.process_indicator_file(file_prefix))
except Exception as e:
failed_post_process_list.append(file_prefix)
logging.error(f"Failed to process {file_prefix}: {e}")
# Return combined DataFrame
if df_combined:
return pd.DataFrame(df_combined)
else:
return
def process_indicator_file(self, file_prefix: str) -> dict:
"""
Processes a pair of `ins.xml` and `out.xml` files for an indicator, calculates statistics,
and returns a dictionary of the results.
Args:
file_prefix (str): The base name of the indicator files (without extensions).
Returns:
dict: Dictionary of indicator statistics.
"""
# Load data from XML files
result_in, p_fac_in, trades_in = self.load_xml_data(file_prefix, "ins")
result_out, p_fac_out, trades_out = self.load_xml_data(file_prefix, "out")
# Calculate result statistics
result_mean = (result_in + result_out) / 2
pc_result = self.calc_percent_diff(result_in, result_out)
pc_p_fac = self.calc_percent_diff(p_fac_in, p_fac_out)
pc_trades = self.calc_percent_diff(trades_in, trades_out)
# Return a dictionary with computed data
return {
'Indicator': file_prefix,
'R_ins': result_in,
'R_outs': result_out,
'R_dif': pc_result,
'R_mean': result_mean,
'P_fac_in': p_fac_in,
'P_fac_out': p_fac_out,
'P_fac_dif': pc_p_fac,
'trades_in': trades_in,
'trades_out': trades_out,
'trades_dif': pc_trades
}
def load_xml_data(self, file_prefix: str, file_type: str):
"""
Loads data from an XML file (either 'ins' or 'out'), and extracts the result, profit factor, and trades.
Args:
file_prefix (str): The prefix of the file (without extension).
file_type (str): The type of the file ('ins' or 'out').
Returns:
tuple: Contains result, profit factor, and trades values as floats.
"""
try:
df = self.load_data_from_xml(f"{file_prefix}_{file_type}.xml")
return float(df["Result"][0]), float(df["Profit Factor"][0]), float(df["Trades"][0])
except Exception as e:
logging.error(f"Error loading {file_prefix}_{file_type}.xml: {e}")
return
@staticmethod
def load_data_from_xml(file: str) -> pd.DataFrame:
"""
Loads data from an XML file and converts it into a Pandas DataFrame.
Args:
file (str): The name of the XML file to load.
Returns:
pd.DataFrame: Data extracted from the XML file.
"""
excel_handler = ExcelHandler()
parse(file, excel_handler)
df = pd.DataFrame(excel_handler.tables[0][1:], columns=excel_handler.tables[0][0])
return df
@staticmethod
def calc_percent_diff(in_sample: float, out_sample: float) -> float:
"""
Calculates the percentage difference between two values.
Args:
in_sample (float): The "in-sample" value.
out_sample (float): The "out-sample" value.
Returns:
float: The percentage difference between the two values.
"""
try:
return round(abs(in_sample - out_sample) / out_sample * 100.0, 2)
except ZeroDivisionError:
return 0.0
def combine_opt_results(self):
"""
Combines all 'opt_results.txt' files in the results directory into one combined file.
"""
combined_file_path = self.results_dir / "2_combined_opt_results.txt"
if combined_file_path.exists():
combined_file_path.unlink()
opt_results_list = [file for file in self.results_dir.iterdir() if
file.suffix == ".txt" and "opt_results" in file.name]
new_lines = []
for file_path in opt_results_list:
new_lines.append(file_path.read_text())
new_lines.append("\n\n")
combined_file_path.write_text("".join(new_lines))
class ExcelHandler(ContentHandler):
"""
Custom handler to parse XML files and extract table data.
"""
def __init__(self):
self.rows = None
self.cells = None
self.chars = []
self.tables = []
def characters(self, content: str):
"""
Collects characters in XML elements.
"""
self.chars.append(content)
def start_element(self, name: str, attrs):
"""
Handle the start of XML elements.
"""
if name == "Table":
self.rows = []
elif name == "Row":
self.cells = []
elif name == "Data":
self.chars = []
def end_element(self, name: str):
"""
Handle the end of XML elements.
"""
if name == "Table":
self.tables.append(self.rows)
elif name == "Row":
self.rows.append(self.cells)
elif name == "Data":
self.cells.append("".join(self.chars))
if __name__ == "__main__":
# Example usage with a results directory path
post_processor = PostProcessData(Path(r'path_to_results'))
+81
View File
@@ -0,0 +1,81 @@
from pathlib import Path
import os
DEFAULTS_DIR = Path(
r'C:\Users\mkcor\AppData\Roaming\MetaQuotes\Terminal\49CDDEAA95A409ED22BD2287BB67CB9C\MQL5\Experts\My_Experts\NNFX\Entry_testing\default\indicators')
OPTIMISE_DIR = Path(
r'C:\Users\mkcor\AppData\Roaming\MetaQuotes\Terminal\49CDDEAA95A409ED22BD2287BB67CB9C\MQL5\Experts\My_Experts\NNFX\Entry_testing\optimise\indicators')
MASTER_CONFIG_DIR = Path(
r'C:\Users\mkcor\AppData\Roaming\MetaQuotes\Terminal\49CDDEAA95A409ED22BD2287BB67CB9C\MQL5\Experts\My_Experts\NNFX\Entry_testing\optimise\master_config_files')
def pre_process_checks():
"""
Pre-process check that verifies the presence of valid indicator files and generates necessary configuration templates.
This function:
- Checks if the indicator files in the default and optimizable directories have the correct suffixes.
- Creates master configuration template files if required in the master config directory.
"""
print("---------- Checking: default/indicators ---------------")
check_indicators_suffix(DEFAULTS_DIR)
print("---------- Checking: optimise/indicators --------------")
check_indicators_suffix(OPTIMISE_DIR)
print("---------- Checking: optimise/master_config_files -----")
create_cp_templates(OPTIMISE_DIR, MASTER_CONFIG_DIR)
def check_indicators_suffix(dir):
"""
Verifies if the indicator files in the specified directory have valid suffixes.
Args:
dir (Path): The directory containing the indicator files.
This function checks that all .mq5 and .ex5 files have one of the following suffixes:
- "clc", "cbc", "clx", "hcc", "hlx", "lcc", "0lx", "2lx"
If a file does not have one of these suffixes, a warning is printed.
"""
suffex_list = ["clc", "cbc", "clx", "hcc", "hlx", "lcc", "0lx", "2lx"]
for file in os.listdir(dir):
filename, file_extension = os.path.splitext(file)
if file_extension in [".mq5", ".ex5"]:
file_suffix = filename[-3:]
if file_suffix not in suffex_list:
print(f"{file} - INCORRECT FILE SUFFIX")
print("Complete.")
def create_cp_templates(opt_dir, master_config_dir):
"""
Creates master configuration template files in the specified master config directory.
Args:
opt_dir (Path): The directory containing the optimizable indicator files.
master_config_dir (Path): The directory where the master configuration files should be created.
This function generates a new template file for each .ex5 file in the optimizable directory,
creating a template file with the same name in the master config directory. If a corresponding
.ini file already exists, it skips the creation.
"""
for file in os.listdir(opt_dir):
filename, file_extension = os.path.splitext(file)
if file_extension == ".ex5":
template_file = master_config_dir / filename
ini_file = master_config_dir / f"{filename}.ini"
if ini_file.exists() and template_file.exists():
template_file.unlink() # Remove existing template file if it exists
else:
with open(template_file, 'w') as f:
f.write("Template content here") # Add content to the template
print(f"Master config for required - {filename}")
print("Complete.")
if __name__ == "__main__":
pre_process_checks()
+436
View File
@@ -0,0 +1,436 @@
"""
MattC - 2025
This code is a working prototype and is intended for initial testing and development purposes. Some Python
standards, including but not limited to PEP 8 compliance, error handling, and code optimization, are yet to be fully
implemented. Further refactoring and enhancements are planned to improve readability, maintainability,
and efficiency.
"""
from pathlib import Path
import configparser
import os
import pandas as pd
from subprocess import call
from post_process_test import PostProcessData as ppd
MT5_TERM_EXE = Path(r'C:\Program Files\FTMO MetaTrader 5\terminal64.exe')
MT5_DIRECTORY = Path(r"C:\Users\mkcor\AppData\Roaming\MetaQuotes\Terminal\49CDDEAA95A409ED22BD2287BB67CB9C")
##
class TestParent:
def __init__(self, name, start_date, end_date, chart_period, custom_loss_function, symbol_mode, data_split):
"""
@param name: test name e.g. "name"
@param start_date: backtest start e.g. "2010.10.01"
@param end_date: backtest end e.g. "2010.10.01"
@param chart_period: "Daily", "H4", "H1", "M15", etc
@param custom_loss_function: "0"= W/L ratio, "1"= W percent, "2"=W percent (min 200 trades) ....
@param symbol_mode: "0"=Chart sym only, "1"= Multi sym FX5, "2"=Multi sym 28FX pairs
@param data_split: "year" or "month"
"""
self.name = name
self.start_date = start_date
self.end_date = end_date
self.chart_period = chart_period
self.custom_loss_function = custom_loss_function
self.symbol_mode = symbol_mode
self.data_split = data_split
self.mt5_term = MT5_TERM_EXE
self.mt5_dir = MT5_DIRECTORY
self.mq5_test_cash = Path.joinpath(self.mt5_dir, r"Tester\cache")
self.test_folder = Path.joinpath(self.mt5_dir, r'MQL5\Experts\My_Experts\NNFX\Entry_testing')
self.indi_dir = None
self.indi_rel_path = None
self.output_dir = None
self.results_dir = None
self.master_config_loc = None
self.pct_risk = 2
self.tp_atr = 1
self.sl_atr = 1.5
def create_test_name(self):
start = self.start_date.replace('.', '')
end = self.end_date.replace('.', '')
test_name = f'{self.name}_{start}_{end}_cl{self.custom_loss_function}_sm{self.symbol_mode}_ds-{self.data_split}_cp-{self.chart_period}'
return test_name
def check_results_df(self, results_dir):
file_list = []
for file in os.listdir(results_dir):
file_list.append(file)
combined_resuts_file = '1_combined_results.csv'
df_path = Path.joinpath(results_dir, combined_resuts_file)
if combined_resuts_file not in file_list:
df = pd.DataFrame(
columns=['Indicator', 'Type', 'R_ins', 'R_outs', 'R_dif', 'R_mean', 'P_fac_in', 'P_fac_out',
'P_fac_dif', 'trades_in', 'trades_out', 'trades_dif'])
df.to_csv(df_path, index=False)
return df_path
def create_indi_optimisation_ini(self, config_paser, indicator, config_files_dir, sample_data, force_optimisation,
opt_os=False):
config = config_paser
config['Tester']['Expert'] = str(Path.joinpath(self.indi_rel_path, indicator)) + ".ex5"
config['Tester']['Symbol'] = "EURUSD"
config['Tester']['Period'] = f"{self.chart_period}"
config['Tester']['Optimization'] = "2"
config['Tester']['Model'] = "1"
config['Tester']['FromDate'] = f'{self.start_date}'
config['Tester']['ToDate'] = f'{self.end_date}'
config['Tester']['ForwardMode'] = "0"
config['Tester']['Deposit'] = "100000"
config['Tester']['Currency'] = "USD"
config['Tester']['ProfitInPips'] = "0"
config['Tester']['Leverage'] = "100"
config['Tester']['ExecutionMode'] = "0"
config['Tester']['OptimizationCriterion'] = "6"
config['Tester']['Visual'] = "0"
config['Tester']['ReplaceReport'] = "1"
config['Tester']['ShutdownTerminal'] = "1"
config['TesterInputs']['inp_lot_mode'] = "2||0||0||2||N"
config['TesterInputs']['inp_lot_var'] = f"{self.pct_risk}||2.0||0.2||20||N"
config['TesterInputs']['inp_sl_mode'] = "2||0||0||5||N"
config['TesterInputs']['inp_sl_var'] = f"{self.sl_atr}||1.0||0.1||10||N"
config['TesterInputs']['inp_tp_mode'] = "2||0||0||5||N"
config['TesterInputs']['inp_tp_var'] = f"{self.tp_atr}||1.5||0.15||15||N"
config['TesterInputs']['inp_custom_criteria'] = f"{self.custom_loss_function}||0||0||1||N"
config['TesterInputs']['inp_sym_mode'] = f"{self.symbol_mode}||0||0||2||N"
config['TesterInputs']['inp_force_opt'] = f"1||1||1||2||{force_optimisation}"
if sample_data == "in":
config['Tester']['report'] = f"{indicator}_ins"
if self.data_split == "year":
config['TesterInputs']['inp_data_split_method'] = f"1||0||0||3||N"
if self.data_split == "month":
config['TesterInputs']['inp_data_split_method'] = f"3||0||0||3||N"
if sample_data == "out":
config['Tester']['report'] = f"{indicator}_out"
if self.data_split == "year":
config['TesterInputs']['inp_data_split_method'] = f"2||0||0||3||N"
if self.data_split == "month":
config['TesterInputs']['inp_data_split_method'] = f"4||0||0||3||N"
if opt_os:
test_inp_list = list(config.items('TesterInputs'))
test_inp_key = []
for x in test_inp_list:
test_inp_key.append(x[0])
removal_list = ['inp_lot_mode', 'inp_lot_var', 'inp_sl_mode', 'inp_sl_var', 'inp_tp_mode', 'inp_tp_var',
'inp_custom_criteria', 'inp_sym_mode', 'inp_force_opt', 'inp_data_split_method']
keys_to_mod = [x for x in test_inp_key if (x not in removal_list)]
opt_results = self.get_opt_results_from_xml(indicator)
lines = []
lines2 = []
for key in keys_to_mod:
for j in test_inp_list:
if key == j[0]:
string_value = j[1]
string_value = string_value.split("||", 1)[
1] # removes the first int from e.g f"2||0||0||3||N"
for k in opt_results:
if key == k[0]:
result_val = k[1]
string_value = f'{result_val}||{string_value}'
string_value = string_value[:-1]
string_value = f'{string_value}N'
config['TesterInputs'][key] = string_value
value = config['TesterInputs'][key].split("||", 1)[0]
lines.append(f"{key}={value}, ")
lines2.append(f"{value}, ")
self.save_in_sample_opt_results_to_file(indicator, lines, lines2)
new_file = indicator + ".ini"
new_file_path = Path.joinpath(config_files_dir, new_file)
with open(new_file_path, 'w', encoding='utf-16') as configfile:
config.write(configfile)
def save_in_sample_opt_results_to_file(self, indicator, lines, lines2):
file_path = Path.joinpath(self.results_dir, f"{indicator}_opt_results.txt")
if file_path.is_file():
file_path.unlink() # delete old files.
lines_mod = ''
for str in lines2:
lines_mod = lines_mod + str
print(lines_mod[:-2])
f = open(file_path, "a")
f.writelines(indicator + "\n")
f.writelines(lines)
f.writelines("\n, ")
f.write(lines_mod[:-2])
f.close()
def get_opt_results_from_xml(self, indicator):
ins_results = f"{indicator}_ins.xml"
df = ppd.load_data_from_xml(self.results_dir)
df = df.drop(['Pass', 'Result', 'Profit', 'Profit Factor', 'Custom', 'Expected Payoff', 'Recovery Factor',
'Sharpe Ratio', 'Equity DD %', 'Trades'], axis=1)
column_names = list(df.columns.values)
opt_result = []
for count, value in enumerate(column_names):
param_results = df[column_names[count]][0]
tup = (value.lower(), param_results) # Convert string to lower case.
opt_result.append(tup)
return opt_result
def create_indicator_list(self, df_path, indicator_dir, optimisation=False):
# Create list
indi_list = []
for file in os.listdir(os.fsencode(indicator_dir)):
filename = os.fsdecode(file)
if filename.endswith(".ex5"):
indi_name = os.path.splitext(filename)[0]
indi_list.append(str(indi_name))
# remove previously processed indicators:
ti_list = []
for i in indi_list:
df = pd.read_csv(df_path)
if i in df["Indicator"].tolist():
# ti_list.append(i)
print(f"Indicator - {i} - already processed")
print("-" * 60)
indi_list2 = [x for x in indi_list if x not in ti_list]
conf_ini_list = []
if optimisation:
for file in os.listdir(os.fsencode(self.master_config_loc)):
filename = os.fsdecode(file)
if filename.endswith(".ini"):
indi_name = os.path.splitext(filename)[0]
conf_ini_list.append(str(indi_name))
removed_list = [x for x in indi_list2 if x not in conf_ini_list]
for i in removed_list:
print(f"Indicator - {i} - NO MASTER CONFIG!")
print("-" * 60)
indi_list3 = [x for x in indi_list2 if x in conf_ini_list]
return_list = []
if optimisation:
return_list = indi_list3
else:
return_list = indi_list2
for i in return_list:
print(f"Indicator - {i} - To be tested.")
return return_list
def create_dir(self, dir_name):
dir_string = Path.joinpath(self.output_dir, dir_name)
if not os.path.exists(dir_string):
os.makedirs(dir_string)
try:
os.makedirs(dir_string)
except Exception as e:
pass
return dir_string
@staticmethod
def delete_files_in_directory(directory_path):
try:
files = os.listdir(directory_path)
for file in files:
file_path = os.path.join(directory_path, file)
if os.path.isfile(file_path):
os.remove(file_path)
except OSError:
print("Error occurred while deleting files.")
class TestIndicators(TestParent):
def __init__(self, name, start_date, end_date, chart_period, custom_loss_function, symbol_mode, data_split):
super().__init__(name, start_date, end_date, chart_period, custom_loss_function, symbol_mode, data_split)
self.template_file = Path.joinpath(self.test_folder, "template.ini")
self.indi_dir = Path.joinpath(self.test_folder, "default\indicators")
self.indi_rel_path = Path(str(self.indi_dir).split(r'\MQL5\Experts')[1][1:])
self.output_dir = Path.joinpath(Path(self.indi_dir).parents[0], f'Testing\{self.name}')
self.results_dir = self.create_dir("results")
self.run()
def run(self):
print("~" * 80)
print(f" DEFAULT INDICATOR TEST - {self.name}\n")
# Create in-sample dir/ delete its content:
input_files_is_dir = self.create_dir("config_files_in_sample")
self.delete_files_in_directory(input_files_is_dir)
# Create out sample dir/ delete its content:
input_files_os_dir = self.create_dir("config_files_out_sample")
self.delete_files_in_directory(input_files_os_dir)
df_path = self.check_results_df(self.results_dir)
indicator_list = self.create_indicator_list(df_path, self.indi_dir)
print("-" * 25 + " STARTING TEST " + "-" * 25)
for indicator in indicator_list:
config_paser = self.load_config_paser()
# Create run input files:
self.create_indi_optimisation_ini(config_paser, indicator, input_files_is_dir, "in", "Y")
self.create_indi_optimisation_ini(config_paser, indicator, input_files_os_dir, "out", "Y")
# Delete MQL5 Tester Cash:
line = f'del /F /Q {self.mq5_test_cash}'
call(line, shell=True)
# Run the in-sample test:
print(f"Running defalts in-sample test for {indicator}")
line = f'"{self.mt5_term}" /config: {input_files_is_dir}\{indicator}.ini'
call(line, shell=True)
# Copy output to run results dir:
line = f'copy {self.mt5_dir}\{indicator}_ins.xml {self.results_dir}\{indicator}_ins.xml'
call(line, shell=True)
# Run the out of sample test:
print(f"Running defalts out-of-sample test for {indicator}")
line = f'"{self.mt5_term}" /config: {input_files_os_dir}\{indicator}.ini'
call(line, shell=True)
# Copy output to run results dir:
line = f'copy {self.mt5_dir}\{indicator}_out.xml {self.results_dir}\{indicator}_out.xml'
call(line, shell=True)
def load_config_paser(self):
config_paser = configparser.ConfigParser()
config_paser.read(self.template_file, encoding='utf-16')
return config_paser
class OptimiseIndicators(TestParent):
def __init__(self, name, start_date, end_date, chart_period, custom_loss_function, symbol_mode, data_split):
super().__init__(name, start_date, end_date, chart_period, custom_loss_function, symbol_mode, data_split)
self.indi_dir = Path.joinpath(self.test_folder, "optimise\indicators")
self.master_config_loc = Path.joinpath(self.test_folder, "optimise\master_config_files")
self.indi_rel_path = Path(str(self.indi_dir).split(r'\MQL5\Experts')[1][1:])
self.output_dir = Path.joinpath(Path(self.indi_dir).parents[0], f'Testing\{self.name}')
self.results_dir = self.create_dir("results")
self.run()
def run(self):
print("~" * 80)
print(f"\n INDICATOR OPTIMISATION - {self.name}\n")
# Create in-sample dir/ delete its content:
input_files_is_dir = self.create_dir("config_files_in_sample")
self.delete_files_in_directory(input_files_is_dir)
# Create out sample dir/ delete its content:
input_files_os_dir = self.create_dir("config_files_out_sample")
self.delete_files_in_directory(input_files_os_dir)
df_path = self.check_results_df(self.results_dir)
indicator_list = self.create_indicator_list(df_path, self.indi_dir, True)
print("-" * 25 + " STARTING TEST " + "-" * 25)
for indicator in indicator_list:
# Delete MQL5 Tester Cash:
line = f'del /F /Q {self.mq5_test_cash}'
call(line, shell=True)
# Load the MQL5 .ini for the current indicator:
config_paser = self.load_config_paser(indicator)
# Create in-sample input file:
self.create_indi_optimisation_ini(config_paser, indicator, input_files_is_dir, "in", "N")
# Run the in-sample test:
print(f"Running in-sample optimisation for {indicator}")
line = f'"{self.mt5_term}" /config: {input_files_is_dir}\{indicator}.ini'
call(line, shell=True)
# Copy output to run results dir:
line = f'copy {self.mt5_dir}\{indicator}_ins.xml {self.results_dir}\{indicator}_ins.xml'
call(line, shell=True)
# Create OOS test input file for optimisation results:
self.create_indi_optimisation_ini(config_paser, indicator, input_files_os_dir, "out", "Y", opt_os=True)
# Run the out of sample test:
print(f"Running out-of-sample optimisation for {indicator}")
line = f'"{self.mt5_term}" /config: {input_files_os_dir}\{indicator}.ini'
call(line, shell=True)
# Copy output to run results dir:
line = f'copy {self.mt5_dir}\{indicator}_out.xml {self.results_dir}\{indicator}_out.xml'
call(line, shell=True)
def load_config_paser(self, indicator):
config_paser = configparser.ConfigParser()
inp_file = f'{self.master_config_loc}\{indicator}.ini'
config_paser.read(inp_file, encoding='utf-16')
return config_paser
if __name__ == "__main__":
# Testing on 12 years data. in/out sample data split: year
TestIndicators(name="Apollo-dftest",
start_date="2012.01.01",
end_date="2022.01.01",
chart_period="Daily",
custom_loss_function="1", # 1 = no trade limit
symbol_mode="1",
data_split="month"
)
OptimiseIndicators(name="Apollo-opt",
start_date="2012.01.01",
end_date="2022.01.01",
chart_period="Daily",
custom_loss_function="4", # 400 trades min
symbol_mode="1",
data_split="month"
)