no_overnight_trades.mqh >> The file is in a zipped folder at the end of this article.
Add this MQL5 .mqh include file to your EA if you want to convert it to no overnight trades.
This is not made for a multi-symbol EA. One Symbol loaded only.
Useful if wanting to see if your strategy would work under the constraints of no overnight trades.
Many futures prop firms don't allow overnight trades. Test if your CFD strategy would work with their rules..
This include file will:
- Close your trades before market close; (input market_close) minus (input close_offset_min)
- Re-open your trades after market open; (input market_open) plus (input open_offset_min)
Offset inputs are in minutes. You can set your brokers' open and closing time.
- It takes a snapshot of the EA's open trades near the market close, stores it in item[] array,
- Sends those trades in JSON format to a database .sqlite file saved in MQL5> File... directory
- On the market open EA will:
i) search for open trades (overnight trades) and excludes them from being reopened.
ii) looks at the market open price and excludes trades that either would have hit SL or TP overnight,
often happens with price gap-up and gap-downs.
iii) re-opens all trades in the most recent database entry that have not been excluded . These trades are stored in the keep[] array.
iv) the re-opened trades will copy; lot size, stop loss, take profit and comment of each trade in the .sqlite database. Obviously the open price of the trade will be different than before.
Where to place no_overnight_trades.mqh in your EA .
//+------------------------------------------------------------------+ //| Your_EA.mq5 | //| | //| | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include <no_overnight_trades.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { TS_Init(order_magic); //No overnight trades //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- TS_Deinit(); // No Overnight trades } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { TS_OnTick(); //No Overnight trades }
no_overnight_trades.mqh
//+------------------------------------------------------------------+ //| no_overnight_trades.mqh | //| Fully MQL5-compliant version with proper JSON trimming | //+------------------------------------------------------------------+ #property strict #property copyright " (c)2025 Philip Sang-Nelson" #include <Trade\Trade.mqh> input long order_magic = 123456; // EA magic number input string market_open = "16:35"; // broker open input int open_offset_min = 5; // reopen X min after open input string market_close = "22:55"; // broker close input int close_offset_min = 5; // snapshot X min before close input bool debug_logging = true; int g_db_handle = INVALID_HANDLE; bool g_db_ready = false; string g_db_name = ""; string g_symbol = ""; CTrade trade; int last_snapshot_day = -1; int last_reopen_day = -1; //---------- Struct for trades ---------------------------------------- struct TradeItem { long ticket; double lots; double sl; double tp; string comment; }; //---------- Logging -------------------------------------------------- void Log(string s) { if(debug_logging) PrintFormat("TS_DB: %s", s); } //---------- Time helpers --------------------------------------------- int HourFromStr(const string s) { return (int)(StringToInteger(StringSubstr(s,0,2))); } int MinFromStr (const string s) { return (int)(StringToInteger(StringSubstr(s,3,2))); } datetime TimeTodayAt(int h,int m) { datetime now = TimeCurrent(); MqlDateTime dt; TimeToStruct(now,dt); dt.hour = h; dt.min = m; dt.sec = 0; return StructToTime(dt); } //---------- String trim ---------------------------------------------- string StringTrim(string pstring) { StringTrimLeft(pstring); StringTrimRight(pstring); return pstring; } //---------- DB Init / Deinit ---------------------------------------- bool TS_Init(long magic) { trade.SetExpertMagicNumber(order_magic); g_symbol = Symbol(); g_db_name = StringFormat("%d_%s_trading_state.sqlite",(int)magic,g_symbol); g_db_handle = DatabaseOpen(g_db_name, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); if(g_db_handle == INVALID_HANDLE) { PrintFormat("TS_DB: Failed to open DB '%s' (err %d)", g_db_name, GetLastError()); g_db_ready = false; return false; } g_db_ready = true; string sql = "CREATE TABLE IF NOT EXISTS batches (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "ts INTEGER," "direction INTEGER," // 1=long, -1=short "data TEXT," "reopened INTEGER DEFAULT 0" ");"; if(!DatabaseExecute(g_db_handle, sql)) { PrintFormat("TS_DB: Create table failed (err %d)", GetLastError()); g_db_ready = false; return false; } Log("DB opened: " + g_db_name); return true; } void TS_Deinit() { if(g_db_handle != INVALID_HANDLE) DatabaseClose(g_db_handle); g_db_handle = INVALID_HANDLE; g_db_ready = false; Log("DB closed."); } //---------- Insert JSON batch ---------------------------------------- bool TS_InsertBatch(int direction,string json_data) { if(!g_db_ready) return false; int ts = (int)TimeCurrent(); StringReplace(json_data,"'","''"); string sql = StringFormat("INSERT INTO batches(ts,direction,data,reopened) VALUES(%d,%d,'%s',0);", ts,direction,json_data); if(!DatabaseExecute(g_db_handle,sql)) { PrintFormat("TS_DB: Insert failed (err %d)",GetLastError()); return false; } return true; } //---------- Load most recent batch ---------------------------------- bool TS_LoadMostRecentBatch(int direction, int &batch_id, string &json_out) { batch_id = -1; json_out = ""; if(!g_db_ready) return false; string sql = StringFormat("SELECT id,data FROM batches WHERE direction=%d ORDER BY ts DESC LIMIT 1;", direction); int stmt = DatabasePrepare(g_db_handle, sql); if(stmt == INVALID_HANDLE) { PrintFormat("TS_DB: Prepare failed (err %d)", GetLastError()); return false; } int temp_id; if(DatabaseRead(stmt)) { if(DatabaseColumnInteger(stmt, 0, temp_id)) batch_id = temp_id; DatabaseColumnText(stmt, 1, json_out); } DatabaseFinalize(stmt); return (batch_id != -1 && StringLen(json_out) > 0); } //---------- Strict JSON parser -------------------------------------- void TS_ParseItem(const string json, TradeItem &out_item) { int p, start, comma; string tmp; // ticket p = StringFind(json,"\"ticket\":"); start=p+8; comma = StringFind(json,",",start); tmp = (comma==-1)? StringTrim(StringSubstr(json,start)) : StringTrim(StringSubstr(json,start,comma-start)); out_item.ticket = (long)StringToInteger(tmp); // lots p = StringFind(json,"\"lots\":"); start=p+7; comma = StringFind(json,",",start); tmp = (comma==-1)? StringTrim(StringSubstr(json,start)) : StringTrim(StringSubstr(json,start,comma-start)); out_item.lots = StringToDouble(tmp); // sl p = StringFind(json,"\"sl\":"); start=p+5; comma = StringFind(json,",",start); tmp = (comma==-1)? StringTrim(StringSubstr(json,start)) : StringTrim(StringSubstr(json,start,comma-start)); out_item.sl = StringToDouble(tmp); // tp p = StringFind(json,"\"tp\":"); start=p+5; comma = StringFind(json,",",start); tmp = (comma==-1)? StringTrim(StringSubstr(json,start)) : StringTrim(StringSubstr(json,start,comma-start)); out_item.tp = StringToDouble(tmp); // comment p = StringFind(json,"\"comment\":"); start=p+10; while(start < StringLen(json) && (json[start]==' '||json[start]=='\t')) start++; int q1 = start; int q2 = StringFind(json,"\"",q1+1); out_item.comment = StringSubstr(json,q1+1,q2-q1-1); } void TS_ParseArray(const string json, TradeItem &items[]) { ArrayResize(items,0); int pos=0; while(true) { int start=StringFind(json,"{",pos); if(start<0) break; int end=StringFind(json,"}",start); if(end<0) break; string chunk=StringSubstr(json,start,end-start+1); TradeItem item; TS_ParseItem(chunk,item); int newSize=ArraySize(items)+1; ArrayResize(items,newSize); items[newSize-1]=item; pos=end+1; } } //---------- Build JSON ---------------------------------------------- string TS_BuildJSON(TradeItem &item) { return StringFormat("{\"ticket\":%d, \"lots\":%.2f, \"sl\":%.10g, \"tp\":%.10g, \"comment\":\"%s\"}", item.ticket,item.lots,item.sl,item.tp,item.comment); } string TS_BuildJSONArray(TradeItem &items[]) { int n=ArraySize(items); if(n==0) return "[]"; string out="["; for(int i=0;i<n;i++) { out+=TS_BuildJSON(items[i]); if(i<n-1) out+=","; } out+="]"; return out; } //---------- Get today's open price ---------------------------------- double TS_GetTodayOpenPrice() { double val=iOpen(g_symbol,PERIOD_M5,0); if(val<=0) val=SymbolInfoDouble(g_symbol,SYMBOL_BID); if(val<=0) val=SymbolInfoDouble(g_symbol,SYMBOL_ASK); return val; } //---------- Serialize open trades ---------------------------------- string TS_SerializeOpenTrades(int direction) { string out="["; bool first=true; int total=PositionsTotal(); for(int i=0;i<total;i++) { if(!PositionGetTicket(i)) continue; if(PositionGetString(POSITION_SYMBOL)!=g_symbol) continue; if(PositionGetInteger(POSITION_MAGIC)!=(long)order_magic) continue; int dir=(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)?1:-1; if(dir!=direction) continue; TradeItem item; item.ticket=(long)PositionGetInteger(POSITION_TICKET); item.lots=PositionGetDouble(POSITION_VOLUME); item.sl=PositionGetDouble(POSITION_SL); item.tp=PositionGetDouble(POSITION_TP); item.comment=PositionGetString(POSITION_COMMENT); string row=TS_BuildJSON(item); if(first){ out+=row; first=false;} else out+=","+row; } out+="]"; return out; } //---------- Close all positions ------------------------------------ void TS_CloseAllFor(int direction) { for(int i=PositionsTotal()-1;i>=0;i--) { if(!PositionGetTicket(i)) continue; if(PositionGetString(POSITION_SYMBOL)!=g_symbol) continue; if(PositionGetInteger(POSITION_MAGIC)!=(long)order_magic) continue; int dir=(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)?1:-1; if(dir!=direction) continue; ulong ticket=(ulong)PositionGetInteger(POSITION_TICKET); if(!trade.PositionClose(ticket)) PrintFormat("TS_DB: Close failed ticket %d error %d",ticket,GetLastError()); else Log("Closed ticket "+(string)ticket); } } //---------- Snapshot & close ---------------------------------------- void TS_SnapshotAndClose() { for(int d=0;d<2;d++) { int dir=(d==0)?1:-1; string json=TS_SerializeOpenTrades(dir); TS_InsertBatch(dir,json); TS_CloseAllFor(dir); Log("Snapshot saved for dir="+IntegerToString(dir)); } } //---------- Filter SL violations ----------------------------------- void TS_FilterByOpen(TradeItem &in_items[], TradeItem &out_items[], int direction) { ArrayResize(out_items,0); double open_price=TS_GetTodayOpenPrice(); for(int i=0;i<ArraySize(in_items);i++) { bool stopped=false; if(direction==1 && open_price<=in_items[i].sl) stopped=true; if(direction==-1 && open_price>=in_items[i].sl) stopped=true; if(direction==1 && open_price>= in_items[i].tp) stopped=true; if(direction==-1 && open_price<=in_items[i].tp) stopped=true; if(!stopped) { int newSize=ArraySize(out_items)+1; ArrayResize(out_items,newSize); out_items[newSize-1]=in_items[i]; } } } //---------- Reopen trades ------------------------------------------ void TS_ReopenTrades(TradeItem &items[], int direction) { for(int i = 0; i < ArraySize(items); i++) { bool already_open = false; //ulong existing_ticket; // Check if THIS trade already exists (match by comment or lots/sl) for(int p = 0; p < PositionsTotal(); p++) { if(!PositionGetTicket(p)) continue; //if(!PositionSelectByIndex(p)) continue; if(PositionGetString(POSITION_SYMBOL) != g_symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != (long)order_magic) continue; int dir = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 1 : -1; if(dir != direction) continue; // match on comment (best), or lots/sl/tp if(PositionGetInteger(POSITION_TICKET) == items[i].ticket) { already_open = true; break; } } if(already_open) continue; // Reopen the trade bool ok = false; if(direction == 1) { double ask = SymbolInfoDouble(g_symbol, SYMBOL_ASK); ok = trade.Buy(items[i].lots, g_symbol, ask, items[i].sl, items[i].tp, items[i].comment); } else { double bid = SymbolInfoDouble(g_symbol, SYMBOL_BID); ok = trade.Sell(items[i].lots, g_symbol, bid, items[i].sl, items[i].tp, items[i].comment); } if(ok) Log("Reopened trade from keep[] index " + (string)i); else PrintFormat("Reopen failed for keep[%d], error %d", i, GetLastError()); } } //---------- Reopen from most recent batch -------------------------- void TS_ReopenFromMostRecent() { for(int d=0;d<2;d++) { int direction=(d==0)?1:-1; int batch_id; string json; if(!TS_LoadMostRecentBatch(direction,batch_id,json)) {Log("No batch for dir "+IntegerToString(direction)); continue;} TradeItem items[]; TS_ParseArray(json,items); if(ArraySize(items)==0) {Log("Empty batch"); continue;} TradeItem keep[]; TS_FilterByOpen(items,keep,direction); string new_json=TS_BuildJSONArray(keep); TS_InsertBatch(direction,new_json); TS_ReopenTrades(keep,direction); string mark_sql=StringFormat("UPDATE batches SET reopened=1 WHERE id=%d;",batch_id); DatabaseExecute(g_db_handle,mark_sql); } } //---------- Driver: call in OnTick --------------------------------- void TS_OnTick() { if(!g_db_ready) return; int open_h=HourFromStr(market_open); int open_m=MinFromStr(market_open); int close_h=HourFromStr(market_close); int close_m=MinFromStr(market_close); datetime open_dt=TimeTodayAt(open_h,open_m); datetime reopen_dt=open_dt+open_offset_min*60; datetime close_dt=TimeTodayAt(close_h,close_m); datetime snapshot_dt=close_dt-close_offset_min*60; MqlDateTime tm; TimeToStruct(TimeCurrent(),tm); int today=tm.day; if(today!=last_snapshot_day && TimeCurrent()>=snapshot_dt) { Log("SnapshotAndClose at "+TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS)); TS_SnapshotAndClose(); last_snapshot_day=today; } if(today!=last_reopen_day && TimeCurrent()>=reopen_dt) { Log("ReopenFromMostRecent at "+TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS)); TS_ReopenFromMostRecent(); last_reopen_day=today; } }


