//+------------------------------------------------------------------+ //| bb_trader.mq5 | //| Copyright 2024, Competent Dilettante | //| https://www.youtube.com/@competentdilettante | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Competent Dilettante" #property link "https://www.youtube.com/@competentdilettante" #property version "1.00" #define STRUCT_FILENAME "bb_files/struct.dat" #define SET_FILENAME "bb_files/set_struct.dat" enum ENUM_LIFETIME_SIGNAL{ LIFETIME_SIGNAL_DAY = 0, //Время жизни сигнала - день LIFETIME_SIGNAL_WEEK, //Время жизни сигнала - неделя LIFETIME_SIGNAL_TWO_WEEKS, //Время жизни сигнала - 2 недели LIFETIME_SIGNAL_MONTH //Время жизни сигала - месяц }; struct SSettings{ bool allowTrade; double outComm; int slipage; ENUM_TIMEFRAMES slowTf; int slowBBPeriod; int slowBBShift; int slowMAPeriod; ENUM_TIMEFRAMES fastTf; int fastBBPeriod; int fastBBShift; int fastAO_MA; int slowAO_MA; int fastMACD; int slowMACD; int smaMACD; ENUM_LIFETIME_SIGNAL inLifeTime; int inFiltr; double inStopLevel; double close50Percent; double close30Percent; double close20Percent; double inRisk; bool operator !=(const SSettings &other){ if(allowTrade == other.allowTrade && (int)(outComm * 100) == (int)(other.outComm * 100) && slipage == other.slipage && slowTf == other.slowTf && slowBBPeriod == other.slowBBPeriod && slowBBShift == other.slowBBShift && slowMAPeriod == other.slowMAPeriod && fastTf == other.fastTf && fastBBPeriod == other.fastBBPeriod && fastBBShift == other.fastBBShift && fastAO_MA == other.fastAO_MA && slowAO_MA == other.slowAO_MA && fastMACD == other.fastMACD && slowMACD == other.slowMACD && smaMACD == other.smaMACD && inLifeTime == other.inLifeTime && inFiltr == other.inFiltr && (int)(inStopLevel * 1000) == (int)(other.inStopLevel * 1000) && (int)(close50Percent * 1000) == (int)(other.close50Percent * 1000) && (int)(close30Percent * 1000) == (int)(other.close30Percent * 1000) && (int)(close20Percent * 1000) == (int)(other.close20Percent * 1000) && (int)(inRisk * 100) == (int)(other.close20Percent * 100) ) return false; return true; } }; struct SFibo { double zeroPoint; double onePoint; SFibo() { zeroPoint = 0; onePoint = 0; } }; #include "MyExpertFunctions.mqh" #include "AbsorbingFinder.mqh" input group "Общие настройки" input uint inMagic = 556; //Идентификатор позиций эксперта input bool inAllowTrade = true; //Разрешение на открытие новых позиций input double inOutComm = 0.0; input int inSlipage = 10; input group "Настройки старшего ТФ" input ENUM_TIMEFRAMES slowTf = PERIOD_H4; input int slowBBPeriod = 21; input int slowBBShift = 1; input int slowMAPeriod = 120; input group "Настройки младшего ТФ" input ENUM_TIMEFRAMES fastTf = PERIOD_M15; input int fastBBPeriod = 21; input int fastBBShift = 1; input int fastAO_MA = 1; input int slowAO_MA = 7; input int fastMACD = 12; input int slowMACD = 30; input int smaMACD = 9; input ENUM_LIFETIME_SIGNAL inLifeTime = LIFETIME_SIGNAL_WEEK; //Время жизни сигнала input group "Настройки торговли" input int inFiltr = 50; input double inStopLevel = 0.306; input double close50Percent = 1.618; input double close30Percent = 2.618; input double close20Percent = 4.236; input double inRisk = 1.5; //Risk on one deal (%) //Handle values int slowMAHandle, slowBBHandle; int fastBBHandle, AOHandle, fastAO_MAHandle,slowAO_MAHandle,MACDHandle; //Position struct MqlParam posParams[2]; //Signal flags bool isSignal = false; int signalFlag{0}; int globalFlag{0}; //Time flags static datetime t{0},slowT{0},fastT{0}; //Fibo struct SFibo points; //Levels array const double levelaArray[] {1.768,1.88,2.786,2.88,4.786,4.88}; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- MqlParam params[]; ::ArrayResize(params,4); params[0].type = TYPE_INT; params[0].integer_value = slowMAPeriod; params[1].type = TYPE_INT; params[1].integer_value = 0; params[2].type = TYPE_INT; params[2].integer_value = MODE_SMA; params[3].type = TYPE_INT; params[3].integer_value = PRICE_CLOSE; ::ResetLastError(); slowAO_MAHandle = std::GetHandle(_Symbol,slowTf,IND_MA,params); if(slowAO_MAHandle == INVALID_HANDLE) { ::Print("MA on slow TF handlig error ",::GetLastError()); return INIT_FAILED; } params[0].integer_value = slowBBPeriod; params[1].integer_value = slowBBShift; params[2].type = TYPE_DOUBLE; params[2].double_value = 2.0; slowBBHandle = std::GetHandle(_Symbol,slowTf,IND_BANDS,params); if(slowBBHandle == INVALID_HANDLE) { ::Print("BB on slow TF handlig error ",::GetLastError()); return INIT_FAILED; } params[0].integer_value = fastBBPeriod; params[1].integer_value = fastBBShift; fastBBHandle = std::GetHandle(_Symbol,fastTf,IND_BANDS,params); if(fastBBHandle == INVALID_HANDLE) { ::Print("BB on fast TF handlig error ",::GetLastError()); return INIT_FAILED; } MqlParam array[]; AOHandle = std::GetHandle(_Symbol,fastTf,IND_AO,array); if(AOHandle == INVALID_HANDLE) { ::Print("AO on fast TF handlig error ",::GetLastError()); return INIT_FAILED; } params[0].integer_value = fastAO_MA; params[1].integer_value = 0; params[2].type = TYPE_INT; params[2].integer_value = MODE_SMA; params[3].integer_value = AOHandle; fastAO_MAHandle = std::GetHandle(_Symbol,fastTf,IND_MA,params); if(fastAO_MAHandle == INVALID_HANDLE) { ::Print("Fast MA from AO on fast TF handlig error ",::GetLastError()); return INIT_FAILED; } params[0].integer_value = slowAO_MA; slowAO_MAHandle = std::GetHandle(_Symbol,fastTf,IND_MA,params); if(slowAO_MAHandle == INVALID_HANDLE) { ::Print("Slow MA from AO on fast TF handlig error ",::GetLastError()); return INIT_FAILED; } params[0].integer_value = fastMACD; params[1].integer_value = slowMACD; params[2].type = TYPE_INT; params[2].integer_value = smaMACD; params[3].integer_value = PRICE_CLOSE; MACDHandle = std::GetHandle(_Symbol,fastTf,IND_MACD,params); if(MACDHandle == INVALID_HANDLE) { ::Print("MACD on fast TF handlig error ",::GetLastError()); return INIT_FAILED; } if(!::FileIsExist(STRUCT_FILENAME)) { ::Print("File ",STRUCT_FILENAME," not found! Creating new file"); if(!std::fileWriteBinStuct(STRUCT_FILENAME,points)) { ::Print("Creating file ",STRUCT_FILENAME," error #",::GetLastError()); return INIT_FAILED; } } ::Print("File ",STRUCT_FILENAME," is in system"); if(!::FileIsExist(SET_FILENAME)){ SSettings sets; setupSetStruct(sets); if(!std::fileWriteBinStuct(SET_FILENAME,points)) { ::Print("Creating file ",SET_FILENAME," error #",::GetLastError()); return INIT_FAILED; } } else{ ::Print("File ",SET_FILENAME," is in system"); SSettings curSets,saveSets; setupSetStruct(curSets); std::fileReadBinStuct(SET_FILENAME,saveSets); if(curSets != saveSets){ resetStructure(); std::fileWriteBinStuct(SET_FILENAME,curSets); } } posParams[0].type = TYPE_STRING; posParams[0].string_value = _Symbol; posParams[1].type = TYPE_UINT; posParams[1].integer_value = inMagic; //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- std::fileWriteBinStuct(STRUCT_FILENAME,points); SSettings sets; setupSetStruct(sets); std::fileWriteBinStuct(SET_FILENAME,sets); //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- //Block open position tracking if(t != ::iTime(_Symbol,PERIOD_M1,0)) { t = ::iTime(_Symbol,PERIOD_M1,0); if(std::CountPositions(posParams) <= 0) { points.onePoint = 0; points.zeroPoint = 0; } else { if(!::PositionSelect(_Symbol)) { t = 0; } double pos_sl = ::PositionGetDouble(POSITION_SL); ulong pos_id = ::PositionGetInteger(POSITION_IDENTIFIER); ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_volume = ::PositionGetDouble(POSITION_VOLUME); if(pos_sl <= 0) SetStop(::PositionGetInteger(POSITION_IDENTIFIER)); double l1{0.0},l2{0.0},l3{0.0}; int fibo = std::DistPrices(points.zeroPoint,points.onePoint,_Symbol); if(pos_type == POSITION_TYPE_SELL) { l1 = points.zeroPoint - LevelDist(fibo,close50Percent) * _Point; l2 = points.zeroPoint - LevelDist(fibo,close30Percent) * _Point; l3 = points.zeroPoint - LevelDist(fibo,close20Percent) * _Point; } else { l1 = points.zeroPoint + LevelDist(fibo,close50Percent) * _Point; l2 = points.zeroPoint + LevelDist(fibo,close30Percent) * _Point; l3 = points.zeroPoint + LevelDist(fibo,close20Percent) * _Point; } double close_level{0.0}; int count = GetClosedParts(pos_id,pos_volume); if(count == 0) { ::Print("Error history access. Error ",::GetLastError()); t = 0; return; } else if(count == 1) close_level = l1; else if(close_level == 2) close_level = l2; else close_level = l3; if(pos_type == POSITION_TYPE_BUY) { if(::iClose(_Symbol,PERIOD_M1,1) >= close_level) { double lot{0.0}; if(close_level == l1) lot = std::LotNormalize(pos_volume * 0.5,_Symbol); else if(close_level == l2) lot = std::LotNormalize(pos_volume * 0.3,_Symbol); else lot = pos_volume; if(!std::Close_PartPosition(pos_id,lot,inSlipage)) { t = 0; return; } } } else { if(::iClose(_Symbol,PERIOD_M1,1) <= close_level) { double lot{0.0}; if(close_level == l1) lot = std::LotNormalize(pos_volume * 0.5,_Symbol); else if(close_level == l2) lot = std::LotNormalize(pos_volume * 0.3,_Symbol); else lot = pos_volume; if(!std::Close_PartPosition(pos_id,lot,inSlipage)) { t = 0; return; } } } } } if(slowT != ::iTime(_Symbol,slowTf,0)) { slowT = ::iTime(_Symbol,slowTf,0); double BBarrayUp[],BBarrayDn[]; if(!::CopyBuffer(slowBBHandle,1,0,2,BBarrayUp) || !::CopyBuffer(slowBBHandle,2,0,2,BBarrayDn)) { slowT = 0; return; } ::ArraySetAsSeries(BBarrayUp,true); ::ArraySetAsSeries(BBarrayDn,true); double MAarray[]; if(!::CopyBuffer(slowMAHandle,0,0,2,MAarray)) { slowT = 0; return; } ::ArraySetAsSeries(MAarray,true); if(::iClose(_Symbol,slowTf,1) > MAarray[1]) signalFlag = 1; else if(::iClose(_Symbol,slowTf,1) < MAarray[1]) signalFlag = 2; else signalFlag = 0; if(signalFlag == 1) { if(::iClose(_Symbol,slowTf,1) < BBarrayDn[1]) isSignal = true; } else if(signalFlag == 2) { if(::iClose(_Symbol,slowTf,1) > BBarrayUp[1]) isSignal = true; } if(signalFlag > 0 && isSignal && globalFlag <= 0){ if(IsAbsorbingSignal(_Symbol,1)){ if(signalFlag == 1) globalFlag = 1; if(signalFlag == 2) globalFlag = 2; } } } if(fastT != ::iTime(_Symbol,fastTf,0)) { fastT = ::iTime(_Symbol,fastTf,0); if(!inAllowTrade) return; if(std::CountPositions(posParams) > 0) { ResetSignal(); return; } if(signalFlag <= 0 || !isSignal || globalFlag <= 0) return; double AOarray[],MACDarray[]; if(!::CopyBuffer(AOHandle,0,0,2,AOarray)) { fastT = 0; return; } ::ArraySetAsSeries(AOarray,true); if(!::CopyBuffer(MACDHandle,1,0,2,MACDarray)) { fastT = 0; return; } ::ArraySetAsSeries(MACDarray,true); if(isSignal && signalFlag == 1) { if(AOarray[1] > 0 && MACDarray[1] > 50) { ResetSignal(); return; } double s{0},z{0}; if(IsAbsorbingUp(_Symbol,fastTf,1,s,z) || IsDoubleAbsorbingUp(_Symbol,fastTf,1,s,z) || IsTripleAbsorbingUp(_Symbol,fastTf,1,s,z)) { ::Print("=== Absorbing up find ==="); ::Print("Zero point = ",::DoubleToString(z,_Digits),"\tEnd point = ",::DoubleToString(s,_Digits)); if(AOarray[1] < 0 && MACDarray[1] < 50) { points.zeroPoint = z; points.onePoint = s; int fibo = std::DistPrices(z,s,_Symbol); int sl_dist = LevelDist(fibo,inStopLevel); double sl = points.onePoint - sl_dist * _Point - inFiltr * _Point; double cur = ::SymbolInfoDouble(_Symbol,SYMBOL_ASK); double acc = ::AccountInfoDouble(ACCOUNT_BALANCE); double lot = std::LotSize(_Symbol,ORDER_TYPE_BUY,cur,sl,acc,inRisk); if(lot <= 0) lot = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); ulong pos = std::Open_Buy_Pos(_Symbol,lot,inSlipage,inMagic); if(pos <= 0) { fastT = 0; return; } if(!std::Position_SLTP(pos,sl,0.0)) { fastT = 0; return; } ResetSignal(); } } } else if(isSignal && signalFlag == 2) { if(AOarray[1] < 0 && MACDarray[1] < 50) { ResetSignal(); return; } double s{0},z{0}; if(IsAbsorbingDn(_Symbol,fastTf,1,s,z) || IsDoubleAbsorbingDn(_Symbol,fastTf,1,s,z) || IsTripleAbsorbingDn(_Symbol,fastTf,1,s,z)) { ::Print("=== Absorbing down find ==="); ::Print("Zero point = ",::DoubleToString(z,_Digits),"\tEnd point = ",::DoubleToString(s,_Digits)); if(AOarray[1] > 0 && MACDarray[1] > 50) { points.zeroPoint = z; points.onePoint = s; int fibo = std::DistPrices(z,s,_Symbol); int sl_dist = LevelDist(fibo,inStopLevel); double sl = points.onePoint + sl_dist * _Point + inFiltr * _Point; double cur = ::SymbolInfoDouble(_Symbol,SYMBOL_BID); double acc = ::AccountInfoDouble(ACCOUNT_BALANCE); double lot = std::LotSize(_Symbol,ORDER_TYPE_SELL,cur,sl,acc,inRisk); if(lot <= 0) lot = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); ulong pos = std::Open_Sell_Pos(_Symbol,lot,inSlipage,inMagic); if(pos <= 0) { fastT = 0; return; } if(!std::Position_SLTP(pos,sl,0.0)) { fastT = 0; return; } ResetSignal(); } } } } //--- } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void SetStop(const ulong pos) { ::ResetLastError(); if(!::PositionSelectByTicket(pos)) return; int fibo_dist = std::DistPrices(points.onePoint,points.zeroPoint,_Symbol); int sl_points = (int)::ceil(fibo_dist * inStopLevel); if(!std::Position_SLTP(pos,sl_points,0)) { ::Print("Stop-loss set up failed. Error ",::GetLastError()); return; } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ResetSignal() { //points.onePoint = 0; //points.zeroPoint = 0; signalFlag = 0; isSignal = false; globalFlag = 0; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int LevelDist(const int size,const double dec) { return ((int)(::ceil(size * dec))); } //+------------------------------------------------------------------+ void resetStructure(){ points.onePoint = 0; points.zeroPoint = 0; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetClosedParts(const ulong position,double &volume) { if(!::HistorySelectByPosition(position)) return 0; int count = ::HistoryDealsTotal(); if(count == 2) { for(int i = 0;i < count;i++) { ulong deal = ::HistoryDealGetTicket(i); if(::HistoryDealGetInteger(deal,DEAL_ENTRY) == DEAL_ENTRY_IN) { volume = ::HistoryDealGetDouble(deal,DEAL_VOLUME); break; } } } return count; } //+------------------------------------------------------------------+ bool IsAbsorbingSignal(const string symbol,const int index){ if(signalFlag <= 0) return false; double open = ::iOpen(symbol,slowTf,1); double close = ::iClose(symbol,slowTf,1); if(signalFlag == 1 && open > close) return false; if(signalFlag == 2 && open < close) return false; if(signalFlag > 0){ int history = 0; switch(inLifeTime){ case LIFETIME_SIGNAL_DAY: history = 6; break; case LIFETIME_SIGNAL_WEEK: history = 30; break; case LIFETIME_SIGNAL_TWO_WEEKS: history = 60; break; case LIFETIME_SIGNAL_MONTH: history = 120; break; } double buffer[]; int ctrl_candle = 0; if(signalFlag == 1){ if(!::CopyBuffer(slowBBHandle,2,0,history,buffer)) return false; for(int i = 1;i < history;i++){ if(::iClose(symbol,slowTf,i) > buffer[i]) continue; if(iOpen(symbol,slowTf,i) > ::iClose(symbol,slowTf,i)){ ctrl_candle = i; break; } } if(close > ::iOpen(symbol,slowTf,ctrl_candle)) return true; } else{ if(!::CopyBuffer(slowBBHandle,1,0,history,buffer)) return false; for(int i = 1;i < history;i++){ if(::iClose(symbol,slowTf,i) < buffer[i]) continue; if(iOpen(symbol,slowTf,i) < ::iClose(symbol,slowTf,i)){ ctrl_candle = i; break; } } if(close < ::iOpen(symbol,slowTf,ctrl_candle)) return true; } } return false; } //+------------------------------------------------------------------+ void setupSetStruct(SSettings &sets){ sets.allowTrade = inAllowTrade; sets.fastAO_MA = fastAO_MA; sets.fastBBPeriod = fastBBPeriod; sets.fastBBShift = fastBBShift; sets.fastMACD = fastMACD; sets.fastTf = fastTf; sets.slowBBPeriod = slowBBPeriod; sets.slowBBShift = slowBBShift; sets.slowMACD = slowMACD; sets.slowMAPeriod = slowMAPeriod; sets.slowAO_MA = slowAO_MA; sets.slowTf = slowTf; sets.slipage = inSlipage; sets.inLifeTime = inLifeTime; sets.inFiltr = inFiltr; sets.inStopLevel = inStopLevel; sets.smaMACD = smaMACD; sets.close20Percent = close20Percent; sets.close30Percent = close30Percent; sets.close50Percent = close50Percent; sets.outComm = inOutComm; sets.inRisk = inRisk; } //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction &trans,const MqlTradeRequest &request,const MqlTradeResult &result){ if(trans.type == TRADE_TRANSACTION_DEAL_ADD){ ulong deal = trans.deal; ::HistoryDealSelect(deal); string symbol = ::HistoryDealGetString(deal,DEAL_SYMBOL); uint magic = (uint)::HistoryDealGetInteger(deal,DEAL_MAGIC); if(symbol == _Symbol && magic == inMagic){ if(::HistoryDealGetInteger(deal,DEAL_REASON) == DEAL_REASON_SL){ ResetSignal(); resetStructure(); } } } }