diff --git a/ADX MACD Deev.mq5 b/ADX MACD Deev.mq5 new file mode 100644 index 0000000..05ce258 Binary files /dev/null and b/ADX MACD Deev.mq5 differ diff --git a/AllPendingsOrderSend.mq5 b/AllPendingsOrderSend.mq5 new file mode 100644 index 0000000..3d27f7c --- /dev/null +++ b/AllPendingsOrderSend.mq5 @@ -0,0 +1,150 @@ +//+------------------------------------------------------------------+ +//| AllPendingsOrderSend.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Construct MqlTradeRequest for all pending order types and call OrderSend with TRADE_ACTION_PENDING." +#property description "Expiration, Stop Loss and Take Profit can be set optionally." + +#define SHOW_WARNINGS // output extended info into the log, with changes in data state +#define WARNING Print // use simple Print for warnings (instead of a built-in format with line numbers etc.) +#include + +input ulong Magic = 1234567890; +input int Distance2SLTP = 0; // Distance to SL/TP in points (0 = no) +input ENUM_ORDER_TYPE_TIME Expiration = ORDER_TIME_GTC; +input datetime Until = 0; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(AccountInfoInteger(ACCOUNT_TRADE_MODE) != ACCOUNT_TRADE_MODE_DEMO) + { + Alert("This is a test EA! Run it on a DEMO account only!"); + return INIT_FAILED; + } + + // setup timer for postponed execution + EventSetTimer(1); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Prepare MqlTradeRequest struct and call OrderSend with it | +//| to place a pending order of specific type with respect to | +//| given daily price range | +//+------------------------------------------------------------------+ +bool PlaceOrder(const ENUM_ORDER_TYPE type, const double range) +{ + bool success = false; + MqlTradeRequestSync request; + + // distance for orders of different types from current market price, + // indices are ENUM_ORDER_TYPE + static double coefficients[] = + { + 0 , // ORDER_TYPE_BUY - not used + 0 , // ORDER_TYPE_SELL - not used + -0.5, // ORDER_TYPE_BUY_LIMIT - below price + +0.5, // ORDER_TYPE_SELL_LIMIT - above price + +1.0, // ORDER_TYPE_BUY_STOP - far above price + -1.0, // ORDER_TYPE_SELL_STOP - far below price + +0.7, // ORDER_TYPE_BUY_STOP_LIMIT - middle price above current + -0.7, // ORDER_TYPE_SELL_STOP_LIMIT - middle price below current + 0 , // ORDER_TYPE_CLOSE_BY - not used + }; + + // default values + const double volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + const double price = TU::GetCurrentPrice(type) + range * coefficients[type]; + const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + // origin is filled only for *_STOP_LIMIT-orders + const bool stopLimit = type == ORDER_TYPE_BUY_STOP_LIMIT || type == ORDER_TYPE_SELL_STOP_LIMIT; + const double origin = stopLimit ? TU::GetCurrentPrice(type) : 0; + + TU::TradeDirection dir(type); + const double stop = Distance2SLTP == 0 ? 0 : + dir.negative(stopLimit ? origin : price, Distance2SLTP * point); + const double take = Distance2SLTP == 0 ? 0 : + dir.positive(stopLimit ? origin : price, Distance2SLTP * point); + + // Fill the struct: + // Here we assign the required field 'type' directly, but do it only + // to place many orders of different types in a loop, + // and don't want a switch to call different methods like + // buyStop/sellStop/buyLimit/sellLimit etc. + // Instead we call the underlying '_pending' method. + request.type = type; // required field (here filled directly only for the purpose of this demo) + request.magic = Magic; // optional field (always filled directly, when necessary) + + // Normally you should call specific public methods - + // buyStop/sellStop/buyLimit/sellLimit etc (see PendingOrderSend.mq5) + // to prepare an order of corresponding type + + ResetLastError(); + // send request and wait for results + const ulong order = request._pending(_Symbol, volume, price, stop, take, Expiration, Until, origin); + if(order != 0 && request.completed()) + { + Print("OK order placed: #=", order); + success = true; + } + Print(TU::StringOf(request)); + Print(TU::StringOf(request.result)); + return success; +} + +//+------------------------------------------------------------------+ +//| Timer event handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + // once executed, wait for another setup from user + EventKillTimer(); + + const double range = iHigh(_Symbol, PERIOD_D1, 1) - iLow(_Symbol, PERIOD_D1, 1); + Print("Autodetected daily range: ", TU::StringOf(range)); + + int count = 0; + for(ENUM_ORDER_TYPE i = ORDER_TYPE_BUY_LIMIT; i <= ORDER_TYPE_SELL_STOP_LIMIT; ++i) + { + count += PlaceOrder(i, range); + } + + if(count > 0) + { + Alert(StringFormat("%d pending orders placed - remove them manually, please", count)); + } +} +//+------------------------------------------------------------------+ +/* + example output (EURUSD, default settings): + + Autodetected daily range: 0.01413 + OK order placed: #=1282032135 + TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.08824, ORDER_TIME_GTC, M=1234567890 + DONE, #=1282032135, V=0.01, Request executed, Req=73 + OK order placed: #=1282032136 + TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10238, ORDER_TIME_GTC, M=1234567890 + DONE, #=1282032136, V=0.01, Request executed, Req=74 + OK order placed: #=1282032138 + TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, @ 1.10944, ORDER_TIME_GTC, M=1234567890 + DONE, #=1282032138, V=0.01, Request executed, Req=75 + OK order placed: #=1282032141 + TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP, V=0.01, ORDER_FILLING_FOK, @ 1.08118, ORDER_TIME_GTC, M=1234567890 + DONE, #=1282032141, V=0.01, Request executed, Req=76 + OK order placed: #=1282032142 + TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10520, X=1.09531, ORDER_TIME_GTC, M=1234567890 + DONE, #=1282032142, V=0.01, Request executed, Req=77 + OK order placed: #=1282032144 + TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.08542, X=1.09531, ORDER_TIME_GTC, M=1234567890 + DONE, #=1282032144, V=0.01, Request executed, Req=78 + Alert: 6 pending orders placed - remove them manually, please + +*/ +//+------------------------------------------------------------------+ diff --git a/Aroon Indicator EA.mq5 b/Aroon Indicator EA.mq5 new file mode 100644 index 0000000..2f00bb4 --- /dev/null +++ b/Aroon Indicator EA.mq5 @@ -0,0 +1,35 @@ +//+------------------------------------------------------------------+ +//| Aroon Indicator EA.mq5 | +//| Copyright 2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2024, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.00" +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { +//--- + +//--- + return(INIT_SUCCEEDED); + } +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { +//--- + + } +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() + { +//--- + + } +//+------------------------------------------------------------------+ diff --git a/BandOsMA.mq5 b/BandOsMA.mq5 new file mode 100644 index 0000000..e8a53bd --- /dev/null +++ b/BandOsMA.mq5 @@ -0,0 +1,415 @@ +//+------------------------------------------------------------------+ +//| BandOsMA.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trading strategy based on OsMA, BBands and MA indicators." + +#include +#include +#include +#include +#include +#include +#include + +// #define USE_R2_CRITERION // uncomment this to use R2 +#ifdef USE_R2_CRITERION +#include +#endif + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ + +input group "C O M M O N S E T T I N G S" +sinput ulong Magic = 1234567890; +input double Lots = 0.01; +input int StopLoss = 1000; + +input group "O S M A S E T T I N G S" +input int FastOsMA = 12; +input int SlowOsMA = 26; +input int SignalOsMA = 9; +input ENUM_APPLIED_PRICE PriceOsMA = PRICE_TYPICAL; + +input group "B B A N D S S E T T I N G S" +input int BandsMA = 26; +input int BandsShift = 0; +input double BandsDeviation = 2.0; + +input group "M A S E T T I N G S" +input int PeriodMA = 10; +input int ShiftMA = 0; +input ENUM_MA_METHOD MethodMA = MODE_SMA; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading | +//+------------------------------------------------------------------+ +interface TradingStrategy +{ + virtual bool trade(void); +}; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading signals | +//+------------------------------------------------------------------+ +interface TradingSignal +{ + virtual int signal(void); +}; + +//+------------------------------------------------------------------+ +//| Trade signal detector based on 3 indicators | +//+------------------------------------------------------------------+ +class BandOsMaSignal: public TradingSignal +{ + int hOsMA, hBands, hMA; + int direction; +public: + BandOsMaSignal(const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price, + const int bands, const int shift, const double deviation, + const int period, const int x, ENUM_MA_METHOD method) + { + hOsMA = iOsMA(_Symbol, _Period, fast, slow, signal, price); + hBands = iBands(_Symbol, _Period, bands, shift, deviation, hOsMA); + hMA = iMA(_Symbol, _Period, period, x, method, hOsMA); + direction = 0; + } + + ~BandOsMaSignal() + { + IndicatorRelease(hMA); + IndicatorRelease(hBands); + IndicatorRelease(hOsMA); + } + + virtual int signal(void) override + { + double osma[2], upper[2], lower[2], ma[2]; + // copy 2 values from bars 1 and 2 for every indicator + if(CopyBuffer(hOsMA, 0, 1, 2, osma) != 2) return 0; + if(CopyBuffer(hBands, UPPER_BAND, 1, 2, upper) != 2) return 0; + if(CopyBuffer(hBands, LOWER_BAND, 1, 2, lower) != 2) return 0; + if(CopyBuffer(hMA, 0, 1, 2, ma) != 2) return 0; + + // if there was a signal, check if it's over + if(direction != 0) + { + if(direction > 0) + { + if(osma[0] >= ma[0] && osma[1] < ma[1]) + { + direction = 0; + } + } + else + { + if(osma[0] <= ma[0] && osma[1] > ma[1]) + { + direction = 0; + } + } + } + + // in any case check for new signals + if(osma[0] <= lower[0] && osma[1] > lower[1]) + { + direction = +1; + } + else if(osma[0] >= upper[0] && osma[1] < upper[1]) + { + direction = -1; + } + + return direction; + } +}; + +//+------------------------------------------------------------------+ +//| Main class with trading strategy | +//+------------------------------------------------------------------+ +class SimpleStrategy: public TradingStrategy +{ +protected: + AutoPtr position; + AutoPtr trailing; + AutoPtr command; + + const int stopLoss; + const ulong magic; + const double lots; + + datetime lastBar; + +public: + SimpleStrategy(TradingSignal *signal, const ulong m, const int sl, const double v): + command(signal), magic(m), stopLoss(sl), lots(v), lastBar(0) + { + // pick up existing positions (if any) + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_MAGIC, magic).let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + if(n > 1) + { + Alert(StringFormat("Too many positions: %d", n)); + // TODO: close old positions + } + else if(n > 0) + { + position = new PositionState(tickets[0]); + if(stopLoss) + { + trailing = new TrailingStop(tickets[0], stopLoss, stopLoss / 50); + } + } + } + + virtual bool trade() override + { + // work only once per bar, at its opening + if(lastBar == iTime(_Symbol, _Period, 0)) return false; + + int s = command[].signal(); // get the signal + + ulong ticket = 0; + + if(position[] != NULL) + { + if(position[].refresh()) // position still exists + { + // the signal is reversed or does not exist anymore + if((position[].get(POSITION_TYPE) == POSITION_TYPE_BUY && s != +1) + || (position[].get(POSITION_TYPE) == POSITION_TYPE_SELL && s != -1)) + { + PrintFormat("Signal lost: %d for position %d %lld", + s, position[].get(POSITION_TYPE), position[].get(POSITION_TICKET)); + if(close(position[].get(POSITION_TICKET))) + { + position = NULL; + } + else + { + position[].refresh(); // make sure 'ready' flag is dropped if closed anyway + } + } + else + { + position[].update(); + if(trailing[]) trailing[].trail(); + } + } + else // position closed + { + position = NULL; + } + } + + if(position[] == NULL) + { + if(s != 0) + { + ticket = (s == +1) ? openBuy() : openSell(); + } + } + + if(ticket > 0) // new position is just opened + { + position = new PositionState(ticket); + if(stopLoss) + { + trailing = new TrailingStop(ticket, stopLoss, stopLoss / 50); + } + } + + lastBar = iTime(_Symbol, _Period, 0); + + return true; + } + +protected: + void prepare(MqlTradeRequestSync &request) + { + request.deviation = stopLoss / 50; + request.magic = magic; + } + + ulong postprocess(MqlTradeRequestSync &request) + { + if(request.completed()) + { + return request.result.position; + } + return 0; + } + + ulong openBuy() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_ASK); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.buy(_Symbol, lots, price, + stopLoss ? price - stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + ulong openSell() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_BID); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.sell(_Symbol, lots, price, + stopLoss ? price + stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + bool close(const ulong ticket) + { + MqlTradeRequestSync request; + prepare(request); + return request.close(ticket) && postprocess(request); + } +}; + +//+------------------------------------------------------------------+ +//| Global pointer for the pool of strategies | +//+------------------------------------------------------------------+ +AutoPtr strategy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT; + strategy = new SimpleStrategy( + new BandOsMaSignal(FastOsMA, SlowOsMA, SignalOsMA, PriceOsMA, + BandsMA, BandsShift, BandsDeviation, + PeriodMA, ShiftMA, MethodMA), + Magic, StopLoss, Lots); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(strategy[] != NULL) + { + strategy[].trade(); + } +} + +//+------------------------------------------------------------------+ +//| Helper struct to hold and request all tester stats | +//+------------------------------------------------------------------+ +struct TesterRecord +{ + string feature; + double value; + + static void fill(TesterRecord &stats[]) + { + ResetLastError(); + for(int i = 0; ; ++i) + { + const double v = TesterStatistics((ENUM_STATISTICS)i); + if(_LastError) return; + TesterRecord t = {EnumToString((ENUM_STATISTICS)i), v}; + PUSH(stats, t); + } + } +}; + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + TesterRecord stats[]; + TesterRecord::fill(stats); + ArrayPrint(stats, 2); +} + +//+------------------------------------------------------------------+ +double sign(const double x) +{ + return x > 0 ? +1 : (x < 0 ? -1 : 0); +} + +//+------------------------------------------------------------------+ +//| Tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ +#ifdef USE_R2_CRITERION + return GetR2onBalanceCurve(); +#else + const double profit = TesterStatistics(STAT_PROFIT); + return sign(profit) * sqrt(fabs(profit)) + * sqrt(TesterStatistics(STAT_PROFIT_FACTOR)) + * sqrt(TesterStatistics(STAT_TRADES)) + * sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO))); +#endif +} + +#ifdef USE_R2_CRITERION + +#define STAT_PROPS 4 + +//+------------------------------------------------------------------+ +//| Build balance curve and estimate R2 for it | +//+------------------------------------------------------------------+ +double GetR2onBalanceCurve() +{ + HistorySelect(0, LONG_MAX); + + const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] = + { + DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE + }; + double expenses[][STAT_PROPS]; + ulong tickets[]; // used here only to match 'select' prototype, but helpful for debug + + DealFilter filter; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE) + .select(props, tickets, expenses); + + const int n = ArraySize(tickets); + + double balance[]; + + ArrayResize(balance, n + 1); + balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT); + + for(int i = 0; i < n; ++i) + { + double result = 0; + for(int j = 0; j < STAT_PROPS; ++j) + { + result += expenses[i][j]; + } + balance[i + 1] = result + balance[i]; + } + const double r2 = RSquaredTest(balance); + return r2 * 100; +} +#endif +//+------------------------------------------------------------------+ diff --git a/BandOsMACustom.mq5 b/BandOsMACustom.mq5 new file mode 100644 index 0000000..c706eec --- /dev/null +++ b/BandOsMACustom.mq5 @@ -0,0 +1,546 @@ +//+------------------------------------------------------------------+ +//| BandOsMACustom.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trading strategy based on OsMA, BBands and MA indicators." + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USE_R2_CRITERION +#ifdef USE_R2_CRITERION +#include +#endif + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ + +input group "C O M M O N S E T T I N G S" +sinput ulong Magic = 1234567890; +input double Lots = 0.01; +input int StopLoss = 1000; +input string WorkSymbol = ""; + +input group "O S M A S E T T I N G S" +input int FastOsMA = 12; +input int SlowOsMA = 26; +input int SignalOsMA = 9; +input ENUM_APPLIED_PRICE PriceOsMA = PRICE_TYPICAL; + +input group "B B A N D S S E T T I N G S" +input int BandsMA = 26; +input int BandsShift = 0; +input double BandsDeviation = 2.0; + +input group "M A S E T T I N G S" +input int PeriodMA = 10; +input int ShiftMA = 0; +input ENUM_MA_METHOD MethodMA = MODE_SMA; + +input group "A U X I L I A R Y" +sinput int FastSlowCombo4Optimization = 0; // (reserved for optimization) +sinput ulong FastShadow4Optimization = 0; // (reserved for optimization) +sinput ulong SlowShadow4Optimization = 0; // (reserved for optimization) +sinput ulong StepsShadow4Optimization = 0; // (reserved for optimization) + + +//+------------------------------------------------------------------+ +//| Simple common interface for trading | +//+------------------------------------------------------------------+ +interface TradingStrategy +{ + virtual bool trade(void); +}; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading signals | +//+------------------------------------------------------------------+ +interface TradingSignal +{ + virtual int signal(void); +}; + +//+------------------------------------------------------------------+ +//| Trade signal detector based on 3 indicators | +//+------------------------------------------------------------------+ +class BandOsMaSignal: public TradingSignal +{ + int hOsMA, hBands, hMA; + int direction; +public: + BandOsMaSignal(const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price, + const int bands, const int shift, const double deviation, + const int period, const int x, ENUM_MA_METHOD method) + { + hOsMA = iOsMA(_Symbol, _Period, fast, slow, signal, price); + hBands = iBands(_Symbol, _Period, bands, shift, deviation, hOsMA); + hMA = iMA(_Symbol, _Period, period, x, method, hOsMA); + direction = 0; + } + + ~BandOsMaSignal() + { + IndicatorRelease(hMA); + IndicatorRelease(hBands); + IndicatorRelease(hOsMA); + } + + virtual int signal(void) override + { + double osma[2], upper[2], lower[2], ma[2]; + // copy 2 values from bars 1 and 2 for every indicator + if(CopyBuffer(hOsMA, 0, 1, 2, osma) != 2) return 0; + if(CopyBuffer(hBands, UPPER_BAND, 1, 2, upper) != 2) return 0; + if(CopyBuffer(hBands, LOWER_BAND, 1, 2, lower) != 2) return 0; + if(CopyBuffer(hMA, 0, 1, 2, ma) != 2) return 0; + + // if there was a signal, check if it's over + if(direction != 0) + { + if(direction > 0) + { + if(osma[0] >= ma[0] && osma[1] < ma[1]) + { + direction = 0; + } + } + else + { + if(osma[0] <= ma[0] && osma[1] > ma[1]) + { + direction = 0; + } + } + } + + // in any case check for new signals + if(osma[0] <= lower[0] && osma[1] > lower[1]) + { + direction = +1; + } + else if(osma[0] >= upper[0] && osma[1] < upper[1]) + { + direction = -1; + } + + return direction; + } +}; + +//+------------------------------------------------------------------+ +//| Main class with trading strategy | +//+------------------------------------------------------------------+ +class SimpleStrategy: public TradingStrategy +{ +protected: + AutoPtr position; + AutoPtr trailing; + AutoPtr command; + + const int stopLoss; + const ulong magic; + const double lots; + + datetime lastBar; + +public: + SimpleStrategy(TradingSignal *signal, const ulong m, const int sl, const double v): + command(signal), magic(m), stopLoss(sl), lots(v), lastBar(0) + { + // pick up existing positions (if any) + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_MAGIC, magic).let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + if(n > 1) + { + Alert(StringFormat("Too many positions: %d", n)); + // TODO: close old positions + } + else if(n > 0) + { + position = new PositionState(tickets[0]); + if(stopLoss) + { + trailing = new TrailingStop(tickets[0], stopLoss, stopLoss / 50); + } + } + } + + virtual bool trade() override + { + // work only once per bar, at its opening + if(lastBar == iTime(_Symbol, _Period, 0)) return false; + + int s = command[].signal(); // get the signal + + ulong ticket = 0; + + if(position[] != NULL) + { + if(position[].refresh()) // position still exists + { + // the signal is reversed or does not exist anymore + if((position[].get(POSITION_TYPE) == POSITION_TYPE_BUY && s != +1) + || (position[].get(POSITION_TYPE) == POSITION_TYPE_SELL && s != -1)) + { + PrintFormat("Signal lost: %d for position %d %lld", + s, position[].get(POSITION_TYPE), position[].get(POSITION_TICKET)); + if(close(position[].get(POSITION_TICKET))) + { + position = NULL; + } + else + { + position[].refresh(); // make sure 'ready' flag is dropped if closed anyway + } + } + else + { + position[].update(); + if(trailing[]) trailing[].trail(); + } + } + else // position closed + { + position = NULL; + } + } + + if(position[] == NULL) + { + if(s != 0) + { + ticket = (s == +1) ? openBuy() : openSell(); + } + } + + if(ticket > 0) // new position is just opened + { + position = new PositionState(ticket); + if(stopLoss) + { + trailing = new TrailingStop(ticket, stopLoss, stopLoss / 50); + } + } + + lastBar = iTime(_Symbol, _Period, 0); + + return true; + } + +protected: + void prepare(MqlTradeRequestSync &request) + { + request.deviation = stopLoss / 50; + request.magic = magic; + } + + ulong postprocess(MqlTradeRequestSync &request) + { + if(request.completed()) + { + return request.result.position; + } + return 0; + } + + ulong openBuy() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_ASK); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.buy(_Symbol, lots, price, + stopLoss ? price - stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + ulong openSell() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_BID); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.sell(_Symbol, lots, price, + stopLoss ? price + stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + bool close(const ulong ticket) + { + MqlTradeRequestSync request; + prepare(request); + return request.close(ticket) && postprocess(request); + } +}; + +//+------------------------------------------------------------------+ +//| Global pointer for the pool of strategies | +//+------------------------------------------------------------------+ +AutoPtr strategy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT; + + // during optimization we require shadow parameters + if(MQLInfoInteger(MQL_OPTIMIZATION) && StepsShadow4Optimization == 0) + { + return INIT_PARAMETERS_INCORRECT; + } + + if(WorkSymbol != "") + { + CustomOrder::setReplacementSymbol(WorkSymbol); + + // force a chart for the work symbol to open (in visual mode only) + MqlRates rates[1]; + CopyRates(WorkSymbol, PERIOD_CURRENT, 0, 1, rates); + } + PairOfPeriods p = {FastOsMA, SlowOsMA}; // try original parameters by default + if(FastShadow4Optimization && SlowShadow4Optimization && StepsShadow4Optimization) + { + // if shadow copies are assigned, decode them and override periods settings + int FastStart = (int)(FastShadow4Optimization & 0xFFFF); + int FastStop = (int)((FastShadow4Optimization >> 16) & 0xFFFF); + int SlowStart = (int)(SlowShadow4Optimization & 0xFFFF); + int SlowStop = (int)((SlowShadow4Optimization >> 16) & 0xFFFF); + int FastStep = (int)(StepsShadow4Optimization & 0xFFFF); + int SlowStep = (int)((StepsShadow4Optimization >> 16) & 0xFFFF); + + p = Iterate(FastStart, FastStop, FastStep, + SlowStart, SlowStop, SlowStep, FastSlowCombo4Optimization); + PrintFormat("MA periods are restored from shadow: FastOsMA=%d SlowOsMA=%d", + p.fast, p.slow); + } + + strategy = new SimpleStrategy( + new BandOsMaSignal(p.fast, p.slow, SignalOsMA, PriceOsMA, + BandsMA, BandsShift, BandsDeviation, + PeriodMA, ShiftMA, MethodMA), + Magic, StopLoss, Lots); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(strategy[] != NULL) + { + strategy[].trade(); + } +} + +//+------------------------------------------------------------------+ +//| Helper struct to hold and request all tester stats | +//+------------------------------------------------------------------+ +struct TesterRecord +{ + string feature; + double value; + + static void fill(TesterRecord &stats[]) + { + ResetLastError(); + for(int i = 0; ; ++i) + { + const double v = TesterStatistics((ENUM_STATISTICS)i); + if(_LastError) return; + TesterRecord t = {EnumToString((ENUM_STATISTICS)i), v}; + PUSH(stats, t); + } + } +}; + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + TesterRecord stats[]; + TesterRecord::fill(stats); + ArrayPrint(stats, 2); +} + +//+------------------------------------------------------------------+ +double sign(const double x) +{ + return x > 0 ? +1 : (x < 0 ? -1 : 0); +} + +//+------------------------------------------------------------------+ +//| Tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ +#ifdef USE_R2_CRITERION + return GetR2onBalanceCurve(); +#else + const double profit = TesterStatistics(STAT_PROFIT); + return sign(profit) * sqrt(fabs(profit)) + * sqrt(TesterStatistics(STAT_PROFIT_FACTOR)) + * sqrt(TesterStatistics(STAT_TRADES)) + * sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO))); +#endif +} + +#ifdef USE_R2_CRITERION + +#define STAT_PROPS 4 + +//+------------------------------------------------------------------+ +//| Build balance curve and estimate R2 for it | +//+------------------------------------------------------------------+ +double GetR2onBalanceCurve() +{ + HistorySelect(0, LONG_MAX); + + const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] = + { + DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE + }; + double expenses[][STAT_PROPS]; + ulong tickets[]; // used here only to match 'select' prototype, but helpful for debug + + DealFilter filter; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE) + .select(props, tickets, expenses); + + const int n = ArraySize(tickets); + + double balance[]; + + ArrayResize(balance, n + 1); + balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT); + + for(int i = 0; i < n; ++i) + { + double result = 0; + for(int j = 0; j < STAT_PROPS; ++j) + { + result += expenses[i][j]; + } + balance[i + 1] = result + balance[i]; + } + const double r2 = RSquaredTest(balance); + return r2 * 100; +} +#endif +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| Struct for pair of MA periods (checked for correctness) | +//+------------------------------------------------------------------+ +struct PairOfPeriods +{ + int fast; + int slow; +}; + +//+------------------------------------------------------------------+ +//| Get a pair of MA periods according to given ranges and steps | +//+------------------------------------------------------------------+ +PairOfPeriods Iterate(const long start1, const long stop1, const long step1, + const long start2, const long stop2, const long step2, + const long find = -1) +{ + int count = 0; + for(int i = (int)start1; i <= (int)stop1; i += (int)step1) + { + for(int j = (int)start2; j <= (int)stop2; j += (int)step2) + { + if(i < j) + { + if(count == find) + { + PairOfPeriods p = {i, j}; + return p; + } + ++count; + } + } + } + PairOfPeriods p = {count, 0}; + return p; +} + +//+------------------------------------------------------------------+ +//| Optimization initialization handler | +//+------------------------------------------------------------------+ +void OnTesterInit() +{ + bool enabled1, enabled2; + long value1, start1, step1, stop1; + long value2, start2, step2, stop2; + // obtain optimization settings for FastOsMA and SlowOsMA + if(ParameterGetRange("FastOsMA", enabled1, value1, start1, step1, stop1) + && ParameterGetRange("SlowOsMA", enabled2, value2, start2, step2, stop2)) + { + if(enabled1 && enabled2) + { + // disable them + if(!ParameterSetRange("FastOsMA", false, value1, start1, step1, stop1) + || !ParameterSetRange("SlowOsMA", false, value2, start2, step2, stop2)) + { + Print("Can't disable optimization by FastOsMA and SlowOsMA: ", + E2S(_LastError)); + return; + } + // find out a number of correct combinations of 2 periods + PairOfPeriods p = Iterate(start1, stop1, step1, start2, stop2, step2); + const int count = p.fast; + // request optimization for this range for new shadow parameter + ParameterSetRange("FastSlowCombo4Optimization", true, 0, 0, 1, count); + PrintFormat("Parameter FastSlowCombo4Optimization is enabled with maximum: %d", + count); + // send required settings to our copy running on the agent + const ulong fast = start1 | (stop1 << 16); + const ulong slow = start2 | (stop2 << 16); + const ulong step = step1 | (step2 << 16); + ParameterSetRange("FastShadow4Optimization", false, fast, fast, 1, fast); + ParameterSetRange("SlowShadow4Optimization", false, slow, slow, 1, slow); + ParameterSetRange("StepsShadow4Optimization", false, step, step, 1, step); + } + } + else + { + Print("Can't adjust optimization by FastOsMA and SlowOsMA: ", E2S(_LastError)); + } +} + +//+------------------------------------------------------------------+ +//| Optimization finalization handler | +//| (for some reason compiler requires this handler as well, | +//| but it's useless here) | +//+------------------------------------------------------------------+ +void OnTesterDeinit() +{ +} +//+------------------------------------------------------------------+ diff --git a/BandOsMACustomSignal.mq5 b/BandOsMACustomSignal.mq5 new file mode 100644 index 0000000..ae7bd05 --- /dev/null +++ b/BandOsMACustomSignal.mq5 @@ -0,0 +1,555 @@ +//+------------------------------------------------------------------+ +//| BandOsMACustomSignal.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trading strategy based on OsMA, BBands and MA indicators." + +#include +#include +#include +#include +#include +#include +#include + +#define USE_R2_CRITERION +#ifdef USE_R2_CRITERION +#include +#endif + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ + +input group "C O M M O N S E T T I N G S" +sinput ulong Magic = 1234567890; +input double Lots = 0.01; +input int StopLoss = 1000; +input string SignalSymbol = ""; +input ENUM_TIMEFRAMES SignalTimeframe = PERIOD_M1; + +input group "O S M A S E T T I N G S" +input int FastOsMA = 12; +input int SlowOsMA = 26; +input int SignalOsMA = 9; +input ENUM_APPLIED_PRICE PriceOsMA = PRICE_TYPICAL; + +input group "B B A N D S S E T T I N G S" +input int BandsMA = 26; +input int BandsShift = 0; +input double BandsDeviation = 2.0; + +input group "M A S E T T I N G S" +input int PeriodMA = 10; +input int ShiftMA = 0; +input ENUM_MA_METHOD MethodMA = MODE_SMA; + +input group "A U X I L I A R Y" +sinput int FastSlowCombo4Optimization = 0; // (reserved for optimization) +sinput ulong FastShadow4Optimization = 0; // (reserved for optimization) +sinput ulong SlowShadow4Optimization = 0; // (reserved for optimization) +sinput ulong StepsShadow4Optimization = 0; // (reserved for optimization) + + +//+------------------------------------------------------------------+ +//| Simple common interface for trading | +//+------------------------------------------------------------------+ +interface TradingStrategy +{ + virtual bool trade(void); +}; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading signals | +//+------------------------------------------------------------------+ +interface TradingSignal +{ + virtual int signal(void); + virtual string symbol(); + virtual ENUM_TIMEFRAMES timeframe(); +}; + +//+------------------------------------------------------------------+ +//| Trade signal detector based on 3 indicators | +//+------------------------------------------------------------------+ +class BandOsMaSignal: public TradingSignal +{ + int hOsMA, hBands, hMA; + int direction; + const string _symbol; + const ENUM_TIMEFRAMES _timeframe; +public: + BandOsMaSignal(const string s, const ENUM_TIMEFRAMES tf, + const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price, + const int bands, const int shift, const double deviation, + const int period, const int x, ENUM_MA_METHOD method): _symbol(s), _timeframe(tf) + { + hOsMA = iOsMA(s, tf, fast, slow, signal, price); + hBands = iBands(s, tf, bands, shift, deviation, hOsMA); + hMA = iMA(s, tf, period, x, method, hOsMA); + direction = 0; + } + + ~BandOsMaSignal() + { + IndicatorRelease(hMA); + IndicatorRelease(hBands); + IndicatorRelease(hOsMA); + } + + virtual int signal(void) override + { + double osma[2], upper[2], lower[2], ma[2]; + // copy 2 values from bars 1 and 2 for every indicator + if(CopyBuffer(hOsMA, 0, 1, 2, osma) != 2) return 0; + if(CopyBuffer(hBands, UPPER_BAND, 1, 2, upper) != 2) return 0; + if(CopyBuffer(hBands, LOWER_BAND, 1, 2, lower) != 2) return 0; + if(CopyBuffer(hMA, 0, 1, 2, ma) != 2) return 0; + + // if there was a signal, check if it's over + if(direction != 0) + { + if(direction > 0) + { + if(osma[0] >= ma[0] && osma[1] < ma[1]) + { + direction = 0; + } + } + else + { + if(osma[0] <= ma[0] && osma[1] > ma[1]) + { + direction = 0; + } + } + } + + // in any case check for new signals + if(osma[0] <= lower[0] && osma[1] > lower[1]) + { + direction = +1; + } + else if(osma[0] >= upper[0] && osma[1] < upper[1]) + { + direction = -1; + } + + return direction; + } + + virtual string symbol() override + { + return _symbol; + } + + virtual ENUM_TIMEFRAMES timeframe() override + { + return _timeframe; + } +}; + +//+------------------------------------------------------------------+ +//| Main class with trading strategy | +//+------------------------------------------------------------------+ +class SimpleStrategy: public TradingStrategy +{ +protected: + AutoPtr position; + AutoPtr trailing; + AutoPtr command; + + const int stopLoss; + const ulong magic; + const double lots; + + datetime lastBar; + +public: + SimpleStrategy(TradingSignal *signal, const ulong m, const int sl, const double v): + command(signal), magic(m), stopLoss(sl), lots(v), lastBar(0) + { + // pick up existing positions (if any) + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_MAGIC, magic).let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + if(n > 1) + { + Alert(StringFormat("Too many positions: %d", n)); + // TODO: close old positions + } + else if(n > 0) + { + position = new PositionState(tickets[0]); + if(stopLoss) + { + trailing = new TrailingStop(tickets[0], stopLoss, stopLoss / 50); + } + } + } + + virtual bool trade() override + { + // work only once per bar, at its opening + if(lastBar == iTime(command[].symbol(), command[].timeframe(), 0)) return false; + + int s = command[].signal(); // get the signal + + ulong ticket = 0; + + if(position[] != NULL) + { + if(position[].refresh()) // position still exists + { + // the signal is reversed or does not exist anymore + if((position[].get(POSITION_TYPE) == POSITION_TYPE_BUY && s != +1) + || (position[].get(POSITION_TYPE) == POSITION_TYPE_SELL && s != -1)) + { + PrintFormat("Signal lost: %d for position %d %lld", + s, position[].get(POSITION_TYPE), position[].get(POSITION_TICKET)); + if(close(position[].get(POSITION_TICKET))) + { + position = NULL; + } + else + { + position[].refresh(); // make sure 'ready' flag is dropped if closed anyway + } + } + else + { + position[].update(); + if(trailing[]) trailing[].trail(); + } + } + else // position closed + { + position = NULL; + } + } + + if(position[] == NULL) + { + if(s != 0) + { + ticket = (s == +1) ? openBuy() : openSell(); + } + } + + if(ticket > 0) // new position is just opened + { + position = new PositionState(ticket); + if(stopLoss) + { + trailing = new TrailingStop(ticket, stopLoss, stopLoss / 50); + } + } + + lastBar = iTime(_Symbol, _Period, 0); + + return true; + } + +protected: + void prepare(MqlTradeRequestSync &request) + { + request.deviation = stopLoss / 50; + request.magic = magic; + } + + ulong postprocess(MqlTradeRequestSync &request) + { + if(request.completed()) + { + return request.result.position; + } + return 0; + } + + ulong openBuy() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_ASK); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.buy(_Symbol, lots, price, + stopLoss ? price - stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + ulong openSell() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_BID); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.sell(_Symbol, lots, price, + stopLoss ? price + stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + bool close(const ulong ticket) + { + MqlTradeRequestSync request; + prepare(request); + return request.close(ticket) && postprocess(request); + } +}; + +//+------------------------------------------------------------------+ +//| Global pointer for the pool of strategies | +//+------------------------------------------------------------------+ +AutoPtr strategy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT; + + // during optimization we require shadow parameters + if(MQLInfoInteger(MQL_OPTIMIZATION) && StepsShadow4Optimization == 0) + { + return INIT_PARAMETERS_INCORRECT; + } + + PairOfPeriods p = {FastOsMA, SlowOsMA}; // try original parameters by default + if(FastShadow4Optimization && SlowShadow4Optimization && StepsShadow4Optimization) + { + // if shadow copies are assigned, decode them and override periods settings + int FastStart = (int)(FastShadow4Optimization & 0xFFFF); + int FastStop = (int)((FastShadow4Optimization >> 16) & 0xFFFF); + int SlowStart = (int)(SlowShadow4Optimization & 0xFFFF); + int SlowStop = (int)((SlowShadow4Optimization >> 16) & 0xFFFF); + int FastStep = (int)(StepsShadow4Optimization & 0xFFFF); + int SlowStep = (int)((StepsShadow4Optimization >> 16) & 0xFFFF); + + p = Iterate(FastStart, FastStop, FastStep, + SlowStart, SlowStop, SlowStep, FastSlowCombo4Optimization); + PrintFormat("MA periods are restored from shadow: FastOsMA=%d SlowOsMA=%d", + p.fast, p.slow); + } + + strategy = new SimpleStrategy( + new BandOsMaSignal(SignalSymbol != "" ? SignalSymbol : _Symbol, + SignalSymbol != "" ? SignalTimeframe : _Period, + p.fast, p.slow, SignalOsMA, PriceOsMA, + BandsMA, BandsShift, BandsDeviation, + PeriodMA, ShiftMA, MethodMA), + Magic, StopLoss, Lots); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(strategy[] != NULL) + { + strategy[].trade(); + } +} + +//+------------------------------------------------------------------+ +//| Helper struct to hold and request all tester stats | +//+------------------------------------------------------------------+ +struct TesterRecord +{ + string feature; + double value; + + static void fill(TesterRecord &stats[]) + { + ResetLastError(); + for(int i = 0; ; ++i) + { + const double v = TesterStatistics((ENUM_STATISTICS)i); + if(_LastError) return; + TesterRecord t = {EnumToString((ENUM_STATISTICS)i), v}; + PUSH(stats, t); + } + } +}; + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + TesterRecord stats[]; + TesterRecord::fill(stats); + ArrayPrint(stats, 2); +} + +//+------------------------------------------------------------------+ +double sign(const double x) +{ + return x > 0 ? +1 : (x < 0 ? -1 : 0); +} + +//+------------------------------------------------------------------+ +//| Tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ +#ifdef USE_R2_CRITERION + return GetR2onBalanceCurve(); +#else + const double profit = TesterStatistics(STAT_PROFIT); + return sign(profit) * sqrt(fabs(profit)) + * sqrt(TesterStatistics(STAT_PROFIT_FACTOR)) + * sqrt(TesterStatistics(STAT_TRADES)) + * sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO))); +#endif +} + +#ifdef USE_R2_CRITERION + +#define STAT_PROPS 4 + +//+------------------------------------------------------------------+ +//| Build balance curve and estimate R2 for it | +//+------------------------------------------------------------------+ +double GetR2onBalanceCurve() +{ + HistorySelect(0, LONG_MAX); + + const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] = + { + DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE + }; + double expenses[][STAT_PROPS]; + ulong tickets[]; // used here only to match 'select' prototype, but helpful for debug + + DealFilter filter; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE) + .select(props, tickets, expenses); + + const int n = ArraySize(tickets); + + double balance[]; + + ArrayResize(balance, n + 1); + balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT); + + for(int i = 0; i < n; ++i) + { + double result = 0; + for(int j = 0; j < STAT_PROPS; ++j) + { + result += expenses[i][j]; + } + balance[i + 1] = result + balance[i]; + } + const double r2 = RSquaredTest(balance); + return r2 * 100; +} +#endif +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| Struct for pair of MA periods (checked for correctness) | +//+------------------------------------------------------------------+ +struct PairOfPeriods +{ + int fast; + int slow; +}; + +//+------------------------------------------------------------------+ +//| Get a pair of MA periods according to given ranges and steps | +//+------------------------------------------------------------------+ +PairOfPeriods Iterate(const long start1, const long stop1, const long step1, + const long start2, const long stop2, const long step2, + const long find = -1) +{ + int count = 0; + for(int i = (int)start1; i <= (int)stop1; i += (int)step1) + { + for(int j = (int)start2; j <= (int)stop2; j += (int)step2) + { + if(i < j) + { + if(count == find) + { + PairOfPeriods p = {i, j}; + return p; + } + ++count; + } + } + } + PairOfPeriods p = {count, 0}; + return p; +} + +//+------------------------------------------------------------------+ +//| Optimization initialization handler | +//+------------------------------------------------------------------+ +void OnTesterInit() +{ + bool enabled1, enabled2; + long value1, start1, step1, stop1; + long value2, start2, step2, stop2; + // obtain optimization settings for FastOsMA and SlowOsMA + if(ParameterGetRange("FastOsMA", enabled1, value1, start1, step1, stop1) + && ParameterGetRange("SlowOsMA", enabled2, value2, start2, step2, stop2)) + { + if(enabled1 && enabled2) + { + // disable them + if(!ParameterSetRange("FastOsMA", false, value1, start1, step1, stop1) + || !ParameterSetRange("SlowOsMA", false, value2, start2, step2, stop2)) + { + Print("Can't disable optimization by FastOsMA and SlowOsMA: ", + E2S(_LastError)); + return; + } + // find out a number of correct combinations of 2 periods + PairOfPeriods p = Iterate(start1, stop1, step1, start2, stop2, step2); + const int count = p.fast; + // request optimization for this range for new shadow parameter + ParameterSetRange("FastSlowCombo4Optimization", true, 0, 0, 1, count); + PrintFormat("Parameter FastSlowCombo4Optimization is enabled with maximum: %d", + count); + // send required settings to our copy running on the agent + const ulong fast = start1 | (stop1 << 16); + const ulong slow = start2 | (stop2 << 16); + const ulong step = step1 | (step2 << 16); + ParameterSetRange("FastShadow4Optimization", false, fast, fast, 1, fast); + ParameterSetRange("SlowShadow4Optimization", false, slow, slow, 1, slow); + ParameterSetRange("StepsShadow4Optimization", false, step, step, 1, step); + } + } + else + { + Print("Can't adjust optimization by FastOsMA and SlowOsMA: ", E2S(_LastError)); + } +} + +//+------------------------------------------------------------------+ +//| Optimization finalization handler | +//| (for some reason compiler requires this handler as well, | +//| but it's useless here) | +//+------------------------------------------------------------------+ +void OnTesterDeinit() +{ +} +//+------------------------------------------------------------------+ diff --git a/BandOsMApro.mq5 b/BandOsMApro.mq5 new file mode 100644 index 0000000..2d41c72 --- /dev/null +++ b/BandOsMApro.mq5 @@ -0,0 +1,536 @@ +//+------------------------------------------------------------------+ +//| BandOsMApro.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trading strategy based on OsMA, BBands and MA indicators." + +#include +#include +#include +#include +#include +#include +#include + +#define USE_R2_CRITERION +#ifdef USE_R2_CRITERION +#include +#endif + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ + +input group "C O M M O N S E T T I N G S" +sinput ulong Magic = 1234567890; +input double Lots = 0.01; +input int StopLoss = 1000; + +input group "O S M A S E T T I N G S" +input int FastOsMA = 12; +input int SlowOsMA = 26; +input int SignalOsMA = 9; +input ENUM_APPLIED_PRICE PriceOsMA = PRICE_TYPICAL; + +input group "B B A N D S S E T T I N G S" +input int BandsMA = 26; +input int BandsShift = 0; +input double BandsDeviation = 2.0; + +input group "M A S E T T I N G S" +input int PeriodMA = 10; +input int ShiftMA = 0; +input ENUM_MA_METHOD MethodMA = MODE_SMA; + +input group "A U X I L I A R Y" +sinput int FastSlowCombo4Optimization = 0; // (reserved for optimization) +sinput ulong FastShadow4Optimization = 0; // (reserved for optimization) +sinput ulong SlowShadow4Optimization = 0; // (reserved for optimization) +sinput ulong StepsShadow4Optimization = 0; // (reserved for optimization) + + +//+------------------------------------------------------------------+ +//| Simple common interface for trading | +//+------------------------------------------------------------------+ +interface TradingStrategy +{ + virtual bool trade(void); +}; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading signals | +//+------------------------------------------------------------------+ +interface TradingSignal +{ + virtual int signal(void); +}; + +//+------------------------------------------------------------------+ +//| Trade signal detector based on 3 indicators | +//+------------------------------------------------------------------+ +class BandOsMaSignal: public TradingSignal +{ + int hOsMA, hBands, hMA; + int direction; +public: + BandOsMaSignal(const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price, + const int bands, const int shift, const double deviation, + const int period, const int x, ENUM_MA_METHOD method) + { + hOsMA = iOsMA(_Symbol, _Period, fast, slow, signal, price); + hBands = iBands(_Symbol, _Period, bands, shift, deviation, hOsMA); + hMA = iMA(_Symbol, _Period, period, x, method, hOsMA); + direction = 0; + } + + ~BandOsMaSignal() + { + IndicatorRelease(hMA); + IndicatorRelease(hBands); + IndicatorRelease(hOsMA); + } + + virtual int signal(void) override + { + double osma[2], upper[2], lower[2], ma[2]; + // copy 2 values from bars 1 and 2 for every indicator + if(CopyBuffer(hOsMA, 0, 1, 2, osma) != 2) return 0; + if(CopyBuffer(hBands, UPPER_BAND, 1, 2, upper) != 2) return 0; + if(CopyBuffer(hBands, LOWER_BAND, 1, 2, lower) != 2) return 0; + if(CopyBuffer(hMA, 0, 1, 2, ma) != 2) return 0; + + // if there was a signal, check if it's over + if(direction != 0) + { + if(direction > 0) + { + if(osma[0] >= ma[0] && osma[1] < ma[1]) + { + direction = 0; + } + } + else + { + if(osma[0] <= ma[0] && osma[1] > ma[1]) + { + direction = 0; + } + } + } + + // in any case check for new signals + if(osma[0] <= lower[0] && osma[1] > lower[1]) + { + direction = +1; + } + else if(osma[0] >= upper[0] && osma[1] < upper[1]) + { + direction = -1; + } + + return direction; + } +}; + +//+------------------------------------------------------------------+ +//| Main class with trading strategy | +//+------------------------------------------------------------------+ +class SimpleStrategy: public TradingStrategy +{ +protected: + AutoPtr position; + AutoPtr trailing; + AutoPtr command; + + const int stopLoss; + const ulong magic; + const double lots; + + datetime lastBar; + +public: + SimpleStrategy(TradingSignal *signal, const ulong m, const int sl, const double v): + command(signal), magic(m), stopLoss(sl), lots(v), lastBar(0) + { + // pick up existing positions (if any) + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_MAGIC, magic).let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + if(n > 1) + { + Alert(StringFormat("Too many positions: %d", n)); + // TODO: close old positions + } + else if(n > 0) + { + position = new PositionState(tickets[0]); + if(stopLoss) + { + trailing = new TrailingStop(tickets[0], stopLoss, stopLoss / 50); + } + } + } + + virtual bool trade() override + { + // work only once per bar, at its opening + if(lastBar == iTime(_Symbol, _Period, 0)) return false; + + int s = command[].signal(); // get the signal + + ulong ticket = 0; + + if(position[] != NULL) + { + if(position[].refresh()) // position still exists + { + // the signal is reversed or does not exist anymore + if((position[].get(POSITION_TYPE) == POSITION_TYPE_BUY && s != +1) + || (position[].get(POSITION_TYPE) == POSITION_TYPE_SELL && s != -1)) + { + PrintFormat("Signal lost: %d for position %d %lld", + s, position[].get(POSITION_TYPE), position[].get(POSITION_TICKET)); + if(close(position[].get(POSITION_TICKET))) + { + position = NULL; + } + else + { + position[].refresh(); // make sure 'ready' flag is dropped if closed anyway + } + } + else + { + position[].update(); + if(trailing[]) trailing[].trail(); + } + } + else // position closed + { + position = NULL; + } + } + + if(position[] == NULL) + { + if(s != 0) + { + ticket = (s == +1) ? openBuy() : openSell(); + } + } + + if(ticket > 0) // new position is just opened + { + position = new PositionState(ticket); + if(stopLoss) + { + trailing = new TrailingStop(ticket, stopLoss, stopLoss / 50); + } + } + + lastBar = iTime(_Symbol, _Period, 0); + + return true; + } + +protected: + void prepare(MqlTradeRequestSync &request) + { + request.deviation = stopLoss / 50; + request.magic = magic; + } + + ulong postprocess(MqlTradeRequestSync &request) + { + if(request.completed()) + { + return request.result.position; + } + return 0; + } + + ulong openBuy() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_ASK); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.buy(_Symbol, lots, price, + stopLoss ? price - stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + ulong openSell() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_BID); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.sell(_Symbol, lots, price, + stopLoss ? price + stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + bool close(const ulong ticket) + { + MqlTradeRequestSync request; + prepare(request); + return request.close(ticket) && postprocess(request); + } +}; + +//+------------------------------------------------------------------+ +//| Global pointer for the pool of strategies | +//+------------------------------------------------------------------+ +AutoPtr strategy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT; + + // during optimization we require shadow parameters + if(MQLInfoInteger(MQL_OPTIMIZATION) && StepsShadow4Optimization == 0) + { + return INIT_PARAMETERS_INCORRECT; + } + + PairOfPeriods p = {FastOsMA, SlowOsMA}; // try original parameters by default + if(FastShadow4Optimization && SlowShadow4Optimization && StepsShadow4Optimization) + { + // if shadow copies are assigned, decode them and override periods settings + int FastStart = (int)(FastShadow4Optimization & 0xFFFF); + int FastStop = (int)((FastShadow4Optimization >> 16) & 0xFFFF); + int SlowStart = (int)(SlowShadow4Optimization & 0xFFFF); + int SlowStop = (int)((SlowShadow4Optimization >> 16) & 0xFFFF); + int FastStep = (int)(StepsShadow4Optimization & 0xFFFF); + int SlowStep = (int)((StepsShadow4Optimization >> 16) & 0xFFFF); + + p = Iterate(FastStart, FastStop, FastStep, + SlowStart, SlowStop, SlowStep, FastSlowCombo4Optimization); + PrintFormat("MA periods are restored from shadow: FastOsMA=%d SlowOsMA=%d", + p.fast, p.slow); + } + + strategy = new SimpleStrategy( + new BandOsMaSignal(p.fast, p.slow, SignalOsMA, PriceOsMA, + BandsMA, BandsShift, BandsDeviation, + PeriodMA, ShiftMA, MethodMA), + Magic, StopLoss, Lots); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(strategy[] != NULL) + { + strategy[].trade(); + } +} + +//+------------------------------------------------------------------+ +//| Helper struct to hold and request all tester stats | +//+------------------------------------------------------------------+ +struct TesterRecord +{ + string feature; + double value; + + static void fill(TesterRecord &stats[]) + { + ResetLastError(); + for(int i = 0; ; ++i) + { + const double v = TesterStatistics((ENUM_STATISTICS)i); + if(_LastError) return; + TesterRecord t = {EnumToString((ENUM_STATISTICS)i), v}; + PUSH(stats, t); + } + } +}; + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + TesterRecord stats[]; + TesterRecord::fill(stats); + ArrayPrint(stats, 2); +} + +//+------------------------------------------------------------------+ +double sign(const double x) +{ + return x > 0 ? +1 : (x < 0 ? -1 : 0); +} + +//+------------------------------------------------------------------+ +//| Tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ +#ifdef USE_R2_CRITERION + return GetR2onBalanceCurve(); +#else + const double profit = TesterStatistics(STAT_PROFIT); + return sign(profit) * sqrt(fabs(profit)) + * sqrt(TesterStatistics(STAT_PROFIT_FACTOR)) + * sqrt(TesterStatistics(STAT_TRADES)) + * sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO))); +#endif +} + +#ifdef USE_R2_CRITERION + +#define STAT_PROPS 4 + +//+------------------------------------------------------------------+ +//| Build balance curve and estimate R2 for it | +//+------------------------------------------------------------------+ +double GetR2onBalanceCurve() +{ + HistorySelect(0, LONG_MAX); + + const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] = + { + DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE + }; + double expenses[][STAT_PROPS]; + ulong tickets[]; // used here only to match 'select' prototype, but helpful for debug + + DealFilter filter; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE) + .select(props, tickets, expenses); + + const int n = ArraySize(tickets); + + double balance[]; + + ArrayResize(balance, n + 1); + balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT); + + for(int i = 0; i < n; ++i) + { + double result = 0; + for(int j = 0; j < STAT_PROPS; ++j) + { + result += expenses[i][j]; + } + balance[i + 1] = result + balance[i]; + } + const double r2 = RSquaredTest(balance); + return r2 * 100; +} +#endif +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| Struct for pair of MA periods (checked for correctness) | +//+------------------------------------------------------------------+ +struct PairOfPeriods +{ + int fast; + int slow; +}; + +//+------------------------------------------------------------------+ +//| Get a pair of MA periods according to given ranges and steps | +//+------------------------------------------------------------------+ +PairOfPeriods Iterate(const long start1, const long stop1, const long step1, + const long start2, const long stop2, const long step2, + const long find = -1) +{ + int count = 0; + for(int i = (int)start1; i <= (int)stop1; i += (int)step1) + { + for(int j = (int)start2; j <= (int)stop2; j += (int)step2) + { + if(i < j) + { + if(count == find) + { + PairOfPeriods p = {i, j}; + return p; + } + ++count; + } + } + } + PairOfPeriods p = {count, 0}; + return p; +} + +//+------------------------------------------------------------------+ +//| Optimization initialization handler | +//+------------------------------------------------------------------+ +void OnTesterInit() +{ + bool enabled1, enabled2; + long value1, start1, step1, stop1; + long value2, start2, step2, stop2; + // obtain optimization settings for FastOsMA and SlowOsMA + if(ParameterGetRange("FastOsMA", enabled1, value1, start1, step1, stop1) + && ParameterGetRange("SlowOsMA", enabled2, value2, start2, step2, stop2)) + { + if(enabled1 && enabled2) + { + // disable them + if(!ParameterSetRange("FastOsMA", false, value1, start1, step1, stop1) + || !ParameterSetRange("SlowOsMA", false, value2, start2, step2, stop2)) + { + Print("Can't disable optimization by FastOsMA and SlowOsMA: ", + E2S(_LastError)); + return; + } + // find out a number of correct combinations of 2 periods + PairOfPeriods p = Iterate(start1, stop1, step1, start2, stop2, step2); + const int count = p.fast; + // request optimization for this range for new shadow parameter + ParameterSetRange("FastSlowCombo4Optimization", true, 0, 0, 1, count); + PrintFormat("Parameter FastSlowCombo4Optimization is enabled with maximum: %d", + count); + // send required settings to our copy running on the agent + const ulong fast = start1 | (stop1 << 16); + const ulong slow = start2 | (stop2 << 16); + const ulong step = step1 | (step2 << 16); + ParameterSetRange("FastShadow4Optimization", false, fast, fast, 1, fast); + ParameterSetRange("SlowShadow4Optimization", false, slow, slow, 1, slow); + ParameterSetRange("StepsShadow4Optimization", false, step, step, 1, step); + } + } + else + { + Print("Can't adjust optimization by FastOsMA and SlowOsMA: ", E2S(_LastError)); + } +} + +//+------------------------------------------------------------------+ +//| Optimization finalization handler | +//| (for some reason compiler requires this handler as well, | +//| but it's useless here) | +//+------------------------------------------------------------------+ +void OnTesterDeinit() +{ +} +//+------------------------------------------------------------------+ diff --git a/BandOsMAprofile (1).mq5 b/BandOsMAprofile (1).mq5 new file mode 100644 index 0000000..a8fcd86 --- /dev/null +++ b/BandOsMAprofile (1).mq5 @@ -0,0 +1,565 @@ +//+------------------------------------------------------------------+ +//| BandOsMAprofile.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trading strategy based on OsMA, BBands and MA indicators." + +#define SETTINGS_FILE "BandOsMAprofile.csv" +#property tester_file SETTINGS_FILE +#property tester_set "/Presets/MQL5Book/BandOsMA.set" + +#include +#include +#include +#include +#include +#include +#include + +#define USE_R2_CRITERION +#ifdef USE_R2_CRITERION +#include +#endif + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ + +input group "C O M M O N S E T T I N G S" +sinput ulong Magic = 1234567890; +input double Lots = 0.01; +input int StopLoss = 1000; + +input group "O S M A S E T T I N G S" +input int FastOsMA = 12; +input int SlowOsMA = 26; +input int SignalOsMA = 9; +input ENUM_APPLIED_PRICE PriceOsMA = PRICE_TYPICAL; + +input group "B B A N D S S E T T I N G S" +input int BandsMA = 26; +input int BandsShift = 0; +input double BandsDeviation = 2.0; + +input group "M A S E T T I N G S" +input int PeriodMA = 10; +input int ShiftMA = 0; +input ENUM_MA_METHOD MethodMA = MODE_SMA; + +input group "A U X I L I A R Y" +sinput int FastSlowCombo4Optimization = 0; // (reserved for optimization) +const string SettingsFile = SETTINGS_FILE; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading | +//+------------------------------------------------------------------+ +interface TradingStrategy +{ + virtual bool trade(void); +}; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading signals | +//+------------------------------------------------------------------+ +interface TradingSignal +{ + virtual int signal(void); +}; + +//+------------------------------------------------------------------+ +//| Trade signal detector based on 3 indicators | +//+------------------------------------------------------------------+ +class BandOsMaSignal: public TradingSignal +{ + int hOsMA, hBands, hMA; + int direction; +public: + BandOsMaSignal(const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price, + const int bands, const int shift, const double deviation, + const int period, const int x, ENUM_MA_METHOD method) + { + hOsMA = iOsMA(_Symbol, _Period, fast, slow, signal, price); + hBands = iBands(_Symbol, _Period, bands, shift, deviation, hOsMA); + hMA = iMA(_Symbol, _Period, period, x, method, hOsMA); + direction = 0; + } + + ~BandOsMaSignal() + { + IndicatorRelease(hMA); + IndicatorRelease(hBands); + IndicatorRelease(hOsMA); + } + + virtual int signal(void) override + { + double osma[2], upper[2], lower[2], ma[2]; + // copy 2 values from bars 1 and 2 for every indicator + if(CopyBuffer(hOsMA, 0, 1, 2, osma) != 2) return 0; + if(CopyBuffer(hBands, UPPER_BAND, 1, 2, upper) != 2) return 0; + if(CopyBuffer(hBands, LOWER_BAND, 1, 2, lower) != 2) return 0; + if(CopyBuffer(hMA, 0, 1, 2, ma) != 2) return 0; + + // if there was a signal, check if it's over + if(direction != 0) + { + if(direction > 0) + { + if(osma[0] >= ma[0] && osma[1] < ma[1]) + { + direction = 0; + } + } + else + { + if(osma[0] <= ma[0] && osma[1] > ma[1]) + { + direction = 0; + } + } + } + + // in any case check for new signals + if(osma[0] <= lower[0] && osma[1] > lower[1]) + { + direction = +1; + } + else if(osma[0] >= upper[0] && osma[1] < upper[1]) + { + direction = -1; + } + + return direction; + } +}; + +//+------------------------------------------------------------------+ +//| Main class with trading strategy | +//+------------------------------------------------------------------+ +class SimpleStrategy: public TradingStrategy +{ +protected: + AutoPtr position; + AutoPtr trailing; + AutoPtr command; + + const int stopLoss; + const ulong magic; + const double lots; + + datetime lastBar; + +public: + SimpleStrategy(TradingSignal *signal, const ulong m, const int sl, const double v): + command(signal), magic(m), stopLoss(sl), lots(v), lastBar(0) + { + // pick up existing positions (if any) + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_MAGIC, magic).let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + if(n > 1) + { + Alert(StringFormat("Too many positions: %d", n)); + // TODO: close old positions + } + else if(n > 0) + { + position = new PositionState(tickets[0]); + if(stopLoss) + { + trailing = new TrailingStop(tickets[0], stopLoss, stopLoss / 50); + } + } + } + + virtual bool trade() override + { + // work only once per bar, at its opening + if(lastBar == iTime(_Symbol, _Period, 0)) return false; + + int s = command[].signal(); // get the signal + + ulong ticket = 0; + + if(position[] != NULL) + { + if(position[].refresh()) // position still exists + { + // the signal is reversed or does not exist anymore + if((position[].get(POSITION_TYPE) == POSITION_TYPE_BUY && s != +1) + || (position[].get(POSITION_TYPE) == POSITION_TYPE_SELL && s != -1)) + { + PrintFormat("Signal lost: %d for position %d %lld", + s, position[].get(POSITION_TYPE), position[].get(POSITION_TICKET)); + if(close(position[].get(POSITION_TICKET))) + { + position = NULL; + } + else + { + position[].refresh(); // make sure 'ready' flag is dropped if closed anyway + } + } + else + { + position[].update(); + if(trailing[]) trailing[].trail(); + } + } + else // position closed + { + position = NULL; + } + } + + if(position[] == NULL) + { + if(s != 0) + { + ticket = (s == +1) ? openBuy() : openSell(); + } + } + + if(ticket > 0) // new position is just opened + { + position = new PositionState(ticket); + if(stopLoss) + { + trailing = new TrailingStop(ticket, stopLoss, stopLoss / 50); + } + } + + lastBar = iTime(_Symbol, _Period, 0); + + return true; + } + +protected: + void prepare(MqlTradeRequestSync &request) + { + request.deviation = stopLoss / 50; + request.magic = magic; + } + + ulong postprocess(MqlTradeRequestSync &request) + { + if(request.completed()) + { + return request.result.position; + } + return 0; + } + + ulong openBuy() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_ASK); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.buy(_Symbol, lots, price, + stopLoss ? price - stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + ulong openSell() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_BID); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.sell(_Symbol, lots, price, + stopLoss ? price + stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + bool close(const ulong ticket) + { + MqlTradeRequestSync request; + prepare(request); + return request.close(ticket) && postprocess(request); + } +}; + +//+------------------------------------------------------------------+ +//| Global pointer for the pool of strategies | +//+------------------------------------------------------------------+ +AutoPtr strategy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT; + + PairOfPeriods p = {FastOsMA, SlowOsMA}; // try original parameters by default + int handle = FileOpen(SettingsFile, FILE_READ | FILE_TXT | FILE_ANSI); + + // during optimization we require shadow parameters + if(MQLInfoInteger(MQL_OPTIMIZATION) && handle == INVALID_HANDLE) + { + return INIT_PARAMETERS_INCORRECT; + } + + if(handle != INVALID_HANDLE) + { + if(FastSlowCombo4Optimization != -1) + { + // if shadow copies are assigned, decode them and override periods + const string line1 = FileReadString(handle); + string settings[]; + if(StringSplit(line1, ',', settings) == 4) + { + int FastStart = (int)StringToInteger(settings[1]); + int FastStep = (int)StringToInteger(settings[2]); + int FastStop = (int)StringToInteger(settings[3]); + const string line2 = FileReadString(handle); + if(StringSplit(line2, ',', settings) == 4) + { + int SlowStart = (int)StringToInteger(settings[1]); + int SlowStep = (int)StringToInteger(settings[2]); + int SlowStop = (int)StringToInteger(settings[3]); + p = Iterate(FastStart, FastStop, FastStep, + SlowStart, SlowStop, SlowStep, FastSlowCombo4Optimization); + PrintFormat("MA periods are restored from shadow: FastOsMA=%d SlowOsMA=%d", + p.fast, p.slow); + } + } + } + FileClose(handle); + } + + strategy = new SimpleStrategy( + new BandOsMaSignal(p.fast, p.slow, SignalOsMA, PriceOsMA, + BandsMA, BandsShift, BandsDeviation, + PeriodMA, ShiftMA, MethodMA), + Magic, StopLoss, Lots); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(strategy[] != NULL) + { + strategy[].trade(); + } +} + +//+------------------------------------------------------------------+ +//| Helper struct to hold and request all tester stats | +//+------------------------------------------------------------------+ +struct TesterRecord +{ + string feature; + double value; + + static void fill(TesterRecord &stats[]) + { + ResetLastError(); + for(int i = 0; ; ++i) + { + const double v = TesterStatistics((ENUM_STATISTICS)i); + if(_LastError) return; + TesterRecord t = {EnumToString((ENUM_STATISTICS)i), v}; + PUSH(stats, t); + } + } +}; + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + TesterRecord stats[]; + TesterRecord::fill(stats); + ArrayPrint(stats, 2); +} + +//+------------------------------------------------------------------+ +double sign(const double x) +{ + return x > 0 ? +1 : (x < 0 ? -1 : 0); +} + +//+------------------------------------------------------------------+ +//| Tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ +#ifdef USE_R2_CRITERION + return GetR2onBalanceCurve(); +#else + const double profit = TesterStatistics(STAT_PROFIT); + return sign(profit) * sqrt(fabs(profit)) + * sqrt(TesterStatistics(STAT_PROFIT_FACTOR)) + * sqrt(TesterStatistics(STAT_TRADES)) + * sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO))); +#endif +} + +#ifdef USE_R2_CRITERION + +#define STAT_PROPS 4 + +//+------------------------------------------------------------------+ +//| Build balance curve and estimate R2 for it | +//+------------------------------------------------------------------+ +double GetR2onBalanceCurve() +{ + HistorySelect(0, LONG_MAX); + + const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] = + { + DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE + }; + double expenses[][STAT_PROPS]; + ulong tickets[]; // used here only to match 'select' prototype, but helpful for debug + + DealFilter filter; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE) + .select(props, tickets, expenses); + + const int n = ArraySize(tickets); + + double balance[]; + + ArrayResize(balance, n + 1); + balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT); + + for(int i = 0; i < n; ++i) + { + double result = 0; + for(int j = 0; j < STAT_PROPS; ++j) + { + result += expenses[i][j]; + } + balance[i + 1] = result + balance[i]; + } + const double r2 = RSquaredTest(balance); + return r2 * 100; +} +#endif +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| Struct for pair of MA periods (checked for correctness) | +//+------------------------------------------------------------------+ +struct PairOfPeriods +{ + int fast; + int slow; +}; + +//+------------------------------------------------------------------+ +//| Get a pair of MA periods according to given ranges and steps | +//+------------------------------------------------------------------+ +PairOfPeriods Iterate(const long start1, const long stop1, const long step1, + const long start2, const long stop2, const long step2, + const long find = -1) +{ + int count = 0; + for(int i = (int)start1; i <= (int)stop1; i += (int)step1) + { + for(int j = (int)start2; j <= (int)stop2; j += (int)step2) + { + if(i < j) + { + if(count == find) + { + PairOfPeriods p = {i, j}; + return p; + } + ++count; + } + } + } + PairOfPeriods p = {count, 0}; + return p; +} + +//+------------------------------------------------------------------+ +//| Optimization initialization handler | +//+------------------------------------------------------------------+ +int OnTesterInit() +{ + bool enabled1, enabled2; + long value1, start1, step1, stop1; + long value2, start2, step2, stop2; + // obtain optimization settings for FastOsMA and SlowOsMA + if(ParameterGetRange("FastOsMA", enabled1, value1, start1, step1, stop1) + && ParameterGetRange("SlowOsMA", enabled2, value2, start2, step2, stop2)) + { + if(enabled1 && enabled2) + { + // disable them + if(!ParameterSetRange("FastOsMA", false, value1, start1, step1, stop1) + || !ParameterSetRange("SlowOsMA", false, value2, start2, step2, stop2)) + { + Print("Can't disable optimization by FastOsMA and SlowOsMA: ", + E2S(_LastError)); + ChartClose(); + return INIT_FAILED; + } + // find out a number of correct combinations of 2 periods + PairOfPeriods p = Iterate(start1, stop1, step1, start2, stop2, step2); + const int count = p.fast; + // request optimization for this range for new shadow parameter + ParameterSetRange("FastSlowCombo4Optimization", true, 0, 0, 1, count); + PrintFormat("Parameter FastSlowCombo4Optimization is enabled with maximum: %d", + count); + + // now check if the shadow file already exists - otherwise it won't be sent to agents + const bool preExisted = FileIsExist(SettingsFile); + + // write required settings to the file for sending to our copy running on agents + int handle = FileOpen(SettingsFile, FILE_WRITE | FILE_CSV | FILE_ANSI, ","); + FileWrite(handle, "FastOsMA", start1, step1, stop1); + FileWrite(handle, "SlowOsMA", start2, step2, stop2); + FileClose(handle); + + if(!preExisted) + { + PrintFormat("Required file %s is missing. It has been just created. Please restart again.", + SettingsFile); + ChartClose(); + return INIT_FAILED; + } + } + } + else + { + Print("Can't adjust optimization by FastOsMA and SlowOsMA: ", E2S(_LastError)); + ChartClose(); + return INIT_FAILED; + } + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Optimization finalization handler | +//| (for some reason compiler requires this handler as well, | +//| but it's useless here) | +//+------------------------------------------------------------------+ +void OnTesterDeinit() +{ +} +//+------------------------------------------------------------------+ diff --git a/BandOsMAprofile.mq5 b/BandOsMAprofile.mq5 new file mode 100644 index 0000000..a8fcd86 --- /dev/null +++ b/BandOsMAprofile.mq5 @@ -0,0 +1,565 @@ +//+------------------------------------------------------------------+ +//| BandOsMAprofile.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trading strategy based on OsMA, BBands and MA indicators." + +#define SETTINGS_FILE "BandOsMAprofile.csv" +#property tester_file SETTINGS_FILE +#property tester_set "/Presets/MQL5Book/BandOsMA.set" + +#include +#include +#include +#include +#include +#include +#include + +#define USE_R2_CRITERION +#ifdef USE_R2_CRITERION +#include +#endif + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ + +input group "C O M M O N S E T T I N G S" +sinput ulong Magic = 1234567890; +input double Lots = 0.01; +input int StopLoss = 1000; + +input group "O S M A S E T T I N G S" +input int FastOsMA = 12; +input int SlowOsMA = 26; +input int SignalOsMA = 9; +input ENUM_APPLIED_PRICE PriceOsMA = PRICE_TYPICAL; + +input group "B B A N D S S E T T I N G S" +input int BandsMA = 26; +input int BandsShift = 0; +input double BandsDeviation = 2.0; + +input group "M A S E T T I N G S" +input int PeriodMA = 10; +input int ShiftMA = 0; +input ENUM_MA_METHOD MethodMA = MODE_SMA; + +input group "A U X I L I A R Y" +sinput int FastSlowCombo4Optimization = 0; // (reserved for optimization) +const string SettingsFile = SETTINGS_FILE; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading | +//+------------------------------------------------------------------+ +interface TradingStrategy +{ + virtual bool trade(void); +}; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading signals | +//+------------------------------------------------------------------+ +interface TradingSignal +{ + virtual int signal(void); +}; + +//+------------------------------------------------------------------+ +//| Trade signal detector based on 3 indicators | +//+------------------------------------------------------------------+ +class BandOsMaSignal: public TradingSignal +{ + int hOsMA, hBands, hMA; + int direction; +public: + BandOsMaSignal(const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price, + const int bands, const int shift, const double deviation, + const int period, const int x, ENUM_MA_METHOD method) + { + hOsMA = iOsMA(_Symbol, _Period, fast, slow, signal, price); + hBands = iBands(_Symbol, _Period, bands, shift, deviation, hOsMA); + hMA = iMA(_Symbol, _Period, period, x, method, hOsMA); + direction = 0; + } + + ~BandOsMaSignal() + { + IndicatorRelease(hMA); + IndicatorRelease(hBands); + IndicatorRelease(hOsMA); + } + + virtual int signal(void) override + { + double osma[2], upper[2], lower[2], ma[2]; + // copy 2 values from bars 1 and 2 for every indicator + if(CopyBuffer(hOsMA, 0, 1, 2, osma) != 2) return 0; + if(CopyBuffer(hBands, UPPER_BAND, 1, 2, upper) != 2) return 0; + if(CopyBuffer(hBands, LOWER_BAND, 1, 2, lower) != 2) return 0; + if(CopyBuffer(hMA, 0, 1, 2, ma) != 2) return 0; + + // if there was a signal, check if it's over + if(direction != 0) + { + if(direction > 0) + { + if(osma[0] >= ma[0] && osma[1] < ma[1]) + { + direction = 0; + } + } + else + { + if(osma[0] <= ma[0] && osma[1] > ma[1]) + { + direction = 0; + } + } + } + + // in any case check for new signals + if(osma[0] <= lower[0] && osma[1] > lower[1]) + { + direction = +1; + } + else if(osma[0] >= upper[0] && osma[1] < upper[1]) + { + direction = -1; + } + + return direction; + } +}; + +//+------------------------------------------------------------------+ +//| Main class with trading strategy | +//+------------------------------------------------------------------+ +class SimpleStrategy: public TradingStrategy +{ +protected: + AutoPtr position; + AutoPtr trailing; + AutoPtr command; + + const int stopLoss; + const ulong magic; + const double lots; + + datetime lastBar; + +public: + SimpleStrategy(TradingSignal *signal, const ulong m, const int sl, const double v): + command(signal), magic(m), stopLoss(sl), lots(v), lastBar(0) + { + // pick up existing positions (if any) + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_MAGIC, magic).let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + if(n > 1) + { + Alert(StringFormat("Too many positions: %d", n)); + // TODO: close old positions + } + else if(n > 0) + { + position = new PositionState(tickets[0]); + if(stopLoss) + { + trailing = new TrailingStop(tickets[0], stopLoss, stopLoss / 50); + } + } + } + + virtual bool trade() override + { + // work only once per bar, at its opening + if(lastBar == iTime(_Symbol, _Period, 0)) return false; + + int s = command[].signal(); // get the signal + + ulong ticket = 0; + + if(position[] != NULL) + { + if(position[].refresh()) // position still exists + { + // the signal is reversed or does not exist anymore + if((position[].get(POSITION_TYPE) == POSITION_TYPE_BUY && s != +1) + || (position[].get(POSITION_TYPE) == POSITION_TYPE_SELL && s != -1)) + { + PrintFormat("Signal lost: %d for position %d %lld", + s, position[].get(POSITION_TYPE), position[].get(POSITION_TICKET)); + if(close(position[].get(POSITION_TICKET))) + { + position = NULL; + } + else + { + position[].refresh(); // make sure 'ready' flag is dropped if closed anyway + } + } + else + { + position[].update(); + if(trailing[]) trailing[].trail(); + } + } + else // position closed + { + position = NULL; + } + } + + if(position[] == NULL) + { + if(s != 0) + { + ticket = (s == +1) ? openBuy() : openSell(); + } + } + + if(ticket > 0) // new position is just opened + { + position = new PositionState(ticket); + if(stopLoss) + { + trailing = new TrailingStop(ticket, stopLoss, stopLoss / 50); + } + } + + lastBar = iTime(_Symbol, _Period, 0); + + return true; + } + +protected: + void prepare(MqlTradeRequestSync &request) + { + request.deviation = stopLoss / 50; + request.magic = magic; + } + + ulong postprocess(MqlTradeRequestSync &request) + { + if(request.completed()) + { + return request.result.position; + } + return 0; + } + + ulong openBuy() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_ASK); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.buy(_Symbol, lots, price, + stopLoss ? price - stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + ulong openSell() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_BID); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.sell(_Symbol, lots, price, + stopLoss ? price + stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + bool close(const ulong ticket) + { + MqlTradeRequestSync request; + prepare(request); + return request.close(ticket) && postprocess(request); + } +}; + +//+------------------------------------------------------------------+ +//| Global pointer for the pool of strategies | +//+------------------------------------------------------------------+ +AutoPtr strategy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT; + + PairOfPeriods p = {FastOsMA, SlowOsMA}; // try original parameters by default + int handle = FileOpen(SettingsFile, FILE_READ | FILE_TXT | FILE_ANSI); + + // during optimization we require shadow parameters + if(MQLInfoInteger(MQL_OPTIMIZATION) && handle == INVALID_HANDLE) + { + return INIT_PARAMETERS_INCORRECT; + } + + if(handle != INVALID_HANDLE) + { + if(FastSlowCombo4Optimization != -1) + { + // if shadow copies are assigned, decode them and override periods + const string line1 = FileReadString(handle); + string settings[]; + if(StringSplit(line1, ',', settings) == 4) + { + int FastStart = (int)StringToInteger(settings[1]); + int FastStep = (int)StringToInteger(settings[2]); + int FastStop = (int)StringToInteger(settings[3]); + const string line2 = FileReadString(handle); + if(StringSplit(line2, ',', settings) == 4) + { + int SlowStart = (int)StringToInteger(settings[1]); + int SlowStep = (int)StringToInteger(settings[2]); + int SlowStop = (int)StringToInteger(settings[3]); + p = Iterate(FastStart, FastStop, FastStep, + SlowStart, SlowStop, SlowStep, FastSlowCombo4Optimization); + PrintFormat("MA periods are restored from shadow: FastOsMA=%d SlowOsMA=%d", + p.fast, p.slow); + } + } + } + FileClose(handle); + } + + strategy = new SimpleStrategy( + new BandOsMaSignal(p.fast, p.slow, SignalOsMA, PriceOsMA, + BandsMA, BandsShift, BandsDeviation, + PeriodMA, ShiftMA, MethodMA), + Magic, StopLoss, Lots); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(strategy[] != NULL) + { + strategy[].trade(); + } +} + +//+------------------------------------------------------------------+ +//| Helper struct to hold and request all tester stats | +//+------------------------------------------------------------------+ +struct TesterRecord +{ + string feature; + double value; + + static void fill(TesterRecord &stats[]) + { + ResetLastError(); + for(int i = 0; ; ++i) + { + const double v = TesterStatistics((ENUM_STATISTICS)i); + if(_LastError) return; + TesterRecord t = {EnumToString((ENUM_STATISTICS)i), v}; + PUSH(stats, t); + } + } +}; + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + TesterRecord stats[]; + TesterRecord::fill(stats); + ArrayPrint(stats, 2); +} + +//+------------------------------------------------------------------+ +double sign(const double x) +{ + return x > 0 ? +1 : (x < 0 ? -1 : 0); +} + +//+------------------------------------------------------------------+ +//| Tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ +#ifdef USE_R2_CRITERION + return GetR2onBalanceCurve(); +#else + const double profit = TesterStatistics(STAT_PROFIT); + return sign(profit) * sqrt(fabs(profit)) + * sqrt(TesterStatistics(STAT_PROFIT_FACTOR)) + * sqrt(TesterStatistics(STAT_TRADES)) + * sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO))); +#endif +} + +#ifdef USE_R2_CRITERION + +#define STAT_PROPS 4 + +//+------------------------------------------------------------------+ +//| Build balance curve and estimate R2 for it | +//+------------------------------------------------------------------+ +double GetR2onBalanceCurve() +{ + HistorySelect(0, LONG_MAX); + + const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] = + { + DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE + }; + double expenses[][STAT_PROPS]; + ulong tickets[]; // used here only to match 'select' prototype, but helpful for debug + + DealFilter filter; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE) + .select(props, tickets, expenses); + + const int n = ArraySize(tickets); + + double balance[]; + + ArrayResize(balance, n + 1); + balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT); + + for(int i = 0; i < n; ++i) + { + double result = 0; + for(int j = 0; j < STAT_PROPS; ++j) + { + result += expenses[i][j]; + } + balance[i + 1] = result + balance[i]; + } + const double r2 = RSquaredTest(balance); + return r2 * 100; +} +#endif +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| Struct for pair of MA periods (checked for correctness) | +//+------------------------------------------------------------------+ +struct PairOfPeriods +{ + int fast; + int slow; +}; + +//+------------------------------------------------------------------+ +//| Get a pair of MA periods according to given ranges and steps | +//+------------------------------------------------------------------+ +PairOfPeriods Iterate(const long start1, const long stop1, const long step1, + const long start2, const long stop2, const long step2, + const long find = -1) +{ + int count = 0; + for(int i = (int)start1; i <= (int)stop1; i += (int)step1) + { + for(int j = (int)start2; j <= (int)stop2; j += (int)step2) + { + if(i < j) + { + if(count == find) + { + PairOfPeriods p = {i, j}; + return p; + } + ++count; + } + } + } + PairOfPeriods p = {count, 0}; + return p; +} + +//+------------------------------------------------------------------+ +//| Optimization initialization handler | +//+------------------------------------------------------------------+ +int OnTesterInit() +{ + bool enabled1, enabled2; + long value1, start1, step1, stop1; + long value2, start2, step2, stop2; + // obtain optimization settings for FastOsMA and SlowOsMA + if(ParameterGetRange("FastOsMA", enabled1, value1, start1, step1, stop1) + && ParameterGetRange("SlowOsMA", enabled2, value2, start2, step2, stop2)) + { + if(enabled1 && enabled2) + { + // disable them + if(!ParameterSetRange("FastOsMA", false, value1, start1, step1, stop1) + || !ParameterSetRange("SlowOsMA", false, value2, start2, step2, stop2)) + { + Print("Can't disable optimization by FastOsMA and SlowOsMA: ", + E2S(_LastError)); + ChartClose(); + return INIT_FAILED; + } + // find out a number of correct combinations of 2 periods + PairOfPeriods p = Iterate(start1, stop1, step1, start2, stop2, step2); + const int count = p.fast; + // request optimization for this range for new shadow parameter + ParameterSetRange("FastSlowCombo4Optimization", true, 0, 0, 1, count); + PrintFormat("Parameter FastSlowCombo4Optimization is enabled with maximum: %d", + count); + + // now check if the shadow file already exists - otherwise it won't be sent to agents + const bool preExisted = FileIsExist(SettingsFile); + + // write required settings to the file for sending to our copy running on agents + int handle = FileOpen(SettingsFile, FILE_WRITE | FILE_CSV | FILE_ANSI, ","); + FileWrite(handle, "FastOsMA", start1, step1, stop1); + FileWrite(handle, "SlowOsMA", start2, step2, stop2); + FileClose(handle); + + if(!preExisted) + { + PrintFormat("Required file %s is missing. It has been just created. Please restart again.", + SettingsFile); + ChartClose(); + return INIT_FAILED; + } + } + } + else + { + Print("Can't adjust optimization by FastOsMA and SlowOsMA: ", E2S(_LastError)); + ChartClose(); + return INIT_FAILED; + } + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Optimization finalization handler | +//| (for some reason compiler requires this handler as well, | +//| but it's useless here) | +//+------------------------------------------------------------------+ +void OnTesterDeinit() +{ +} +//+------------------------------------------------------------------+ diff --git a/BandOsMAticks.mq5 b/BandOsMAticks.mq5 new file mode 100644 index 0000000..9682a6c --- /dev/null +++ b/BandOsMAticks.mq5 @@ -0,0 +1,415 @@ +//+------------------------------------------------------------------+ +//| BandOsMAticks.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trading strategy based on OsMA, BBands and MA indicators." + +#include +#include +#include +#include +#include +#include +#include + +// #define USE_R2_CRITERION // uncomment this to use R2 +#ifdef USE_R2_CRITERION +#include +#endif + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ + +input group "C O M M O N S E T T I N G S" +sinput ulong Magic = 1234567890; +input double Lots = 0.01; +input int StopLoss = 1000; + +input group "O S M A S E T T I N G S" +input int FastOsMA = 12; +input int SlowOsMA = 26; +input int SignalOsMA = 9; +input ENUM_APPLIED_PRICE PriceOsMA = PRICE_TYPICAL; + +input group "B B A N D S S E T T I N G S" +input int BandsMA = 26; +input int BandsShift = 0; +input double BandsDeviation = 2.0; + +input group "M A S E T T I N G S" +input int PeriodMA = 10; +input int ShiftMA = 0; +input ENUM_MA_METHOD MethodMA = MODE_SMA; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading | +//+------------------------------------------------------------------+ +interface TradingStrategy +{ + virtual bool trade(void); +}; + +//+------------------------------------------------------------------+ +//| Simple common interface for trading signals | +//+------------------------------------------------------------------+ +interface TradingSignal +{ + virtual int signal(void); +}; + +//+------------------------------------------------------------------+ +//| Trade signal detector based on 3 indicators | +//+------------------------------------------------------------------+ +class BandOsMaSignal: public TradingSignal +{ + int hOsMA, hBands, hMA; + int direction; +public: + BandOsMaSignal(const int fast, const int slow, const int signal, const ENUM_APPLIED_PRICE price, + const int bands, const int shift, const double deviation, + const int period, const int x, ENUM_MA_METHOD method) + { + hOsMA = iOsMA(_Symbol, _Period, fast, slow, signal, price); + hBands = iBands(_Symbol, _Period, bands, shift, deviation, hOsMA); + hMA = iMA(_Symbol, _Period, period, x, method, hOsMA); + direction = 0; + } + + ~BandOsMaSignal() + { + IndicatorRelease(hMA); + IndicatorRelease(hBands); + IndicatorRelease(hOsMA); + } + + virtual int signal(void) override + { + double osma[2], upper[2], lower[2], ma[2]; + // copy 2 values from bars 0 and 1 for every indicator + if(CopyBuffer(hOsMA, 0, 0, 2, osma) != 2) return 0; + if(CopyBuffer(hBands, UPPER_BAND, 0, 2, upper) != 2) return 0; + if(CopyBuffer(hBands, LOWER_BAND, 0, 2, lower) != 2) return 0; + if(CopyBuffer(hMA, 0, 0, 2, ma) != 2) return 0; + + // if there was a signal, check if it's over + if(direction != 0) + { + if(direction > 0) + { + if(osma[0] >= ma[0] && osma[1] < ma[1]) + { + direction = 0; + } + } + else + { + if(osma[0] <= ma[0] && osma[1] > ma[1]) + { + direction = 0; + } + } + } + + // in any case check for new signals + if(osma[0] <= lower[0] && osma[1] > lower[1]) + { + direction = +1; + } + else if(osma[0] >= upper[0] && osma[1] < upper[1]) + { + direction = -1; + } + + return direction; + } +}; + +//+------------------------------------------------------------------+ +//| Main class with trading strategy | +//+------------------------------------------------------------------+ +class SimpleStrategy: public TradingStrategy +{ +protected: + AutoPtr position; + AutoPtr trailing; + AutoPtr command; + + const int stopLoss; + const ulong magic; + const double lots; + + datetime lastBar; + +public: + SimpleStrategy(TradingSignal *signal, const ulong m, const int sl, const double v): + command(signal), magic(m), stopLoss(sl), lots(v), lastBar(0) + { + // pick up existing positions (if any) + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_MAGIC, magic).let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + if(n > 1) + { + Alert(StringFormat("Too many positions: %d", n)); + // TODO: close old positions + } + else if(n > 0) + { + position = new PositionState(tickets[0]); + if(stopLoss) + { + trailing = new TrailingStop(tickets[0], stopLoss, stopLoss / 50); + } + } + } + + virtual bool trade() override + { + // work on every tick now + // if(lastBar == iTime(_Symbol, _Period, 0)) return false; + + int s = command[].signal(); // get the signal + + ulong ticket = 0; + + if(position[] != NULL) + { + if(position[].refresh()) // position still exists + { + // the signal is reversed or does not exist anymore + if((position[].get(POSITION_TYPE) == POSITION_TYPE_BUY && s != +1) + || (position[].get(POSITION_TYPE) == POSITION_TYPE_SELL && s != -1)) + { + PrintFormat("Signal lost: %d for position %d %lld", + s, position[].get(POSITION_TYPE), position[].get(POSITION_TICKET)); + if(close(position[].get(POSITION_TICKET))) + { + position = NULL; + } + else + { + position[].refresh(); // make sure 'ready' flag is dropped if closed anyway + } + } + else + { + position[].update(); + if(trailing[]) trailing[].trail(); + } + } + else // position closed + { + position = NULL; + } + } + + if(position[] == NULL) + { + if(s != 0) + { + ticket = (s == +1) ? openBuy() : openSell(); + } + } + + if(ticket > 0) // new position is just opened + { + position = new PositionState(ticket); + if(stopLoss) + { + trailing = new TrailingStop(ticket, stopLoss, stopLoss / 50); + } + } + + lastBar = iTime(_Symbol, _Period, 0); + + return true; + } + +protected: + void prepare(MqlTradeRequestSync &request) + { + request.deviation = stopLoss / 50; + request.magic = magic; + } + + ulong postprocess(MqlTradeRequestSync &request) + { + if(request.completed()) + { + return request.result.position; + } + return 0; + } + + ulong openBuy() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_ASK); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.buy(_Symbol, lots, price, + stopLoss ? price - stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + ulong openSell() + { + SymbolMonitor m(_Symbol); + const double price = m.get(SYMBOL_BID); + const double point = m.get(SYMBOL_POINT); + + MqlTradeRequestSync request; + prepare(request); + if(request.sell(_Symbol, lots, price, + stopLoss ? price + stopLoss * point : 0, 0)) + { + return postprocess(request); + } + return 0; + } + + bool close(const ulong ticket) + { + MqlTradeRequestSync request; + prepare(request); + return request.close(ticket) && postprocess(request); + } +}; + +//+------------------------------------------------------------------+ +//| Global pointer for the pool of strategies | +//+------------------------------------------------------------------+ +AutoPtr strategy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(FastOsMA >= SlowOsMA) return INIT_PARAMETERS_INCORRECT; + strategy = new SimpleStrategy( + new BandOsMaSignal(FastOsMA, SlowOsMA, SignalOsMA, PriceOsMA, + BandsMA, BandsShift, BandsDeviation, + PeriodMA, ShiftMA, MethodMA), + Magic, StopLoss, Lots); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(strategy[] != NULL) + { + strategy[].trade(); + } +} + +//+------------------------------------------------------------------+ +//| Helper struct to hold and request all tester stats | +//+------------------------------------------------------------------+ +struct TesterRecord +{ + string feature; + double value; + + static void fill(TesterRecord &stats[]) + { + ResetLastError(); + for(int i = 0; ; ++i) + { + const double v = TesterStatistics((ENUM_STATISTICS)i); + if(_LastError) return; + TesterRecord t = {EnumToString((ENUM_STATISTICS)i), v}; + PUSH(stats, t); + } + } +}; + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + TesterRecord stats[]; + TesterRecord::fill(stats); + ArrayPrint(stats, 2); +} + +//+------------------------------------------------------------------+ +double sign(const double x) +{ + return x > 0 ? +1 : (x < 0 ? -1 : 0); +} + +//+------------------------------------------------------------------+ +//| Tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ +#ifdef USE_R2_CRITERION + return GetR2onBalanceCurve(); +#else + const double profit = TesterStatistics(STAT_PROFIT); + return sign(profit) * sqrt(fabs(profit)) + * sqrt(TesterStatistics(STAT_PROFIT_FACTOR)) + * sqrt(TesterStatistics(STAT_TRADES)) + * sqrt(fabs(TesterStatistics(STAT_SHARPE_RATIO))); +#endif +} + +#ifdef USE_R2_CRITERION + +#define STAT_PROPS 4 + +//+------------------------------------------------------------------+ +//| Build balance curve and estimate R2 for it | +//+------------------------------------------------------------------+ +double GetR2onBalanceCurve() +{ + HistorySelect(0, LONG_MAX); + + const ENUM_DEAL_PROPERTY_DOUBLE props[STAT_PROPS] = + { + DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_FEE + }; + double expenses[][STAT_PROPS]; + ulong tickets[]; // used here only to match 'select' prototype, but helpful for debug + + DealFilter filter; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE) + .select(props, tickets, expenses); + + const int n = ArraySize(tickets); + + double balance[]; + + ArrayResize(balance, n + 1); + balance[0] = TesterStatistics(STAT_INITIAL_DEPOSIT); + + for(int i = 0; i < n; ++i) + { + double result = 0; + for(int j = 0; j < STAT_PROPS; ++j) + { + result += expenses[i][j]; + } + balance[i + 1] = result + balance[i]; + } + const double r2 = RSquaredTest(balance); + return r2 * 100; +} +#endif +//+------------------------------------------------------------------+ diff --git a/BmpOwner.mq5 b/BmpOwner.mq5 new file mode 100644 index 0000000..754cb02 --- /dev/null +++ b/BmpOwner.mq5 @@ -0,0 +1,40 @@ +//+------------------------------------------------------------------+ +//| BmpOwner.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +// shared image (accessible from other programs) +#resource "search1.bmp" +// private image (declared only to demontrate that such resource is not shared) +#resource "search2.bmp" as bitmap image[] +// private text (used for alert below) +#resource "message.txt" as string Message + +//+------------------------------------------------------------------+ +//| Indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + Alert(Message); // this is equivalent of the following line + // Alert("This indicator is not intended to run, it holds a bitmap resource"); + + // remove indicator explicitly because it remains hanging uninitialized on the chart + ChartIndicatorDelete(0, 0, MQLInfoString(MQL_PROGRAM_NAME)); + return INIT_FAILED; +} + +//+------------------------------------------------------------------+ +//| Indicator calculation function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return 0; +} +//+------------------------------------------------------------------+ diff --git a/BmpUser.mq5 b/BmpUser.mq5 new file mode 100644 index 0000000..1f680e9 --- /dev/null +++ b/BmpUser.mq5 @@ -0,0 +1,61 @@ +//+------------------------------------------------------------------+ +//| BmpUser.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +// The default value in 'ResourceOff' input below is equivalent to +// the path and name "\\Indicators\\MQL5Book\\p7\\BmpOwner.ex5::search1.bmp" +input string ResourceOff = "BmpOwner.ex5::search1.bmp"; +input string ResourceOn = "BmpOwner.ex5::search2.bmp"; +input int X = 25; +input int Y = 25; +input ENUM_BASE_CORNER Corner = CORNER_RIGHT_LOWER; + +const string Prefix = "BMP_"; +const ENUM_ANCHOR_POINT Anchors[] = +{ + ANCHOR_LEFT_UPPER, + ANCHOR_LEFT_LOWER, + ANCHOR_RIGHT_LOWER, + ANCHOR_RIGHT_UPPER +}; + +//+------------------------------------------------------------------+ +//| Indicator initialization function | +//+------------------------------------------------------------------+ +void OnInit() +{ + const string name = Prefix + "search"; + ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0); + + ObjectSetString(0, name, OBJPROP_BMPFILE, 0, ResourceOn); + ObjectSetString(0, name, OBJPROP_BMPFILE, 1, ResourceOff); + ObjectSetInteger(0, name, OBJPROP_XDISTANCE, X); + ObjectSetInteger(0, name, OBJPROP_YDISTANCE, Y); + ObjectSetInteger(0, name, OBJPROP_CORNER, Corner); + ObjectSetInteger(0, name, OBJPROP_ANCHOR, Anchors[(int)Corner]); +} + +//+------------------------------------------------------------------+ +//| Indicator calculation function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Indicator finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + ObjectsDeleteAll(0, Prefix); +} +//+------------------------------------------------------------------+ diff --git a/Boolinger Band MrTan.mq5 b/Boolinger Band MrTan.mq5 new file mode 100644 index 0000000..a2cc0fa --- /dev/null +++ b/Boolinger Band MrTan.mq5 @@ -0,0 +1,426 @@ +//+------------------------------------------------------------------+ +//| Boolinger Band MrTan.mq5 | +//| Copyright 2023, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2023, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.00" +//--- +#include +#include +#include +#include +#include +CPositionInfo m_position; // trade position object +CTrade trade; // trading object +CSymbolInfo m_symbol; // symbol info object +CAccountInfo m_account; // account info wrapper +CMoneyFixedMargin *m_money; + +//--- input parameters +input double Lot_size =0.1 ; +input double distance =50 ; // Distance from highestigh or loweslow to start trade +input double TP=4000; // Take profit +input double SL=2000; // Stop loss +input ushort InpTrailingStop = 60; // Trailing Stop (in pips) +input ushort InpTrailingStep = 5; // Trailing Step (in pips) +input int InpMaxPositions = 5; // Maximum positions +input ulong m_magic=47978073; // magic number +int input EXPERT_MAGIC = 1234567; +input ENUM_TIMEFRAMES Trading_timframe=PERIOD_H1; + +// Input Indicator declaration + +input int period_Band =20; // bollingerBand period +input int Shift_band =1; // Shift bar of band +input double Deviation_band =1; // Deviation band +input int period_MA_volume =26 ; // Period of moving average volume tick +input int Shift_volume =1; // Shift bar of Volume tick + +// Global Variable + +//SL-TP management +double m_adjusted_point; // point value adjusted for 3 or 5 points +ulong m_slippage=10; // slippage +double ExtDistance=0.0; +double ExtStopLoss=0.0; +double ExtTakeProfit=0.0; +double ExtTrailingStop=0.0; +double ExtTrailingStep=0.0; +double ExtSpreadLimit=0.0; + +// Indicator Declaration +int handel_Bollingerband; + + //+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { +//--- + if(!m_symbol.Name(Symbol())) // sets symbol name + return(INIT_FAILED); + RefreshRates(); + //--- + trade.SetExpertMagicNumber(m_magic); + trade.SetMarginMode(); + trade.SetTypeFillingBySymbol(m_symbol.Name()); + trade.SetDeviationInPoints(m_slippage); +//--- tuning for 3 or 5 digits + int digits_adjust=1; + if(m_symbol.Digits()==3 || m_symbol.Digits()==5) + digits_adjust=10; + m_adjusted_point=m_symbol.Point()*digits_adjust; + + ExtStopLoss = SL * m_adjusted_point; + ExtTakeProfit = TP * m_adjusted_point; + ExtTrailingStop= InpTrailingStop * m_adjusted_point; + ExtTrailingStep= InpTrailingStep * m_adjusted_point; + ExtDistance = distance*m_adjusted_point; double profit=0; +//--- create handle of the indicator Bollinger band + handel_Bollingerband=iBands(Symbol(),Trading_timframe,period_Band,Shift_band,Deviation_band,PRICE_CLOSE); +//--- if the handle is not created + if(handel_Bollingerband==INVALID_HANDLE) + { + //--- tell about the failure and output the error code + PrintFormat("Failed to create handle of the Bollinger band indicator for the symbol %s/%s, error code %d", + m_symbol.Name(), + EnumToString(Period()), + GetLastError()); + //--- the indicator is stopped early + return(INIT_FAILED); + } + + + +//--- + return(INIT_SUCCEEDED); + } + + + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { +//--- + if(m_money!=NULL) + delete m_money; + + + ChartRedraw(); + } +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() + { + + + // Declaration the Candle + double high[]; ArraySetAsSeries(high,true);CopyHigh(Symbol(),Trading_timframe,0,1000,high); + double low[]; ArraySetAsSeries(low,true);CopyLow(Symbol(),Trading_timframe,0,1000,low); + double open[]; ArraySetAsSeries(open,true);CopyOpen(Symbol(),Trading_timframe,0,1000,open); + double close[]; ArraySetAsSeries(close,true);CopyClose(Symbol(),Trading_timframe,0,1000,close); + + // Declaration Array for Bollinger band + double Uperband[]; ArraySetAsSeries(Uperband,true);CopyBuffer(handel_Bollingerband,UPPER_BAND,0,1000,Uperband);// Array for Uperband + double Lowerband[]; ArraySetAsSeries(Lowerband,true);CopyBuffer(handel_Bollingerband,LOWER_BAND,0,1000,Lowerband);// Array for lowerband + double Midleband[]; ArraySetAsSeries(Midleband,true);CopyBuffer(handel_Bollingerband,BASE_LINE,0,1000,Midleband);// Array for midel band + + // Declaration the volume tick + double MA_volume[]; ArraySetAsSeries(MA_volume,true); Get_MA_volume(0,0,MA_volume,1000); + double volume[];ArraySetAsSeries(volume,true); Get_ivolume(0,0,volume,1000); + + + // declaration count positions + int count_buy=0; int count_sell=0;double profit=0; + CalculatePositions(count_buy,count_sell,profit); + + // Display discription volume check + string discription=" "; + + // Declaration parameter befor send to the broker + double Ask= SymbolInfoDouble(Symbol(),SYMBOL_ASK); + double Bid= SymbolInfoDouble(Symbol(),SYMBOL_BID); + double Stop_level=(int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL); + + // condition check volume + + // Execution main Trade + + // Only trade at new bar + if(BarOpen(Symbol(),Trading_timframe)) + { + CalculatePositions(count_buy,count_sell,profit); + // Trailing stop + Trailing(); + { + // Looking for to go long if there is no long position + if(count_buy==0 ) + { + if( low[2]Midleband[1] && close[1]>open[1] && volume[1]>MA_volume[1] ) + { + double entryprice= Ask; + double sl=entryprice-ExtStopLoss; + double tp =entryprice + ExtTakeProfit; + //if(CheckVolumeValue(Symbol(),Lot_size,discription,entryprice,ORDER_TYPE_BUY)) + //trade.Buy(Lot_size,Symbol(),entryprice,sl,tp); + // you enter code buy or sell + } + } + else if(count_sell==0 ) + { + if( high[2]>Midleband[2]&&close[1]MA_volume[1] ) + { + double entryprice= Bid; + double sl=entryprice+ExtStopLoss; + double tp =entryprice - ExtTakeProfit; + //if(CheckVolumeValue(Symbol(),Lot_size,discription,entryprice,ORDER_TYPE_SELL)) + //trade.Sell(Lot_size,Symbol(),entryprice,sl,tp); + // you enter code buy or sell + } + } + + + } + + } +} + +//+------------------------------------------------------------------+ +//| Refreshes the symbol quotes data | +//+------------------------------------------------------------------+ +bool RefreshRates(void) + { +//--- refresh rates + if(!m_symbol.RefreshRates()) + { + Print("RefreshRates error"); + return(false); + } +//--- protection against the return value of "zero" + if(m_symbol.Ask()==0 || m_symbol.Bid()==0) + return(false); +//--- + return(true); + } + + +//+------------------------------------------------------------------+ +//| Calculate positions Buy and Sell | +//+------------------------------------------------------------------+ +void CalculatePositions(int &count_buys,int &count_sells,double &profit) + { + count_buys=0; + count_sells=0; + profit=0.0; + + for(int i=PositionsTotal()-1;i>=0;i--) + if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties + if(m_position.Symbol()==m_symbol.Name() //&& m_position.Magic()==m_magic + ) + { + profit+=m_position.Commission()+m_position.Swap()+m_position.Profit(); + if(m_position.PositionType()==POSITION_TYPE_BUY) + count_buys++; + + if(m_position.PositionType()==POSITION_TYPE_SELL) + count_sells++; + } + } +//+------------------------------------------------------------------+ +//| close all positions | +//+------------------------------------------------------------------+ +void ClosePositions(const ENUM_POSITION_TYPE pos_type) + { + for(int i=PositionsTotal()-1;i>=0;i--) // returns the number of current positions + if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties + if(m_position.Symbol()==m_symbol.Name() && m_position.Magic()==m_magic) + if(m_position.PositionType()==pos_type) // gets the position type + trade.PositionClose(m_position.Ticket()); // close a position by the specified symbol + } + + //+------------------------------------------------------------------+ +//| get highest value for range | +//+------------------------------------------------------------------+ +double Highest(const double&array[],int range,int fromIndex) +{ + double res=0; +//--- + res=array[fromIndex]; + for(int i=fromIndex;iarray[i]) res=array[i]; + } +//--- + return(res); +} + + + //+------------------------------------------------------------------+ +// Get value of buffers for the volume + +double Get_ivolume( int buffer, int index, double &value[],int count) + { + int handel_Volume=iVolumes(Symbol(),Trading_timframe,VOLUME_TICK); +//--- reset error code + ResetLastError(); +//--- fill a part of the iIchimoku array with values from the indicator buffer that has 0 index + if(CopyBuffer(handel_Volume,buffer,index,count,value)<0) + { + //--- if the copying fails, tell the error code + PrintFormat("Failed to copy data from the volume indicator, error code %d",GetLastError()); + //--- quit with zero result - it means that the indicator is considered as not calculated + //return(0.0); + } + return(value[0]); + } + + //+------------------------------------------------------------------+ +// Get value of buffers for the Bollinger band + +double Get_MA_volume( int buffer, int index, double &value[],int count) + { +//--- reset error code + ResetLastError(); + //--- create handle of the indicator volume +int handel_Volume=iVolumes(Symbol(),Trading_timframe,VOLUME_TICK); +int hande_MA_volume= iMA(Symbol(),Trading_timframe,period_MA_volume,Shift_volume,MODE_EMA,handel_Volume); +//--- fill a part of the iIchimoku array with values from the indicator buffer that has 0 index + if(CopyBuffer(hande_MA_volume,buffer,index,count,value)<0) + { + //--- if the copying fails, tell the error code + PrintFormat("Failed to copy data from the Moving Arverage indicator, error code %d",GetLastError()); + //--- quit with zero result - it means that the indicator is considered as not calculated + //return(0.0); + } + return(value[0]); + } + //+------------------------------------------------------------------+ +//| Get current server time function | +//+------------------------------------------------------------------+ + +datetime m_prev_bar; +bool BarOpen(string symbol,ENUM_TIMEFRAMES timeframe) +{ + datetime bar_time = iTime(symbol, timeframe, 0); + if (bar_time == m_prev_bar) + { + return false; + } + m_prev_bar = bar_time; + return true; +} + + +//+------------------------------------------------------------------+ +//| Trailing | +//+------------------------------------------------------------------+ +void Trailing() + { + if(InpTrailingStop==0) + return; + for(int i=PositionsTotal()-1;i>=0;i--) // returns the number of open positions + if(m_position.SelectByIndex(i)) + if(m_position.Symbol()==m_symbol.Name() //&& m_position.Magic()==m_magic + ) + { + if(m_position.PositionType()==POSITION_TYPE_BUY) + { + if(m_position.PriceCurrent()-m_position.PriceOpen()>ExtTrailingStop+ExtTrailingStep) + if(m_position.StopLoss() false. Result Retcode: ",trade.ResultRetcode(), + ", description of result: ",trade.ResultRetcodeDescription()); + RefreshRates(); + m_position.SelectByIndex(i); + continue; + } + } + else + { + if(m_position.PriceOpen()-m_position.PriceCurrent()>ExtTrailingStop+ExtTrailingStep) + if((m_position.StopLoss()>(m_position.PriceCurrent()+(ExtTrailingStop+ExtTrailingStep))) || + (m_position.StopLoss()==0)) + { + if(!trade.PositionModify(m_position.Ticket(), + m_symbol.NormalizePrice(m_position.PriceCurrent()+ExtTrailingStop), + m_position.TakeProfit())) + Print("Modify ",m_position.Ticket(), + " Position -> false. Result Retcode: ",trade.ResultRetcode(), + ", description of result: ",trade.ResultRetcodeDescription()); + RefreshRates(); + m_position.SelectByIndex(i); + } + } + + } + } + + + //+------------------------------------------------------------------+ +//| Check the correctness of the order volume | +//+------------------------------------------------------------------+ +bool CheckVolumeValue(string symbol,double volume,string &description,double price ,ENUM_ORDER_TYPE type) + { +//--- minimal allowed volume for trade operations + double margin = 0; + double min_volume=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); + if(volumemax_volume) + { + description=StringFormat("Volume is greater than the maximal allowed SYMBOL_VOLUME_MAX=%.2f",max_volume); + return(false); + } + +//--- get minimal step of volume changing + double volume_step=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); + + int ratio=(int)MathRound(volume/volume_step); + if(MathAbs(ratio*volume_step-volume)>0.0000001) + { + description=StringFormat("Volume is not a multiple of the minimal step SYMBOL_VOLUME_STEP=%.2f, the closest correct volume is %.2f", + volume_step,ratio*volume_step); + return(false); + } + + if(OrderCalcMargin(type,Symbol(),volume,price,margin)) + { + double free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); + if(free_margin<0 ) + { + return false; + } + } + description="Correct volume value"; + return(true); + } \ No newline at end of file diff --git a/BreakoutStrategy.mq5 b/BreakoutStrategy.mq5 new file mode 100644 index 0000000..8c9a037 --- /dev/null +++ b/BreakoutStrategy.mq5 @@ -0,0 +1,291 @@ +//+------------------------------------------------------------------+ +//| BreakoutStrategy.mq5 | +//| Copyright 2024, QuanAlpha | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2024, QuanAlpha" +#property version "1.00" + +#include + +//+------------------------------------------------------------------+ +//| INPUT PARAMETERS | +//+------------------------------------------------------------------+ +string input aa = "------------------SETTINGS----------------------"; +string input BOT_NAME = "BreakoutStrategy"; +int input EXPERT_MAGIC = 1; +string input bb = "-------------------ENTRY------------------------"; +int input ENTRY_PERIOD = 20; +int input ENTRY_SHIFT = 1; +string input cc = "--------------------EXIT------------------------"; +int input EXIT_PERIOD = 20; +int input EXIT_SHIFT = 1; +bool input EXIT_MIDDLE_LINE = true; // Use middle line for exit? +string input gg = "----------------RISK PROFILE--------------------"; +ENUM_TIMEFRAMES input TRADING_TIMEFRAME = PERIOD_H1; +double input RISK_PER_TRADE = 0.01; + +//+------------------------------------------------------------------+ +//| GLOBAL VARIABLES | +//+------------------------------------------------------------------+ + +// Trade Object +CTrade trade; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + trade.SetExpertMagicNumber(EXPERT_MAGIC); + trade.SetDeviationInPoints(10); + + printf(BOT_NAME + " initialized!"); + return(INIT_SUCCEEDED); +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + printf(BOT_NAME + " exited, exit code: %d", reason); +} + +//+------------------------------------------------------------------+ +//| Indicators | +//+------------------------------------------------------------------+ + +double HighestHigh(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + return iHigh(_Symbol, timeframe, iHighest(_Symbol, timeframe, MODE_HIGH, period, shift)); +} + +double LowestLow(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + return iLow(_Symbol, timeframe, iLowest(_Symbol, timeframe, MODE_LOW, period, shift)); +} + +double Middle(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + return (HighestHigh(period, shift, timeframe) + LowestLow(period, shift, timeframe)) / 2.; +} + +double ATR(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + int handle = iATR(_Symbol, timeframe, period); + double value[]; + CopyBuffer(handle, 0, shift, 1, value); + return value[0]; +} + +//+------------------------------------------------------------------+ +//| Manage existing positions | +//+------------------------------------------------------------------+ +int PositionManaging(ENUM_POSITION_TYPE position_type) +{ + int positions = 0; + double trail_long, trail_short; + double atr = ATR(20, 1, TRADING_TIMEFRAME); + if (EXIT_MIDDLE_LINE) + { + trail_long = Middle(EXIT_PERIOD, EXIT_SHIFT, TRADING_TIMEFRAME); + trail_short = trail_long; + } + else + { + trail_long = LowestLow(EXIT_PERIOD, EXIT_SHIFT, TRADING_TIMEFRAME); + trail_short = HighestHigh(EXIT_PERIOD, EXIT_SHIFT, TRADING_TIMEFRAME); + } + + for (int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong posTicket = PositionGetTicket(i); + if (PositionSelectByTicket(posTicket)) + { + if (PositionGetString(POSITION_SYMBOL) == _Symbol && + PositionGetInteger(POSITION_MAGIC) == EXPERT_MAGIC && + PositionGetInteger(POSITION_TYPE) == position_type) + { + positions = positions + 1; + double sl = PositionGetDouble(POSITION_SL), tp = PositionGetDouble(POSITION_TP), cp = PositionGetDouble(POSITION_PRICE_CURRENT), op = PositionGetDouble(POSITION_PRICE_OPEN); + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) + { + // Trailing stop + if (cp < trail_long) + { + trade.PositionClose(posTicket); + positions--; + } + else if (trail_long > sl + 0.1 * atr && cp - trail_long >= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + trade.PositionModify(posTicket, trail_long, tp); + } + } + else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) + { + // Trailing stop + if (cp > trail_short) + { + trade.PositionClose(posTicket); + positions--; + } + else if (trail_short < sl - 0.1 * atr && trail_short - cp >= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + trade.PositionModify(posTicket, trail_short, tp); + } + } + } + } + } + return MathMax(positions, 0); +} + +//+------------------------------------------------------------------+ +//| Manage orders | +//+------------------------------------------------------------------+ +int OrderManaging() +{ + int orders = 0; + for (int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong orderTicket = OrderGetTicket(i); + if (OrderSelect(orderTicket)) + { + if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == EXPERT_MAGIC) + { + trade.OrderDelete(orderTicket); + } + } + } + return orders; +} + +//+------------------------------------------------------------------+ +//| Calculate lot_sizes based on risk % of equity per trade | +//+------------------------------------------------------------------+ + +double CalculateLotSize(double sl_value, double price) +{ + double lots = 0.0; + double loss = 0.0; + + double balance=AccountInfoDouble(ACCOUNT_EQUITY); + double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); + double sl_price=price-sl_value; + + if (OrderCalcProfit(ORDER_TYPE_BUY,_Symbol,1.0,price,sl_price,loss)) + { + double lotstep=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); + double risk_money=RISK_PER_TRADE*balance; + double margin = 0; + lots=risk_money/MathAbs(loss); + lots=MathFloor(lots/lotstep)*lotstep; + //--- Adjust lots to broker limits. + double minlot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); + double maxlot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); + if(lots < minlot) + { + lots=minlot; + } + if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,lots,price,margin)) + { + double free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); + if(free_margin<0) + { + lots=0; + } + else if(free_margin= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + double sl = trigger_long - exit_long; + double lot_size = CalculateLotSize(sl, trigger_long); + double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + while (lot_size > max_volume) + { + trade.BuyStop(max_volume, trigger_long, _Symbol, exit_long, 0.0, ORDER_TIME_DAY); + lot_size -= max_volume; + } + trade.BuyStop(lot_size, trigger_long, _Symbol, exit_long, 0.0, ORDER_TIME_DAY); + } + + // Short order + if (short_positions == 0 && bid - trigger_short >= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + double sl = exit_short - trigger_short; + double lot_size = CalculateLotSize(sl, trigger_short); + double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + while (lot_size > max_volume) + { + trade.SellStop(max_volume, trigger_short, _Symbol, exit_short, 0.0, ORDER_TIME_DAY); + lot_size -= max_volume; + } + trade.SellStop(lot_size, trigger_short, _Symbol, exit_short, 0.0, ORDER_TIME_DAY); + } + } +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ + +void OnTick() +{ + ExecuteTrade(); +} \ No newline at end of file diff --git a/CalendarChangeBacktrace.mq5 b/CalendarChangeBacktrace.mq5 new file mode 100644 index 0000000..27cc4c7 --- /dev/null +++ b/CalendarChangeBacktrace.mq5 @@ -0,0 +1,152 @@ +//+------------------------------------------------------------------+ +//| CalendarChangeBacktrace.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Request economic calendar changes in backward direction (decreasing change IDs)." +#property script_show_inputs + +#include + +#define BUF_SIZE 10 + +input int BacktraceSize = 10000; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + ulong change = 0; + MqlCalendarValue values[BUF_SIZE]; + PRTF(CalendarValueLast(change, values)); + if(!change) + { + Print("Can't get start change ID"); + return; + } + + Print("Starting backward from the change ID: ", change); + + ulong id = 0; + const ulong start = change; + for(int i = 1; i <= BacktraceSize && !IsStopped(); ++i) + { + change = start - i; + const int n = CalendarValueLast(change, values); + if(n) + { + if(values[0].id != id) + { + Print("Change ID: ", start - i); + MqlCalendarValue subset[]; + int j = 1; + for(; j < n; ++j) + { + if(values[j].id == id) + { + break; + } + } + ArrayCopy(subset, values, 0, 0, j); + ArrayPrint(subset); + if(j == BUF_SIZE) + { + PrintFormat("[more than %d news records in this change, trimmed]", BUF_SIZE); + } + id = values[0].id; + } + } + } +} +//+------------------------------------------------------------------+ +/* + +CalendarValueLast(change,values)=0 / ok +Starting backward from the change ID: 86504192 +Change ID: 86504191 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 167675 840200009 2022.07.07 17:30:00 2022.07.01 00:00:00 0 -9223372036854775808 82000000 -9223372036854775808 63000000 0 ... +Change ID: 86503679 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 167189 840200009 2022.06.30 17:30:00 2022.06.24 00:00:00 0 82000000 74000000 -9223372036854775808 75000000 2 ... +Change ID: 86503423 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 166647 76080001 2022.09.30 14:00:00 2022.10.01 00:00:00 0 -9223372036854775808 7010000 -9223372036854775808 -9223372036854775808 0 ... +Change ID: 86503167 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163191 76080001 2022.06.30 14:00:00 2022.07.01 00:00:00 0 7010000 6820000 -9223372036854775808 -9223372036854775808 0 ... +Change ID: 86502911 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163462 840010014 2022.07.29 15:30:00 2022.06.01 00:00:00 0 -9223372036854775808 -400000 -9223372036854775808 0 0 ... +Change ID: 86502655 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163402 840010006 2022.07.29 15:30:00 2022.06.01 00:00:00 0 -9223372036854775808 500000 -9223372036854775808 2300000 0 ... +Change ID: 86502399 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163390 840010005 2022.07.29 15:30:00 2022.06.01 00:00:00 0 -9223372036854775808 200000 -9223372036854775808 -100000 0 ... +Change ID: 86502143 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163378 840010004 2022.07.29 15:30:00 2022.06.01 00:00:00 0 -9223372036854775808 6300000 -9223372036854775808 6700000 0 ... +Change ID: 86501887 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163366 840010003 2022.07.29 15:30:00 2022.06.01 00:00:00 0 -9223372036854775808 600000 -9223372036854775808 500000 0 ... +Change ID: 86501631 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163354 840010002 2022.07.29 15:30:00 2022.06.01 00:00:00 0 -9223372036854775808 4700000 -9223372036854775808 5000000 0 ... +Change ID: 86501375 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163342 840010001 2022.07.29 15:30:00 2022.06.01 00:00:00 0 -9223372036854775808 300000 -9223372036854775808 400000 0 ... +Change ID: 86501119 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 162079 124010035 2022.07.29 15:30:00 2022.05.01 00:00:00 0 -9223372036854775808 5000000 -9223372036854775808 4300000 0 ... +Change ID: 86500863 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 161955 124010021 2022.07.29 15:30:00 2022.05.01 00:00:00 0 -9223372036854775808 300000 -9223372036854775808 0 0 ... +Change ID: 86500607 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 168163 840140002 2022.07.07 15:30:00 2022.06.25 00:00:00 0 -9223372036854775808 1328000 -9223372036854775808 1286000 0 ... +Change ID: 86500351 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 168167 840140003 2022.07.07 15:30:00 2022.07.02 00:00:00 0 -9223372036854775808 231750000 -9223372036854775808 233471000 0 ... +Change ID: 86500095 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 168159 840140001 2022.07.07 15:30:00 2022.07.02 00:00:00 0 -9223372036854775808 231000000 -9223372036854775808 209000000 0 ... +Change ID: 86499583 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163461 840010014 2022.06.30 15:30:00 2022.05.01 00:00:00 0 -400000 700000 300000 200000 2 ... +Change ID: 86499071 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163401 840010006 2022.06.30 15:30:00 2022.05.01 00:00:00 0 500000 400000 500000 -3100000 1 ... +Change ID: 86498559 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163389 840010005 2022.06.30 15:30:00 2022.05.01 00:00:00 0 200000 900000 600000 -100000 1 ... +Change ID: 86498047 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163377 840010004 2022.06.30 15:30:00 2022.05.01 00:00:00 0 6300000 6300000 -9223372036854775808 6400000 2 ... +Change ID: 86497535 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163365 840010003 2022.06.30 15:30:00 2022.05.01 00:00:00 0 600000 200000 -9223372036854775808 300000 1 ... +Change ID: 86497023 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163353 840010002 2022.06.30 15:30:00 2022.05.01 00:00:00 0 4700000 4900000 -9223372036854775808 4500000 1 ... +Change ID: 86496511 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 163341 840010001 2022.06.30 15:30:00 2022.05.01 00:00:00 0 300000 300000 -9223372036854775808 400000 2 ... +Change ID: 86495999 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 162078 124010035 2022.06.30 15:30:00 2022.04.01 00:00:00 0 5000000 3500000 -9223372036854775808 4000000 1 ... +Change ID: 86495487 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 161954 124010021 2022.06.30 15:30:00 2022.04.01 00:00:00 0 300000 700000 -9223372036854775808 0 1 ... +Change ID: 86494975 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 167370 840140002 2022.06.30 15:30:00 2022.06.18 00:00:00 0 1328000 1315000 1331000 1358000 1 ... +Change ID: 86494463 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] +[0] 167375 840140003 2022.06.30 15:30:00 2022.06.25 00:00:00 0 231750000 223500000 224500000 225837000 2 ... + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarChangeReader.mq5 b/CalendarChangeReader.mq5 new file mode 100644 index 0000000..9e922b2 --- /dev/null +++ b/CalendarChangeReader.mq5 @@ -0,0 +1,138 @@ +//+------------------------------------------------------------------+ +//| CalendarChangeReader.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Test script to show how to read saved calendar changes from CalendarChangeSaver service." +#property script_show_inputs + +#include + +#define PACK_DATETIME_COUNTER(D,C) (D | (((ulong)(C)) << 48)) +#define DATETIME(A) ((datetime)((A) & 0x7FFFFFFFF)) +#define COUNTER(A) ((ushort)((A) >> 48)) + +input string Filename = "calendar2.chn"; +input datetime Start; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const long day = 60 * 60 * 24; + datetime now = Start ? Start : (datetime)(TimeCurrent() / day * day); + + int handle = FileOpen(Filename, + FILE_READ | FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_BIN); + if(handle == INVALID_HANDLE) + { + PrintFormat("Can't open file '%s' for reading", Filename); + return; + } + + ChangeFileReader reader(handle, now); + + // read the file step by step, incrementing time artificially in this demo + while(!FileIsEnding(handle)) + { + // in real application testing could call reader.check on every tick, + // or postpond further checks until reader.getState().dt (if it's not LONG_MAX) + ulong records[]; + if(reader.check(now, records)) + { + Print(now); + ArrayPrint(records); + } + now += 60; // increment time by 1 minute at once, can be 1 second + } + + FileClose(handle); +} + +//+------------------------------------------------------------------+ +//| Single change (datetime + array of affected calendar record IDs) | +//+------------------------------------------------------------------+ +struct ChangeState +{ + datetime dt; + ulong ids[]; + + ChangeState(): dt(LONG_MAX) {} + ChangeState(const datetime at, ulong &_ids[]) + { + dt = at; + ArraySwap(ids, _ids); + } + void operator=(const ChangeState &other) + { + dt = other.dt; + ArrayCopy(ids, other.ids); + } +}; + +//+------------------------------------------------------------------+ +//| Class to walk through changes saved in a file for advancing time | +//+------------------------------------------------------------------+ +class ChangeFileReader +{ + const int handle; + ChangeState current; + const ChangeState zero; + +public: + ChangeFileReader(const int h, const datetime start = 0): handle(h) + { + if(readState()) + { + if(start) + { + ulong dummy[]; + check(start, dummy, true); // jump to first edit after 'start' + } + } + } + + bool check(datetime now, ulong &records[], const bool fastforward = false) + { + if(current.dt > now) return false; + + ArrayFree(records); + + if(!fastforward) + { + ArrayCopy(records, current.ids); + current = zero; + } + + while(readState() && current.dt <= now) + { + if(!fastforward) ArrayInsert(records, current.ids, ArraySize(records)); + } + + return true; + } + + bool readState() + { + if(FileIsEnding(handle)) return false; + ResetLastError(); + const ulong v = FileReadLong(handle); + current.dt = DATETIME(v); + ArrayFree(current.ids); + const int n = COUNTER(v); + for(int i = 0; i < n; ++i) + { + PUSH(current.ids, FileReadLong(handle)); + } + return _LastError == 0; + } + + ChangeState getState() const + { + return current; + } +}; +//+------------------------------------------------------------------+ diff --git a/CalendarChangeSaver.mq5 b/CalendarChangeSaver.mq5 new file mode 100644 index 0000000..c4f0703 --- /dev/null +++ b/CalendarChangeSaver.mq5 @@ -0,0 +1,120 @@ +//+------------------------------------------------------------------+ +//| CalendarChangeSaver.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Periodically request and save calendar change IDs and their timestamps." +#property service + +#define PACK_DATETIME_COUNTER(D,C) (D | (((ulong)(C)) << 48)) +#define DATETIME(A) ((datetime)((A) & 0x7FFFFFFFF)) +#define COUNTER(A) ((ushort)((A) >> 48)) +#define BULK_LIMIT 100 + +input string Filename = "calendar.chn"; +input int PeriodMsc = 1000; + +//+------------------------------------------------------------------+ +//| Generate user-friendly short description of the event | +//+------------------------------------------------------------------+ +string Description(const MqlCalendarValue &value) +{ + MqlCalendarEvent event; + MqlCalendarCountry country; + CalendarEventById(value.event_id, event); + CalendarCountryById(event.country_id, country); + return StringFormat("%lld (%s/%s @ %s)", + value.id, country.code, event.name, TimeToString(value.time)); +} + +//+------------------------------------------------------------------+ +//| Service program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + bool online = true; + ulong change = 0, last = 0; + int count = 0; + int handle = FileOpen(Filename, + FILE_WRITE | FILE_READ | FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_BIN); + if(handle == INVALID_HANDLE) + { + PrintFormat("Can't open file '%s' for writing", Filename); + return; + } + + const ulong p = FileSize(handle); + if(p > 0) + { + PrintFormat("Resuming file %lld bytes", p); + FileSeek(handle, 0, SEEK_END); + } + + Print("Requesting start ID..."); + + while(!IsStopped()) + { + if(!TerminalInfoInteger(TERMINAL_CONNECTED)) + { + if(online) + { + Print("Waiting for connection..."); + online = false; + } + Sleep(PeriodMsc); + continue; + } + else if(!online) + { + Print("Connected"); + online = true; + } + + MqlCalendarValue values[]; + const int n = CalendarValueLast(change, values); + if(n > 0) + { + // check for unreliable responce from terminal, + // when outdated irrelevant changes are returned, + // which usually happens just after terminal start-up + if(n >= BULK_LIMIT) + { + Print("New change ID: ", change); + PrintFormat("Too many records (%d), malfunction assumed, skipping", n); + } + else + { + string records = "[" + Description(values[0]); + for(int i = 1; i < n; ++i) + { + records += "," + Description(values[i]); + } + records += "]"; + Print("New change ID: ", change, " ", + TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS), "\n", records); + FileWriteLong(handle, PACK_DATETIME_COUNTER(TimeTradeServer(), n)); + for(int i = 0; i < n; ++i) + { + FileWriteLong(handle, values[i].id); + } + FileFlush(handle); + ++count; + } + } + else if(_LastError == 0) + { + if(!last && change) + { + Print("Start change ID obtained: ", change); + } + } + + last = change; + Sleep(PeriodMsc); + } + PrintFormat("%d records added", count); + FileClose(handle); +} +//+------------------------------------------------------------------+ diff --git a/CalendarCountries.mq5 b/CalendarCountries.mq5 new file mode 100644 index 0000000..93260621 --- /dev/null +++ b/CalendarCountries.mq5 @@ -0,0 +1,51 @@ +//+------------------------------------------------------------------+ +//| CalendarCountries.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Output a table of countries supported by built-in economic calendar." + +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + MqlCalendarCountry countries[]; + PRTF(CalendarCountries(countries)); + ArrayPrint(countries); +} +//+------------------------------------------------------------------+ +/* + + CalendarCountries(countries)=23 / ok + [id] [name] [code] [currency] [currency_symbol] [url_name] [reserved] + [ 0] 554 "New Zealand" "NZ" "NZD" "$" "new-zealand" ... + [ 1] 999 "European Union" "EU" "EUR" "€" "european-union" ... + [ 2] 392 "Japan" "JP" "JPY" "ВҐ" "japan" ... + [ 3] 124 "Canada" "CA" "CAD" "$" "canada" ... + [ 4] 36 "Australia" "AU" "AUD" "$" "australia" ... + [ 5] 156 "China" "CN" "CNY" "ВҐ" "china" ... + [ 6] 380 "Italy" "IT" "EUR" "€" "italy" ... + [ 7] 702 "Singapore" "SG" "SGD" "R$" "singapore" ... + [ 8] 276 "Germany" "DE" "EUR" "€" "germany" ... + [ 9] 250 "France" "FR" "EUR" "€" "france" ... + [10] 76 "Brazil" "BR" "BRL" "R$" "brazil" ... + [11] 484 "Mexico" "MX" "MXN" "Mex$" "mexico" ... + [12] 710 "South Africa" "ZA" "ZAR" "R" "south-africa" ... + [13] 344 "Hong Kong" "HK" "HKD" "HK$" "hong-kong" ... + [14] 356 "India" "IN" "INR" "в‚№" "india" ... + [15] 578 "Norway" "NO" "NOK" "Kr" "norway" ... + [16] 0 "Worldwide" "WW" "ALL" "" "worldwide" ... + [17] 840 "United States" "US" "USD" "$" "united-states" ... + [18] 826 "United Kingdom" "GB" "GBP" "ВЈ" "united-kingdom" ... + [19] 756 "Switzerland" "CH" "CHF" "в‚Ј" "switzerland" ... + [20] 410 "South Korea" "KR" "KRW" "в‚©" "south-korea" ... + [21] 724 "Spain" "ES" "EUR" "€" "spain" ... + [22] 752 "Sweden" "SE" "SEK" "Kr" "sweden" ... + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarEventKindsByCountry.mq5 b/CalendarEventKindsByCountry.mq5 new file mode 100644 index 0000000..54ab7fb --- /dev/null +++ b/CalendarEventKindsByCountry.mq5 @@ -0,0 +1,61 @@ +//+------------------------------------------------------------------+ +//| CalendarEventKindsByCountry.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Output a table of calendar event kinds (groups) for a given country." +#property script_show_inputs + +#include + +input string CountryCode = "HK"; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + MqlCalendarEvent events[]; + if(PRTF(CalendarEventByCountry(CountryCode, events))) + { + Print("Event kinds for country: ", CountryCode); + ArrayPrint(events); + } +} +//+------------------------------------------------------------------+ +/* + +CalendarEventByCountry(CountryCode,events)=26 / ok +Event kinds for country: HK + [id] [type] [sector] [frequency] [time_mode] [country_id] [unit] [importance] [multiplier] [digits] [source_url] [event_code] [name] [reserved] +[ 0] 344010001 1 5 2 0 344 6 1 3 1 "https://www.hkma.gov.hk/eng/" "foreign-exchange-reserves" "Foreign Exchange Reserves" ... +[ 1] 344010002 1 5 2 0 344 1 1 0 1 "https://www.hkma.gov.hk/eng/" "hkma-m3-money-supply-yy" "HKMA M3 Money Supply y/y" ... +[ 2] 344020001 1 4 2 0 344 1 1 0 1 "https://www.censtatd.gov.hk/en/" "cpi-yy" "CPI y/y" ... +[ 3] 344020002 1 2 3 0 344 1 3 0 1 "https://www.censtatd.gov.hk/en/" "gdp-qq" "GDP q/q" ... +[ 4] 344020003 1 2 3 0 344 1 2 0 1 "https://www.censtatd.gov.hk/en/" "gdp-yy" "GDP y/y" ... +[ 5] 344020004 1 6 2 0 344 1 1 0 1 "https://www.censtatd.gov.hk/en/" "exports-mm" "Exports y/y" ... +[ 6] 344020005 1 6 2 0 344 1 1 0 1 "https://www.censtatd.gov.hk/en/" "imports-mm" "Imports y/y" ... +[ 7] 344020006 1 6 2 0 344 2 2 3 3 "https://www.censtatd.gov.hk/en/" "trade-balance" "Trade Balance" ... +[ 8] 344020007 1 9 2 0 344 1 1 0 1 "https://www.censtatd.gov.hk/en/" "retail-sales-yy" "Retail Sales y/y" ... +[ 9] 344020008 1 3 2 0 344 1 2 0 1 "https://www.censtatd.gov.hk/en/" "unemployment-rate-3-months" "Unemployment Rate 3-Months" ... +[10] 344030001 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "new-years-day" "New Year's Day" ... +[11] 344030002 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "lunar-new-year" "Lunar New Year" ... +[12] 344030003 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "ching-ming-festival" "Ching Ming Festival" ... +[13] 344030004 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "good-friday" "Good Friday" ... +[14] 344030005 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "easter-monday" "Easter Monday" ... +[15] 344030006 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "birthday-of-buddha" "The Birthday of the Buddha" ... +[16] 344030007 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "labor-day" "Labor Day" ... +[17] 344030008 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "tuen-ng-festival" "Tuen Ng Festival" ... +[18] 344030009 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "hksar-establishment-day" "HKSAR Establishment Day" ... +[19] 344030010 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "day-following-mid-autumn-festival" "The Day Following Mid-Autumn Festival" ... +[20] 344030011 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "national-day" "National Day" ... +[21] 344030012 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "chung-yeung-festival" "Chung Yeung Festival" ... +[22] 344030013 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "christmas-day" "Christmas Day" ... +[23] 344030014 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "first-weekday-after-christmas-day" "The First Weekday After Christmas Day" ... +[24] 344030015 2 12 0 1 344 0 0 0 0 "https://publicholidays.hk/" "day-following-good-friday" "The Day Following Good Friday" ... +[25] 344500001 1 8 2 0 344 0 1 0 1 "https://www.markiteconomics.com" "nikkei-pmi" "S&P Global PMI" ... + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarEventKindsByCurrency.mq5 b/CalendarEventKindsByCurrency.mq5 new file mode 100644 index 0000000..b4479c3 --- /dev/null +++ b/CalendarEventKindsByCurrency.mq5 @@ -0,0 +1,75 @@ +//+------------------------------------------------------------------+ +//| CalendarEventKindsByCurrency.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Output a table of calendar event kinds (groups) for a given currency." +#property script_show_inputs + +#include + +input string Currency = "CNY"; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + MqlCalendarEvent events[]; + if(PRTF(CalendarEventByCurrency(Currency, events))) + { + Print("Event kinds for currency: ", Currency); + ArrayPrint(events); + } +} +//+------------------------------------------------------------------+ +/* + +CalendarEventByCurrency(Currency,events)=40 / ok +Event kinds for currency: CNY + [id] [type] [sector] [frequency] [time_mode] [country_id] [unit] [importance] [multiplier] [digits] [source_url] [event_code] [name] [reserved] +[ 0] 156010001 1 4 2 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "cpi-mm" "CPI m/m" ... +[ 1] 156010002 1 4 2 0 156 1 1 0 1 "http://www.stats.gov.cn/english/" "cpi-yy" "CPI y/y" ... +[ 2] 156010003 1 4 2 0 156 1 1 0 1 "http://www.stats.gov.cn/english/" "ppi-yy" "PPI y/y" ... +[ 3] 156010004 1 2 3 0 156 1 3 0 1 "http://www.stats.gov.cn/english/" "gdp-qq" "GDP q/q" ... +[ 4] 156010005 1 2 3 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "gdp-yy" "GDP y/y" ... +[ 5] 156010006 1 9 2 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "retail-sales-yy" "Retail Sales y/y" ... +[ 6] 156010007 1 8 2 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "industrial-production-yy" "Industrial Production y/y" ... +[ 7] 156010008 1 8 2 0 156 0 3 0 1 "http://www.stats.gov.cn/english/" "manufacturing-pmi" "Manufacturing PMI" ... +[ 8] 156010009 1 8 2 0 156 0 3 0 1 "http://www.stats.gov.cn/english/" "non-manufacturing-pmi" "Non-Manufacturing PMI" ... +[ 9] 156010010 1 8 2 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "fixed-asset-investment-yy" "Fixed Asset Investment y/y" ... +[10] 156010011 0 5 0 0 156 0 2 0 0 "http://www.stats.gov.cn/english/" "nbs-press-conference-on-economic-situation" "NBS Press Conference on Economic Situation" ... +[11] 156010012 1 3 2 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "unemployment-rate" "Unemployment Rate" ... +[12] 156010013 1 8 2 0 156 1 1 0 1 "http://www.stats.gov.cn/english/" "industrial-profit-yy" "Industrial Profit y/y" ... +[13] 156010014 1 8 2 0 156 1 1 0 1 "http://www.stats.gov.cn/english/" "industrial-profit-ytd-yy" "Industrial Profit YTD y/y" ... +[14] 156010015 1 8 2 0 156 0 3 0 1 "http://www.stats.gov.cn/english/" "composite-pmi" "Composite PMI" ... +[15] 156010016 1 8 2 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "industrial-production-ytd-yy" "Industrial Production YTD y/y" ... +[16] 156010017 1 9 2 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "retail-sales-ytd-yy" "Retail Sales YTD y/y" ... +[17] 156010018 1 2 3 0 156 1 2 0 1 "http://www.stats.gov.cn/english/" "gdp-ytd-yy" "GDP YTD y/y" ... +[18] 156020001 1 6 2 3 156 6 2 3 2 "http://english.customs.gov.cn/" "trade-balance-usd" "Trade Balance USD" ... +[19] 156020002 1 6 2 3 156 1 1 0 1 "http://english.customs.gov.cn/" "imports-usd-yy" "Imports USD y/y" ... +[20] 156020003 1 6 2 3 156 1 1 0 1 "http://english.customs.gov.cn/" "exports-usd-yy" "Exports USD y/y" ... +[21] 156020004 1 6 2 3 156 2 2 3 2 "http://english.customs.gov.cn/" "trade-balance" "Trade Balance" ... +[22] 156020005 1 6 2 3 156 1 1 0 1 "http://english.customs.gov.cn/" "imports-yy" "Imports y/y" ... +[23] 156020006 1 6 2 3 156 1 1 0 1 "http://english.customs.gov.cn/" "exports-yy" "Exports y/y" ... +[24] 156030001 1 5 2 3 156 6 1 4 3 "http://www.pbc.gov.cn/english/130437/index.html" "foreign-exchange-reserves" "Foreign Exchange Reserves" ... +[25] 156030002 1 5 2 3 156 2 2 4 3 "http://www.pbc.gov.cn/english/130437/index.html" "pbc-new-loans" "PBC New Loans" ... +[26] 156030003 1 5 2 3 156 1 1 0 1 "http://www.pbc.gov.cn/english/130437/index.html" "pbc-m2-money-stock-yy" "PBC M2 Money Stock y/y" ... +[27] 156030004 1 5 2 3 156 1 1 0 1 "http://www.pbc.gov.cn/english/130437/index.html" "pbc-outstanding-loan-growth-yy" "PBC Outstanding Loan Growth y/y" ... +[28] 156030005 0 5 0 0 156 0 3 0 0 "http://www.pbc.gov.cn/english/130437/index.html" "pbc-governor-yi-speech" "PBC Governor Yi Gang Speech" ... +[29] 156040001 2 12 0 1 156 0 0 0 0 "https://publicholidays.cn/" "new-years-day" "New Year's Day" ... +[30] 156040002 2 12 0 1 156 0 0 0 0 "https://publicholidays.cn/" "spring-festival" "Spring Festival" ... +[31] 156040003 2 12 0 1 156 0 0 0 0 "https://publicholidays.cn/" "ching-ming-festival" "Ching Ming Festival" ... +[32] 156040004 2 12 0 1 156 0 0 0 0 "https://publicholidays.cn/" "labor-day" "Labor Day" ... +[33] 156040005 2 12 0 1 156 0 0 0 0 "https://publicholidays.cn/" "dragon-boat-festival" "Dragon Boat Festival" ... +[34] 156040006 2 12 0 1 156 0 0 0 0 "https://publicholidays.cn/" "mid-autumn-festival" "Mid-Autumn Festival" ... +[35] 156040007 2 12 0 1 156 0 0 0 0 "https://publicholidays.cn/" "national-day" "National Day" ... +[36] 156050001 1 5 2 3 156 1 1 0 1 "http://english.mofcom.gov.cn/" "foreign-direct-investment-ytd-yy" "Foreign Direct Investment YTD y/y" ... +[37] 156500001 1 8 2 0 156 0 2 0 1 "https://www.markiteconomics.com" "caixin-manufacturing-pmi" "Caixin Manufacturing PMI" ... +[38] 156500002 1 8 2 0 156 0 2 0 1 "https://www.markiteconomics.com" "caixin-services-pmi" "Caixin Services PMI" ... +[39] 156500003 1 8 2 0 156 0 2 0 1 "https://www.markiteconomics.com" "caixin-composite-pmi" "Caixin Composite PMI" ... + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarFilterPrint.mq5 b/CalendarFilterPrint.mq5 new file mode 100644 index 0000000..ce91aa2 --- /dev/null +++ b/CalendarFilterPrint.mq5 @@ -0,0 +1,99 @@ +//+------------------------------------------------------------------+ +//| CalendarFilterPrint.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Request economic calendar events according to specified filters." +#property script_show_inputs + +#define LOGGING +#include +#include + +//+------------------------------------------------------------------+ +//| I N P U T S | +//+------------------------------------------------------------------+ +input string Context; // Context (country - 2 chars, currency - 3 chars, empty - all) +input ENUM_CALENDAR_SCOPE Scope = SCOPE_MONTH; +input string Text = "farm"; +input int Limit = -1; + +//+------------------------------------------------------------------+ +//| G L O B A L S | +//+------------------------------------------------------------------+ +CalendarFilter f(Context, TimeCurrent() - Scope, TimeCurrent() + Scope); + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + MqlCalendarValue records[]; + // setup appropriate filter conditions + f.let(CALENDAR_IMPORTANCE_LOW, GREATER) // medium or high priority + .let(LONG_MIN, CALENDAR_PROPERTY_RECORD_FORECAST, NOT_EQUAL) // forecast is available + .let(Text); // full-text search with wildcard '*' support + // NB: strings of 2 or 3 chars without wildcard + // will be treated as a country or currency code respectively + + // apply the filters and get results + if(f.select(records, true, Limit)) + { + static const ENUM_CALENDAR_PROPERTY props[] = + { + CALENDAR_PROPERTY_RECORD_TIME, + CALENDAR_PROPERTY_COUNTRY_CURRENCY, + CALENDAR_PROPERTY_EVENT_NAME, + CALENDAR_PROPERTY_EVENT_IMPORTANCE, + CALENDAR_PROPERTY_RECORD_ACTUAL, + CALENDAR_PROPERTY_RECORD_FORECAST, + CALENDAR_PROPERTY_RECORD_PREVISED, + CALENDAR_PROPERTY_RECORD_IMPACT, + CALENDAR_PROPERTY_EVENT_SECTOR, + }; + static const int p = ArraySize(props); + + // output formatted results + string result[]; + if(f.format(records, props, result, true, true)) + { + for(int i = 0; i < ArraySize(result) / p; ++i) + { + Print(SubArrayCombine(result, " | ", i * p, p)); + } + } + } + else + { + Print("No calendar events for selected filters"); + } +} +//+------------------------------------------------------------------+ +/* + +example for default settings: +- Context = empty, meaning no specific conditions for countries or currencies +- Scope = SCOPE_MONTH +- Text = "farm" means context search within event names +- Limit = -1, no cut off: any number of matching events will be printed + +Selecting calendar records... +country[i]= / ok +calendarValueHistory(temp,from,to,country[i],c)=2372 / ok +Filtering 2372 records +Got 9 records + TIME | CURвЃћ | NAME | IMPORTANвЃћ | ACTUвЃћ | FOREвЃћ | PREVвЃћ | IMPACT | SECTвЃћ +2022.06.02 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +128 | -225 | +202 | POSITIVE | JOBS +2022.06.02 15:30 | USD | Nonfarm Productivity q/q | MODERATE | -7.3 | -7.5 | -7.5 | POSITIVE | JOBS +2022.06.03 15:30 | USD | Nonfarm Payrolls | HIGH | +390 | -19 | +436 | POSITIVE | JOBS +2022.06.03 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +333 | +8 | +405 | POSITIVE | JOBS +2022.06.09 08:30 | EUR | Nonfarm Payrolls q/q | MODERATE | +0.3 | +0.3 | +0.3 | NA | JOBS + — | — | — | — | — | — | — | — | — +2022.07.07 15:15 | USD | ADP Nonfarm Employment Change | HIGH | +nan | -263 | +128 | NA | JOBS +2022.07.08 15:30 | USD | Nonfarm Payrolls | HIGH | +nan | -229 | +390 | NA | JOBS +2022.07.08 15:30 | USD | Private Nonfarm Payrolls | MODERATE | +nan | +51 | +333 | NA | JOBS + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarForDates.mq5 b/CalendarForDates.mq5 new file mode 100644 index 0000000..fff8795 --- /dev/null +++ b/CalendarForDates.mq5 @@ -0,0 +1,115 @@ +//+------------------------------------------------------------------+ +//| CalendarForDates.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Output a table of calendar records for specific range of days, with a filter for country and/or currency." +#property script_show_inputs + +#include +#include + +#define DAY_LONG (60 * 60 * 24) +#define WEEK_LONG (DAY_LONG * 7) +#define MONTH_LONG (DAY_LONG * 30) +#define YEAR_LONG (MONTH_LONG * 12) + +enum ENUM_CALENDAR_SCOPE +{ + SCOPE_DAY = DAY_LONG, // Day + SCOPE_WEEK = WEEK_LONG, // Week + SCOPE_MONTH = MONTH_LONG, // Month + SCOPE_YEAR = YEAR_LONG, // Year +}; + +input string CountryCode = "EU"; +input string Currency = ""; +input ENUM_CALENDAR_SCOPE Scope = SCOPE_DAY; + +//+------------------------------------------------------------------+ +//| Extended struct with user-friendly data from MqlCalendarValue | +//+------------------------------------------------------------------+ +struct MqlCalendarRecord: public MqlCalendarValue +{ + static const string importances[]; + + string importance; + string name; + string currency; + string code; + double actual, previous, revised, forecast; + + MqlCalendarRecord() { } + + MqlCalendarRecord(const MqlCalendarValue &value) + { + this = value; + extend(); + } + + void extend() + { + MqlCalendarEvent event; + CalendarEventById(event_id, event); + + importance = importances[event.importance]; + name = event.name; + + MqlCalendarCountry country; + CalendarCountryById(event.country_id, country); + + currency = country.currency; + code = country.code; + + MqlCalendarValue value = this; + + // Neither one of the following works: + // GetActualValue(); + // this.GetActualValue(); + // MqlCalendarValue::GetActualValue(); + + actual = value.GetActualValue(); + previous = value.GetPreviousValue(); + revised = value.GetRevisedValue(); + forecast = value.GetForecastValue(); + } +}; + +static const string MqlCalendarRecord::importances[] = {"None", "Low", "Medium", "High"}; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + MqlCalendarValue values[]; + MqlCalendarRecord records[]; + datetime from = TimeCurrent() - Scope; + datetime to = TimeCurrent() + Scope; + if(PRTF(CalendarValueHistory(values, from, to, CountryCode, Currency))) + { + for(int i = 0; i < ArraySize(values); ++i) + { + PUSH(records, MqlCalendarRecord(values[i])); + } + Print("Near past and future calendar records (extended): "); + ArrayPrint(records); + } +} +//+------------------------------------------------------------------+ +/* + +CalendarValueHistory(values,from,to,CountryCode,Currency)=6 / ok +Near past and future calendar records (extended): + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] [importance] [name] [currency] [code] [actual] [previous] [revised] [forecast] +[0] 162723 999020003 2022.06.23 03:00:00 1970.01.01 00:00:00 0 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808 0 ... "High" "EU Leaders Summit" "EUR" "EU" nan nan nan nan +[1] 162724 999020003 2022.06.24 03:00:00 1970.01.01 00:00:00 0 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808 0 ... "High" "EU Leaders Summit" "EUR" "EU" nan nan nan nan +[2] 168518 999010034 2022.06.24 11:00:00 1970.01.01 00:00:00 0 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808 0 ... "Medium" "ECB Supervisory Board Member McCaul Speech" "EUR" "EU" nan nan nan nan +[3] 168515 999010031 2022.06.24 13:10:00 1970.01.01 00:00:00 0 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808 0 ... "Medium" "ECB Supervisory Board Member Fernandez-Bollo Speech" "EUR" "EU" nan nan nan nan +[4] 168509 999010014 2022.06.24 14:30:00 1970.01.01 00:00:00 0 -9223372036854775808 -9223372036854775808 -9223372036854775808 -9223372036854775808 0 ... "Medium" "ECB Vice President de Guindos Speech" "EUR" "EU" nan nan nan nan +[5] 161014 999520001 2022.06.24 22:30:00 2022.06.21 00:00:00 0 -9223372036854775808 -6000000 -9223372036854775808 -9223372036854775808 0 ... "Low" "CFTC EUR Non-Commercial Net Positions" "EUR" "EU" nan -6.00000 nan nan + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarMonitor.mq5 b/CalendarMonitor.mq5 new file mode 100644 index 0000000..ebf89aa --- /dev/null +++ b/CalendarMonitor.mq5 @@ -0,0 +1,195 @@ +//+------------------------------------------------------------------+ +//| CalendarMonitor.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property description "Output a table with selected calendar events." +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +// #define LOGGING +#include +#include +#include +#include + +//+------------------------------------------------------------------+ +//| I N P U T S | +//+------------------------------------------------------------------+ +input group "General filters"; +input string Context; // Context (country - 2 chars, currency - 3 chars, empty - all) +input ENUM_CALENDAR_SCOPE Scope = SCOPE_WEEK; +input bool UseChartCurrencies = true; + +input group "Optional filters"; +input ENUM_CALENDAR_EVENT_TYPE_EXT Type = TYPE_ANY; +input ENUM_CALENDAR_EVENT_SECTOR_EXT Sector = SECTOR_ANY; +input ENUM_CALENDAR_EVENT_IMPORTANCE_EXT Importance = IMPORTANCE_MODERATE; // Importance (at least) +input string Text; +input ENUM_CALENDAR_HAS_VALUE HasActual = HAS_ANY; +input ENUM_CALENDAR_HAS_VALUE HasForecast = HAS_ANY; +input ENUM_CALENDAR_HAS_VALUE HasPrevious = HAS_ANY; +input ENUM_CALENDAR_HAS_VALUE HasRevised = HAS_ANY; +input int Limit = 30; + +input group "Rendering settings"; +input ENUM_BASE_CORNER Corner = CORNER_RIGHT_LOWER; +input int Margins = 8; +input int FontSize = 8; +input string FontName = "Consolas"; +input color BackgroundColor = clrSilver; +input uchar BackgroundTransparency = 128; // BackgroundTransparency (255 - opaque, 0 - glassy) + +//+------------------------------------------------------------------+ +//| G L O B A L S | +//+------------------------------------------------------------------+ +CalendarFilter f(Context); +AutoPtr t; + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(!f.isLoaded()) return INIT_FAILED; + + if(UseChartCurrencies) + { + const string base = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE); + const string profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT); + f.let(base); + if(base != profit) + { + f.let(profit); + } + } + + if(Type != TYPE_ANY) + { + f.let((ENUM_CALENDAR_EVENT_TYPE)Type); + } + + if(Sector != SECTOR_ANY) + { + f.let((ENUM_CALENDAR_EVENT_SECTOR)Sector); + } + + if(Importance != IMPORTANCE_ANY) + { + f.let((ENUM_CALENDAR_EVENT_IMPORTANCE)(Importance - 1), GREATER); + } + + if(StringLen(Text)) + { + f.let(Text); + } + + if(HasActual != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_ACTUAL, HasActual == HAS_SET ? NOT_EQUAL : EQUAL); + } + + if(HasPrevious != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_PREVIOUS, HasPrevious == HAS_SET ? NOT_EQUAL : EQUAL); + } + + if(HasRevised != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_REVISED, HasRevised == HAS_SET ? NOT_EQUAL : EQUAL); + } + + if(HasForecast != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_FORECAST, HasForecast == HAS_SET ? NOT_EQUAL : EQUAL); + } + + EventSetTimer(1); + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Timer event handler (main processing of calendar goes here) | +//+------------------------------------------------------------------+ +void OnTimer() +{ + static const ENUM_CALENDAR_PROPERTY props[] = + { + CALENDAR_PROPERTY_RECORD_TIME, + CALENDAR_PROPERTY_COUNTRY_CURRENCY, + CALENDAR_PROPERTY_EVENT_NAME, + CALENDAR_PROPERTY_EVENT_IMPORTANCE, + CALENDAR_PROPERTY_RECORD_ACTUAL, + CALENDAR_PROPERTY_RECORD_FORECAST, + CALENDAR_PROPERTY_RECORD_PREVISED, + CALENDAR_PROPERTY_RECORD_IMPACT, + CALENDAR_PROPERTY_EVENT_SECTOR, + }; + static const int p = ArraySize(props); + + MqlCalendarValue records[]; + + f.let(TimeTradeServer() - Scope, TimeTradeServer() + Scope); + + const ulong trackID = f.getChangeID(); + if(trackID) // already has a state, try to detect changes + { + if(f.update(records)) // find changes that match filters + { + // notify user about new changes + string result[]; + f.format(records, props, result); + for(int i = 0; i < ArraySize(result) / p; ++i) + { + Alert(SubArrayCombine(result, " | ", i * p, p)); + } + // fall through to the table redraw + } + else if(trackID == f.getChangeID()) + { + return; // no changes in the calendar + } + } + + // request complete set of events according to filters + f.select(records, true, Limit); + + // rebuild the table displayed on chart + string result[]; + f.format(records, props, result, true, true); + + /* + // on-chart table copy in the log + for(int i = 0; i < ArraySize(result) / p; ++i) + { + Print(SubArrayCombine(result, " | ", i * p, p)); + } + */ + + if(t[] == NULL || t[].getRows() != ArraySize(records) + 1) + { + t = new Tableau("CALT", ArraySize(records) + 1, p, + TBL_CELL_HEIGHT_AUTO, TBL_CELL_WIDTH_AUTO, + Corner, Margins, FontSize, FontName, FontName + " Bold", + TBL_FLAG_ROW_0_HEADER, + BackgroundColor, BackgroundTransparency); + } + const string hints[] = {}; + t[].fill(result, hints); +} + +//+------------------------------------------------------------------+ +//| Custom indicator iteration function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ diff --git a/CalendarMonitorCached.mq5 b/CalendarMonitorCached.mq5 new file mode 100644 index 0000000..b65d398 --- /dev/null +++ b/CalendarMonitorCached.mq5 @@ -0,0 +1,234 @@ +//+------------------------------------------------------------------+ +//| CalendarMonitorCached.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property description "Output a table with selected calendar events." +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 +#property tester_file "xyz.cal" + +#define LOGGING +#include +#include +#include +#include +#include + +//+------------------------------------------------------------------+ +//| I N P U T S | +//+------------------------------------------------------------------+ +input group "General filters"; +input string Context; // Context (country - 2 chars, currency - 3 chars, empty - all) +input ENUM_CALENDAR_SCOPE Scope = SCOPE_WEEK; +input bool UseChartCurrencies = true; +input string CalendarCacheFile = "xyz.cal"; + +input group "Optional filters"; +input ENUM_CALENDAR_EVENT_TYPE_EXT Type = TYPE_ANY; +input ENUM_CALENDAR_EVENT_SECTOR_EXT Sector = SECTOR_ANY; +input ENUM_CALENDAR_EVENT_IMPORTANCE_EXT Importance = IMPORTANCE_MODERATE; // Importance (at least) +input string Text; +input ENUM_CALENDAR_HAS_VALUE HasActual = HAS_ANY; +input ENUM_CALENDAR_HAS_VALUE HasForecast = HAS_ANY; +input ENUM_CALENDAR_HAS_VALUE HasPrevious = HAS_ANY; +input ENUM_CALENDAR_HAS_VALUE HasRevised = HAS_ANY; +input int Limit = 30; + +input group "Rendering settings"; +input ENUM_BASE_CORNER Corner = CORNER_RIGHT_LOWER; +input int Margins = 8; +input int FontSize = 8; +input string FontName = "Consolas"; +input color BackgroundColor = clrSilver; +input uchar BackgroundTransparency = 128; // BackgroundTransparency (255 - opaque, 0 - glassy) + +//+------------------------------------------------------------------+ +//| G L O B A L S | +//+------------------------------------------------------------------+ +AutoPtr fptr; +AutoPtr t; +AutoPtr cache; + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + cache = new CalendarCache(CalendarCacheFile, true); + if(cache[].isLoaded()) + { + fptr = new CalendarFilterCached(cache[]); + } + else + { + if(MQLInfoInteger(MQL_TESTER)) + { + Print("Can't run in the tester without calendar cache file"); + return INIT_FAILED; + } + else + if(StringLen(CalendarCacheFile)) + { + Alert("Calendar cache not found, trying to create '" + CalendarCacheFile + "'"); + cache = new CalendarCache(); + if(cache[].save(CalendarCacheFile)) + { + Alert("File saved. Re-run indicator in online chart or in the tester"); + } + else + { + Alert("Error: ", _LastError); + } + ChartIndicatorDelete(0, 0, MQLInfoString(MQL_PROGRAM_NAME)); + return INIT_PARAMETERS_INCORRECT; + } + Alert("Currently working in online mode (no cache)"); + fptr = new CalendarFilter(Context); + } + CalendarFilter *f = fptr[]; + + if(!f.isLoaded()) return INIT_FAILED; + + if(UseChartCurrencies) + { + const string base = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE); + const string profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT); + f.let(base); + if(base != profit) + { + f.let(profit); + } + } + + if(Type != TYPE_ANY) + { + f.let((ENUM_CALENDAR_EVENT_TYPE)Type); + } + + if(Sector != SECTOR_ANY) + { + f.let((ENUM_CALENDAR_EVENT_SECTOR)Sector); + } + + if(Importance != IMPORTANCE_ANY) + { + f.let((ENUM_CALENDAR_EVENT_IMPORTANCE)(Importance - 1), GREATER); + } + + if(StringLen(Text)) + { + f.let(Text); + } + + if(HasActual != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_ACTUAL, HasActual == HAS_SET ? NOT_EQUAL : EQUAL); + } + + if(HasPrevious != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_PREVIOUS, HasPrevious == HAS_SET ? NOT_EQUAL : EQUAL); + } + + if(HasRevised != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_REVISED, HasRevised == HAS_SET ? NOT_EQUAL : EQUAL); + } + + if(HasForecast != HAS_ANY) + { + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_FORECAST, HasForecast == HAS_SET ? NOT_EQUAL : EQUAL); + } + + EventSetTimer(1); + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Timer event handler (main processing of calendar goes here) | +//+------------------------------------------------------------------+ +void OnTimer() +{ + CalendarFilter *f = fptr[]; + + static const ENUM_CALENDAR_PROPERTY props[] = + { + CALENDAR_PROPERTY_RECORD_TIME, + CALENDAR_PROPERTY_COUNTRY_CURRENCY, + CALENDAR_PROPERTY_EVENT_NAME, + CALENDAR_PROPERTY_EVENT_IMPORTANCE, + CALENDAR_PROPERTY_RECORD_ACTUAL, + CALENDAR_PROPERTY_RECORD_FORECAST, + CALENDAR_PROPERTY_RECORD_PREVISED, + CALENDAR_PROPERTY_RECORD_IMPACT, + CALENDAR_PROPERTY_EVENT_SECTOR, + }; + static const int p = ArraySize(props); + + MqlCalendarValue records[]; + + f.let(TimeTradeServer() - Scope, TimeTradeServer() + Scope); + + const ulong trackID = f.getChangeID(); + if(trackID) // already has a state, try to detect changes + { + if(f.update(records)) // find changes that match filters + { + // notify user about new changes + string result[]; + f.format(records, props, result); + for(int i = 0; i < ArraySize(result) / p; ++i) + { + Alert(SubArrayCombine(result, " | ", i * p, p)); + } + // fall through to the table redraw + } + else if(trackID == f.getChangeID()) + { + return; // no changes in the calendar + } + } + + // request complete set of events according to filters + f.select(records, true, Limit); + + // rebuild the table displayed on chart + string result[]; + f.format(records, props, result, true, true); + + /* + // on-chart table copy in the log + for(int i = 0; i < ArraySize(result) / p; ++i) + { + Print(SubArrayCombine(result, " | ", i * p, p)); + } + */ + + if(t[] == NULL || t[].getRows() != ArraySize(records) + 1) + { + t = new Tableau("CALT", ArraySize(records) + 1, p, + TBL_CELL_HEIGHT_AUTO, TBL_CELL_WIDTH_AUTO, + Corner, Margins, FontSize, FontName, FontName + " Bold", + TBL_FLAG_ROW_0_HEADER, + BackgroundColor, BackgroundTransparency); + } + const string hints[] = {}; + t[].fill(result, hints); +} + +//+------------------------------------------------------------------+ +//| Custom indicator iteration function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ diff --git a/CalendarRecordById.mq5 b/CalendarRecordById.mq5 new file mode 100644 index 0000000..9a003bc --- /dev/null +++ b/CalendarRecordById.mq5 @@ -0,0 +1,191 @@ +//+------------------------------------------------------------------+ +//| CalendarRecordById.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Finds nearest forthcoming news and monitors it for updates." +#property indicator_chart_window +#property indicator_plots 0 + +#include +#include +#include +#include + +#define DAY_LONG (60 * 60 * 24) +#define WEEK_LONG (DAY_LONG * 7) +#define MONTH_LONG (DAY_LONG * 30) +#define YEAR_LONG (MONTH_LONG * 12) + +input uint TimerSeconds = 5; + +//+------------------------------------------------------------------+ +//| Extended struct with user-friendly data from MqlCalendarValue | +//+------------------------------------------------------------------+ +struct MqlCalendarRecord: public MqlCalendarValue +{ + static const string importances[]; + + string importance; + string name; + string currency; + string code; + double actual, previous, revised, forecast; + + MqlCalendarRecord() { ZeroMemory(this); } + + MqlCalendarRecord(const MqlCalendarValue &value) + { + this = value; + extend(); + } + + void extend() + { + MqlCalendarEvent event; + CalendarEventById(event_id, event); + + importance = importances[event.importance]; + name = event.name; + + MqlCalendarCountry country; + CalendarCountryById(event.country_id, country); + + currency = country.currency; + code = country.code; + + MqlCalendarValue value = this; + + // Neither one of the following works: + // GetActualValue(); + // this.GetActualValue(); + // MqlCalendarValue::GetActualValue(); + + actual = value.GetActualValue(); + previous = value.GetPreviousValue(); + revised = value.GetRevisedValue(); + forecast = value.GetForecastValue(); + } +}; + +static const string MqlCalendarRecord::importances[] = {"None", "Low", "Medium", "High"}; + +MqlCalendarValue track; + +//+------------------------------------------------------------------+ +//| Compare plain structs | +//+------------------------------------------------------------------+ +template +int StructCompare(const S &s1, const S &s2) +{ + uchar array1[], array2[]; + if(StructToCharArray(s1, array1) && StructToCharArray(s2, array2)) + { + return ArrayCompare(array1, array2); + } + return -2; +} + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +void OnInit() +{ + EventSetTimer(TimerSeconds); +} + +//+------------------------------------------------------------------+ +//| Timer event handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + if(!track.id) + { + MqlCalendarValue values[]; + if(PRTF(CalendarValueHistory(values, TimeCurrent(), TimeCurrent() + DAY_LONG * 3))) + { + for(int i = 0; i < ArraySize(values); ++i) + { + MqlCalendarEvent event; + CalendarEventById(values[i].event_id, event); + if(event.type == CALENDAR_TYPE_INDICATOR && !values[i].HasActualValue()) + { + track = values[i]; + PrintFormat("Started monitoring %lld", track.id); + StructPrint(MqlCalendarRecord(track), ARRAYPRINT_HEADER); + return; + } + } + } + } + else + { + MqlCalendarValue update; + if(CalendarValueById(track.id, update)) + { + if(fabs(StructCompare(track, update)) == 1) + { + Alert(StringFormat("News %lld changed", track.id)); + PrintFormat("New state of %lld", track.id); + StructPrint(MqlCalendarRecord(update), ARRAYPRINT_HEADER); + if(update.HasActualValue()) + { + Print("Timer stopped"); + EventKillTimer(); + } + else + { + track = update; + } + } + } + + if(TimeCurrent() <= track.time) + { + Comment("Forthcoming event time: ", track.time, + ", remaining: ", Timing::stringify((uint)(track.time - TimeCurrent()))); + } + else + { + Comment("Forthcoming event time: ", track.time, + ", late for: ", Timing::stringify((uint)(TimeCurrent() - track.time))); + } + } +} + +//+------------------------------------------------------------------+ +//| Custom indicator iteration function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + Comment(""); +} +//+------------------------------------------------------------------+ +/* + +CalendarValueHistory(values,TimeCurrent(),TimeCurrent()+(60*60*24)*3)=186 / ok +Started monitoring 156045 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] [importance] [name] [currency] [code] [actual] [previous] [revised] [forecast] +156045 840020013 2022.06.27 15:30:00 2022.05.01 00:00:00 0 -9223372036854775808 400000 -9223372036854775808 0 0 ... "Medium" "Durable Goods Orders m/m" "USD" "US" nan 0.40000 nan 0.00000 +... +Alert: News 156045 changed +New state of 156045 + [id] [event_id] [time] [period] [revision] [actual_value] [prev_value] [revised_prev_value] [forecast_value] [impact_type] [reserved] [importance] [name] [currency] [code] [actual] [previous] [revised] [forecast] +156045 840020013 2022.06.27 15:30:00 2022.05.01 00:00:00 0 700000 400000 -9223372036854775808 0 1 ... "Medium" "Durable Goods Orders m/m" "USD" "US" 0.70000 0.40000 nan 0.00000 +Timer stopped + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarStatsByEvent.mq5 b/CalendarStatsByEvent.mq5 new file mode 100644 index 0000000..8d20ba3 --- /dev/null +++ b/CalendarStatsByEvent.mq5 @@ -0,0 +1,170 @@ +//+------------------------------------------------------------------+ +//| CalendarStatsByEvent.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Output a table of calendar statistics by event kind for specific range of days and country or currency." +#property script_show_inputs + +#include +#include +#include + +#define DAY_LONG (60 * 60 * 24) +#define WEEK_LONG (DAY_LONG * 7) +#define MONTH_LONG (DAY_LONG * 30) +#define YEAR_LONG (MONTH_LONG * 12) + +enum ENUM_CALENDAR_SCOPE +{ + SCOPE_DAY = DAY_LONG, // Day + SCOPE_WEEK = WEEK_LONG, // Week + SCOPE_MONTH = MONTH_LONG, // Month + SCOPE_YEAR = YEAR_LONG, // Year +}; + +input string CountryOrCurrency = "EU"; +input ENUM_CALENDAR_SCOPE Scope = SCOPE_YEAR; + +//+------------------------------------------------------------------+ +//| Struct for event statistics | +//+------------------------------------------------------------------+ +struct CalendarEventStats +{ + static const string importances[]; + + ulong id; + string name; + string importance; + int count; +}; + +static const string CalendarEventStats::importances[] = {"None", "Low", "Medium", "High"}; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + MqlCalendarEvent events[]; + MqlCalendarValue values[]; + CalendarEventStats stats[]; + + const datetime from = TimeCurrent() - Scope; + const datetime to = TimeCurrent() + Scope; + + if(StringLen(CountryOrCurrency) == 2) + { + PRTF(CalendarEventByCountry(CountryOrCurrency, events)); + } + else + { + PRTF(CalendarEventByCurrency(CountryOrCurrency, events)); + } + + for(int i = 0; i < ArraySize(events); ++i) + { + ResetLastError(); + if(CalendarValueHistoryByEvent(events[i].id, values, from, to)) + { + CalendarEventStats event = {events[i].id, events[i].name, + CalendarEventStats::importances[events[i].importance], ArraySize(values)}; + PUSH(stats, event); + } + else + { + if(_LastError != 0) + { + PrintFormat("Error %d for %lld", _LastError, events[i].id); + } + } + } + + SORT_STRUCT(CalendarEventStats, stats, count); + ArrayReverse(stats); + ArrayPrint(stats); +} +//+------------------------------------------------------------------+ +/* + +CalendarEventByCountry(CountryOrCurrency,events)=82 / ok + [id] [name] [importance] [count] +[ 0] 999520001 "CFTC EUR Non-Commercial Net Positions" "Low" 79 +[ 1] 999010029 "ECB President Lagarde Speech" "High" 69 +[ 2] 999010035 "ECB Executive Board Member Elderson Speech" "Medium" 37 +[ 3] 999030027 "Core CPI" "Low" 36 +[ 4] 999030026 "CPI" "Low" 36 +[ 5] 999030025 "CPI excl. Energy and Unprocessed Food y/y" "Low" 36 +[ 6] 999030024 "CPI excl. Energy and Unprocessed Food m/m" "Low" 36 +[ 7] 999030010 "Core CPI m/m" "Medium" 36 +[ 8] 999030013 "CPI y/y" "Low" 36 +[ 9] 999030012 "Core CPI y/y" "Low" 36 +[10] 999040006 "Consumer Confidence Index" "Low" 36 +[11] 999030011 "CPI m/m" "Medium" 36 +[12] 999010033 "ECB Executive Board Member Schnabel Speech" "Medium" 35 +[13] 999010014 "ECB Vice President de Guindos Speech" "Medium" 34 +[14] 999010020 "ECB Executive Board Member Lane Speech" "Medium" 31 +[15] 999010021 "ECB Supervisory Board Chair Enria Speech" "Medium" 31 +[16] 999010032 "ECB Executive Board Member Panetta Speech" "Medium" 30 +[17] 999500003 "S&P Global Composite PMI" "Medium" 26 +[18] 999500002 "S&P Global Services PMI" "Medium" 26 +[19] 999500001 "S&P Global Manufacturing PMI" "Medium" 26 +[20] 999060001 "Sentix Investor Confidence" "Low" 24 +[21] 999010031 "ECB Supervisory Board Member Fernandez-Bollo Speech" "Medium" 22 +[22] 999010016 "Current Account" "Low" 20 +[23] 999010017 "Current Account n.s.a." "Low" 20 +[24] 999050001 "ZEW Economic Sentiment Indicator" "Medium" 19 +[25] 999010018 "ECB M3 Money Supply y/y" "Low" 19 +[26] 999010026 "ECB Non-Financial Corporations Loans y/y" "Low" 19 +[27] 999010023 "Official Reserve Assets" "Low" 19 +[28] 999010027 "ECB Private Sector Loans y/y" "Low" 19 +[29] 999010034 "ECB Supervisory Board Member McCaul Speech" "Medium" 19 +[30] 999010019 "ECB Households Loans y/y" "Low" 19 +[31] 999040008 "Industry Selling Price Expectations" "Low" 18 +[32] 999040007 "Consumer Price Expectations" "Low" 18 +[33] 999040005 "Economic Sentiment Indicator" "Low" 18 +[34] 999040004 "Services Sentiment Indicator" "Low" 18 +[35] 999040003 "Industrial Confidence Indicator" "Low" 18 +[36] 999030022 "CPI excl. Tobacco y/y" "Low" 18 +[37] 999030021 "CPI excl. Tobacco m/m" "Medium" 18 +[38] 999030020 "Unemployment Rate" "Medium" 18 +[39] 999030019 "Trade Balance n.s.a." "Medium" 18 +[40] 999030018 "Trade Balance" "Medium" 18 +[41] 999030017 "GDP y/y" "Medium" 18 +[42] 999030016 "GDP q/q" "High" 18 +[43] 999030015 "Construction Output y/y" "Low" 18 +[44] 999030014 "Construction Output m/m" "Low" 18 +[45] 999030008 "Industrial Production y/y" "Low" 18 +[46] 999030007 "Industrial Production m/m" "Medium" 18 +[47] 999030006 "PPI y/y" "Low" 18 +[48] 999030005 "PPI m/m" "Medium" 18 +[49] 999030004 "Retail Sales y/y" "Medium" 18 +[50] 999030003 "Retail Sales m/m" "High" 18 +[51] 999010001 "ECB Non-monetary Policy Meeting" "Medium" 18 +[52] 999020001 "Economic and Financial Affairs Council Meeting" "Medium" 18 +[53] 999010015 "ECB Marginal Lending Facility Rate Decision" "High" 16 +[54] 999010024 "ECB Monetary Policy Statement" "Medium" 16 +[55] 999010006 "ECB Deposit Facility Rate Decision" "High" 16 +[56] 999010003 "ECB Monetary Policy Press Conference" "High" 16 +[57] 999010007 "ECB Interest Rate Decision" "High" 16 +[58] 999020003 "EU Leaders Summit" "High" 15 +[59] 999020002 "Eurogroup Meeting" "Medium" 15 +[60] 999500004 "S&P Global Construction PMI" "Medium" 13 +[61] 999030028 "Employment Level" "Low" 12 +[62] 999030001 "Employment Change q/q" "High" 12 +[63] 999030002 "Employment Change y/y" "Medium" 12 +[64] 999010002 "ECB Monetary Policy Meeting Accounts" "Medium" 10 +[65] 999010008 "ECB Economic Bulletin" "Medium" 8 +[66] 999030023 "Wage Costs y/y" "Medium" 6 +[67] 999030009 "Labour Cost Index" "Low" 6 +[68] 999010025 "ECB Bank Lending Survey" "Low" 6 +[69] 999010030 "ECB Supervisory Board Member af Jochnick Speech" "Medium" 4 +[70] 999010022 "ECB Supervisory Board Member Hakkarainen Speech" "Medium" 3 +[71] 999010028 "ECB Financial Stability Review" "Medium" 3 +[72] 999010009 "ECB Targeted LTRO" "Medium" 2 +[73] 999010036 "ECB Supervisory Board Member Tuominen Speech" "Medium" 1 + +*/ +//+------------------------------------------------------------------+ diff --git a/CalendarTrading.mq5 b/CalendarTrading.mq5 new file mode 100644 index 0000000..0d88806 --- /dev/null +++ b/CalendarTrading.mq5 @@ -0,0 +1,275 @@ +//+------------------------------------------------------------------+ +//| CalendarTrading.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Trade by calendar events in the tester or online." +#property tester_file "xyz.cal" + +#define SHOW_WARNINGS // output extended info into the log, with changes in data state +#define WARNING Print // use simple Print for warnings (instead of a built-in format with line numbers etc.) +#define LOGGING // calendar detailed logs +#include +#include +#include +#include +#include +#include +#include + +input double Volume; // Volume (0 = minimal lot) +input int Distance2SLTP = 500; // Distance to SL/TP in points (0 = no) +input uint MultiplePositions = 25; +sinput ulong EventID; +sinput string Text; + +AutoPtr fptr; +AutoPtr cache; +AutoPtr trailing[]; + +double Lot; +bool Hedging; +string Base; +string Profit; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(AccountInfoInteger(ACCOUNT_TRADE_MODE) != ACCOUNT_TRADE_MODE_DEMO) + { + Alert("This is a test EA! Run it on a DEMO account only!"); + return INIT_FAILED; + } + + Lot = Volume == 0 ? SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) : Volume; + Hedging = AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING; + Base = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_BASE); + Profit = SymbolInfoString(_Symbol, SYMBOL_CURRENCY_PROFIT); + + cache = new CalendarCache("xyz.cal", true); + if(cache[].isLoaded()) + { + fptr = new CalendarFilterCached(cache[]); + } + else + { + if(!MQLInfoInteger(MQL_TESTER)) + { + Print("Calendar cache file not found, fall back to online mode"); + fptr = new CalendarFilter(); + } + else + { + Print("Can't proceed in the tester without calendar cache file"); + return INIT_FAILED; + } + } + CalendarFilter *f = fptr[]; + + if(!f.isLoaded()) return INIT_FAILED; + + // if a specific event is given, use it + if(EventID > 0) f.let(EventID); + else + { + // otherwise track news for current chart currencies only + f.let(Base); + if(Base != Profit) + { + f.let(Profit); + } + + // monitor high impact economic indicators with available forecasts + f.let(CALENDAR_TYPE_INDICATOR); + f.let(LONG_MIN, CALENDAR_PROPERTY_RECORD_FORECAST, NOT_EQUAL); + f.let(CALENDAR_IMPORTANCE_HIGH); + + if(StringLen(Text)) f.let(Text); + } + + f.describe(); + + if(Distance2SLTP) + { + ArrayResize(trailing, Hedging && MultiplePositions ? MultiplePositions : 1); + } + // setup timer for periodic trade by news + EventSetTimer(1); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Custom tester event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ + Print("Trade profits by calendar events:"); + HistorySelect(0, LONG_MAX); + DealFilter filter; + int props[] = {DEAL_PROFIT, DEAL_SWAP, DEAL_COMMISSION, DEAL_MAGIC, DEAL_TIME}; + filter.let(DEAL_TYPE, (1 << DEAL_TYPE_BUY) | (1 << DEAL_TYPE_SELL), IS::OR_BITWISE) + .let(DEAL_ENTRY, (1 << DEAL_ENTRY_OUT) | (1 << DEAL_ENTRY_INOUT) | (1 << DEAL_ENTRY_OUT_BY), IS::OR_BITWISE); + Tuple5 trades[]; + + MapArray profits; + MapArray losses; + MapArray counts; + if(filter.select(props, trades)) + { + for(int i = 0; i < ArraySize(trades); ++i) + { + counts.inc((ulong)trades[i]._4); + const double payout = trades[i]._1 + trades[i]._2 + trades[i]._3; + if(payout >= 0) + { + profits.inc((ulong)trades[i]._4, payout); + losses.inc((ulong)trades[i]._4, 0); + } + else + { + profits.inc((ulong)trades[i]._4, 0); + losses.inc((ulong)trades[i]._4, payout); + } + } + + for(int i = 0; i < profits.getSize(); ++i) + { + MqlCalendarEvent event; + MqlCalendarCountry country; + const ulong keyId = profits.getKey(i); + if(cache[].calendarEventById(keyId, event) + && cache[].calendarCountryById(event.country_id, country)) + { + PrintFormat("%lld %s %s %+.2f [%d] (PF:%.2f) %s", + event.id, country.code, country.currency, + profits[keyId] + losses[keyId], counts[keyId], + profits[keyId] / (losses[keyId] != 0 ? -losses[keyId] : DBL_MIN), + event.name); + } + else + { + Print("undefined ", DoubleToString(profits.getValue(i), 2)); + } + } + } + return 0; +} + +//+------------------------------------------------------------------+ +//| Timer event handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + CalendarFilter *f = fptr[]; + MqlCalendarValue records[]; + + f.let(TimeTradeServer() - SCOPE_DAY, TimeTradeServer() + SCOPE_DAY); + + if(f.update(records)) // find changes that match filters + { + // print changes to log + static const ENUM_CALENDAR_PROPERTY props[] = + { + CALENDAR_PROPERTY_RECORD_TIME, + CALENDAR_PROPERTY_COUNTRY_CURRENCY, + CALENDAR_PROPERTY_COUNTRY_CODE, + CALENDAR_PROPERTY_EVENT_NAME, + CALENDAR_PROPERTY_EVENT_IMPORTANCE, + CALENDAR_PROPERTY_RECORD_ACTUAL, + CALENDAR_PROPERTY_RECORD_FORECAST, + CALENDAR_PROPERTY_RECORD_PREVISED, + CALENDAR_PROPERTY_RECORD_IMPACT, + }; + static const int p = ArraySize(props); + string result[]; + f.format(records, props, result); + for(int i = 0; i < ArraySize(result) / p; ++i) + { + Print(SubArrayCombine(result, " | ", i * p, p)); + } + + // calculate news impact + static const int impacts[3] = {0, +1, -1}; + int impact = 0; + string about = ""; + ulong lasteventid = 0; + for(int i = 0; i < ArraySize(records); ++i) + { + const int sign = result[i * p + 1] == Profit ? -1 : +1; + impact += sign * impacts[records[i].impact_type]; + about += StringFormat("%+lld ", sign * (long)records[i].event_id); + lasteventid = records[i].event_id; + } + + if(impact == 0) return; // no signal + + // close existing positions if needed + PositionFilter positions; + ulong tickets[]; + positions.let(POSITION_SYMBOL, _Symbol).select(tickets); + const int n = ArraySize(tickets); + + if(n >= (int)(Hedging ? MultiplePositions : 1)) + { + MqlTradeRequestSync position; + position.close(_Symbol) && position.completed(); + } + + // open new position according to the signal direction + MqlTradeRequestSync request; + request.magic = lasteventid; + request.comment = about; + const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + ulong ticket = 0; + + if(impact > 0) + { + ticket = request.buy(Lot, 0, + Distance2SLTP ? ask - point * Distance2SLTP : 0, + Distance2SLTP ? ask + point * Distance2SLTP : 0); + } + else if(impact < 0) + { + ticket = request.sell(Lot, 0, + Distance2SLTP ? bid + point * Distance2SLTP : 0, + Distance2SLTP ? bid - point * Distance2SLTP : 0); + } + + if(ticket && request.completed() && Distance2SLTP) + { + for(int i = 0; i < ArraySize(trailing); ++i) + { + if(trailing[i][] == NULL) // find free slot, create trailing object + { + trailing[i] = new TrailingStop(ticket, Distance2SLTP, Distance2SLTP / 50); + break; + } + } + } + } +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + for(int i = 0; i < ArraySize(trailing); ++i) + { + if(trailing[i][]) + { + if(!trailing[i][].trail()) // position was closed + { + trailing[i] = NULL; // free the slot, delete object + } + } + } +} + +//+------------------------------------------------------------------+ diff --git a/Connect_Disconnect_Sound_Alert.mq5 b/Connect_Disconnect_Sound_Alert.mq5 new file mode 100644 index 0000000..2d9e40e --- /dev/null +++ b/Connect_Disconnect_Sound_Alert.mq5 @@ -0,0 +1,68 @@ +//+------------------------------------------------------------------+ +//| Connect_Disconnect_Sound_Alert.mq5 | +//| Copyright 2024, Rajesh Kumar Nait | +//| https://www.mql5.com/en/users/rajeshnait/seller | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2024, Rajesh Kumar Nait" +#property link "https://www.mql5.com/en/users/rajeshnait/seller" +#property version "1.00" +#include + +bool first = true; +bool Now_IsConnected = false; +bool Pre_IsConnected = true; +datetime Connect_Start = 0, Connect_Stop = 0; + +CTerminalInfo terminalInfo; +//--- Sound files +//#resource "\\Files\\Sounds\\CONNECTED.wav" +//#resource "\\Files\\Sounds\\DISCONNECTED.wav" +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { +//--- + ResetLastError(); + while ( !IsStopped() ) { + Pre_IsConnected = Now_IsConnected; + Now_IsConnected = terminalInfo.IsConnected(); + + if ( first ) { + Pre_IsConnected = !Now_IsConnected; + } + + if ( Now_IsConnected != Pre_IsConnected ) { + if ( Now_IsConnected ) { + Connect_Start = TimeLocal(); + if ( !first ) { + // if(!PlaySound("::Files\\Sounds\\DISCONNECTED.wav")) + // Print("Error: ",GetLastError()); + } + if ( IsStopped() ) { + break; + } + // if(!PlaySound("::Files\\Sounds\\CONNECTED.wav")) + // Print("Error: ",GetLastError()); + } else { + Connect_Stop = TimeLocal(); + if ( !first ) { + // if(!PlaySound("::Files\\Sounds\\CONNECTED.wav")) + // Print("Error: ",GetLastError()); + } + if ( IsStopped() ) { + break; + } + // if(!PlaySound("::Files\\Sounds\\DISCONNECTED.wav")) + // Print("Error: ",GetLastError()); + } + } + + first = false; + Sleep(1000); + } +//--- + return(INIT_SUCCEEDED); + } + +//+------------------------------------------------------------------+ diff --git a/CrazyCarryTrade.mq5 b/CrazyCarryTrade.mq5 new file mode 100644 index 0000000..3a74348 --- /dev/null +++ b/CrazyCarryTrade.mq5 @@ -0,0 +1,126 @@ +//+------------------------------------------------------------------+ +//| CrazyCarryTrade.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Keep open positions in predefined direction, collect swaps and withdraw them." + +#include +#include +#include + +enum ENUM_ORDER_TYPE_MARKET +{ + MARKET_BUY = ORDER_TYPE_BUY, + MARKET_SELL = ORDER_TYPE_SELL +}; + +input ENUM_ORDER_TYPE_MARKET Type; +input double Volume; +input double MinProfitPerLot = 1000; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(!MQLInfoInteger(MQL_TESTER)) + { + Alert("This is a test EA! Run it in the tester only!"); + return INIT_FAILED; + } + + const double rate = SymbolInfoDouble(_Symbol, + Type == MARKET_BUY ? SYMBOL_SWAP_LONG : SYMBOL_SWAP_SHORT); + if(rate < 0) + { + Alert("Unprofitable symbol and direction specified!"); + return INIT_FAILED; + } + + PRTF(TesterWithdrawal(AccountInfoDouble(ACCOUNT_BALANCE) * 2)); + /* + example: + not enough money for 20 000.00 withdrawal (free margin: 10 000.00) + TesterWithdrawal(AccountInfoDouble(ACCOUNT_BALANCE)*2)=false / MQL_ERROR::10019(10019) + */ + PRTF(TesterWithdrawal(100)); + /* + example: + deal #2 balance -100.00 [withdrawal] done + TesterWithdrawal(100)=true / ok + */ + PRTF(TesterDeposit(100)); // restore + /* + example: + deal #3 balance 100.00 [deposit] done + TesterDeposit(100)=true / ok + */ + + return INIT_SUCCEEDED; +} +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + const double volume = Volume == 0 ? SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) : Volume; + ENUM_POSITION_PROPERTY_DOUBLE props[] = {POSITION_PROFIT, POSITION_SWAP}; + double values[][2]; + ulong tickets[]; + PositionFilter pf; + pf.select(props, tickets, values, true); + if(ArraySize(tickets) > 0) + { + double loss = 0, swaps = 0; + for(int i = 0; i < ArraySize(tickets); ++i) + { + if(values[i][0] + values[i][1] * values[i][1] >= MinProfitPerLot * volume) + { + MqlTradeRequestSync request0; + if(request0.close(tickets[i]) && request0.completed()) + { + swaps += values[i][1]; // sum up swaps of positions being closed + } + } + else + { + loss += values[i][0]; + } + } + + if(loss / ArraySize(tickets) <= -MinProfitPerLot * volume * sqrt(ArraySize(tickets))) + { + MqlTradeRequestSync request1; + (Type == MARKET_BUY ? request1.buy(volume) : request1.sell(volume)); + } + + if(swaps >= 0) + { + TesterWithdrawal(swaps); // withdraw collected swaps + } + } + else + { + MqlTradeRequestSync request1; + (Type == MARKET_BUY ? request1.buy(volume) : request1.sell(volume)); + } +} + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + PrintFormat("Deposit: %.2f Withdrawals: %.2f", + TesterStatistics(STAT_INITIAL_DEPOSIT), + TesterStatistics(STAT_WITHDRAWAL)); + /* + example: + final balance 10091.19 USD + Deposit: 10000.00 Withdrawals: 197.42 + */ +} +//+------------------------------------------------------------------+ diff --git a/CryptDecode.mq5 b/CryptDecode.mq5 new file mode 100644 index 0000000..0f3ba96 --- /dev/null +++ b/CryptDecode.mq5 @@ -0,0 +1,208 @@ +//+------------------------------------------------------------------+ +//| CryptDecode.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Use CryptDecode to restore protected data from given cipher, using different methods." +#property script_show_inputs + +#include +#include +#include + +#define KEY_REQUIRED(C) ((C) == CRYPT_DES || (C) == CRYPT_AES128 || (C) == CRYPT_AES256) +#define IS_HASH(C) ((C) == CRYPT_HASH_MD5 || (C) == CRYPT_HASH_SHA1 || (C) == CRYPT_HASH_SHA256) + +enum ENUM_CRYPT_METHOD_EXT +{ + _CRYPT_DES = CRYPT_DES, // DES + _CRYPT_AES128 = CRYPT_AES128, // AES128 + _CRYPT_AES256 = CRYPT_AES256, // AES256 + _CRYPT_HASH_MD5 = CRYPT_HASH_MD5, // MD5 (irreversible) + _CRYPT_HASH_SHA1 = CRYPT_HASH_SHA1, // SHA1 (irreversible) + _CRYPT_HASH_SHA256 = CRYPT_HASH_SHA256, // SHA256 (irreversible) + _CRYPT_ARCH_ZIP = CRYPT_ARCH_ZIP, // ZIP + _CRYPT_BASE64 = CRYPT_BASE64, // BASE64 +}; + +enum DUMMY_KEY_LENGTH +{ + DUMMY_KEY_0 = 0, // 0 bytes (no key) + DUMMY_KEY_7 = 7, // 7 bytes (sufficient for DES) + DUMMY_KEY_16 = 16, // 16 bytes (sufficient for AES128) + DUMMY_KEY_32 = 32, // 32 bytes (sufficient for AES256) + DUMMY_KEY_CUSTOM, // use CustomKey +}; + +input string Text; // Text (base64, or empty to process File) +input string File = "MQL5Book/clock10.htm.BASE64"; +input ENUM_CRYPT_METHOD_EXT Method = _CRYPT_BASE64; +input DUMMY_KEY_LENGTH GenerateKey = DUMMY_KEY_CUSTOM; // GenerateKey (length, or take from CustomKey) +input string CustomKey = "My top secret key is very strong"; // CustomKey +input bool DisableCRCinZIP = false; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + ENUM_CRYPT_METHOD method = 0; + int methods[]; + uchar key[] = {}; // key is empty by default: ok for zip, base64 + uchar data[], result[]; + uchar zip[], opt[] = {1, 0, 0, 0}; + + if(!StringLen(Text) && !StringLen(File)) + { + Alert("Please specify either Text or File to decode"); + return; + } + + if(GenerateKey == DUMMY_KEY_CUSTOM) + { + if(StringLen(CustomKey)) + { + PRTF(CustomKey); + StringToCharArray(CustomKey, key, 0, -1, CP_UTF8); + ArrayResize(key, ArraySize(key) - 1); + } + } + else if(GenerateKey != DUMMY_KEY_0) + { + // normally the key should be a secret, hard to guess, random selected token, + // here we generate it in straightforward manner for your easy testing + ArrayResize(key, GenerateKey); + for(int i = 0; i < GenerateKey; ++i) key[i] = (uchar)i; + } + + if(ArraySize(key)) + { + Print("Key (bytes):"); + ByteArrayPrint(key); + } + else + { + Print("Key is not provided"); + } + + method = (ENUM_CRYPT_METHOD)Method; + Print("- ", EnumToString(method), ", key required: ", KEY_REQUIRED(method)); + + if(StringLen(Text)) + { + if(method != CRYPT_BASE64) + { + // Since all methods except for Base64 produce binary results, + // it was additionally converted to Base64 in CryptEncode.mq5, + // hence we need to restore binary data from text input + // before deciphering it + uchar base64[]; + const uchar dummy[] = {}; + PRTF(Text); + PRTF(StringToCharArray(Text, base64, 0, -1, CP_UTF8)); + ArrayResize(base64, ArraySize(base64) - 1); + Print("Text (bytes):"); + ByteArrayPrint(base64); + if(!PRTF(CryptDecode(CRYPT_BASE64, base64, dummy, data))) + { + return; // error + } + + Print("Raw data to decipher (after de-base64):"); + ByteArrayPrint(data); + } + else + { + PRTF(StringToCharArray(Text, data, 0, -1, CP_UTF8)); + ArrayResize(data, ArraySize(data) - 1); + } + } + else if(StringLen(File)) + { + PRTF(File); + if(PRTF(FileLoad(File, data)) <= 0) + { + return; // error + } + } + + if(IS_HASH(method)) + { + Print("WARNING: hashes can not be used to restore data! CryptDecode will fail."); + } + + if(method == CRYPT_ARCH_ZIP) + { + // a special key is supported for CRYPT_ARCH_ZIP: + // at least 4 bytes with 1-st '1' is used to disable specific to MQL5 + // Adler32 checksum embedding into the compressed data, + // needed for compatibility with some standards such as ZIP archives + if(DisableCRCinZIP) + { + ArrayCopy(zip, opt); // make dynamic copy of array (dynamic needed for ArraySwap) + } + ArraySwap(key, zip); // substitute the key with empty or optional + } + + ResetLastError(); + if(PRTF(CryptDecode(method, data, key, result))) + { + if(StringLen(Text)) + { + Print("Text restored:"); + Print(CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8)); + } + else // File + { + const string filename = File + ".dec"; + if(PRTF(FileSave(filename, result))) + { + Print("File saved: ", filename); + } + } + } +} + +//+------------------------------------------------------------------+ +/* + +- CRYPT_BASE64, key required: false +File=MQL5Book/clock10.htm.BASE64 / ok +FileLoad(File,data)=1320 / ok +CryptDecode(method,data,key,result)=988 / ok +FileSave(filename,result)=true / ok +File saved: MQL5Book/clock10.htm.BASE64.dec + +... + +CustomKey=My top secret key is very strong / ok +Key (bytes): +[00] 4D | 79 | 20 | 74 | 6F | 70 | 20 | 73 | 65 | 63 | 72 | 65 | 74 | 20 | 6B | 65 | +[16] 79 | 20 | 69 | 73 | 20 | 76 | 65 | 72 | 79 | 20 | 73 | 74 | 72 | 6F | 6E | 67 | +- CRYPT_AES128, key required: true +Text=AQuvVCoSy1szaN8Owy8tQxl9rIrRj9hOqK7KgYYGh9E= / ok +StringToCharArray(Text,base64,0,-1,CP_UTF8)=44 / ok +Text (bytes): +[00] 41 | 51 | 75 | 76 | 56 | 43 | 6F | 53 | 79 | 31 | 73 | 7A | 61 | 4E | 38 | 4F | +[16] 77 | 79 | 38 | 74 | 51 | 78 | 6C | 39 | 72 | 49 | 72 | 52 | 6A | 39 | 68 | 4F | +[32] 71 | 4B | 37 | 4B | 67 | 59 | 59 | 47 | 68 | 39 | 45 | 3D | +CryptDecode(CRYPT_BASE64,base64,dummy,data)=32 / ok +Raw data to decipher (after de-base64): +[00] 01 | 0B | AF | 54 | 2A | 12 | CB | 5B | 33 | 68 | DF | 0E | C3 | 2F | 2D | 43 | +[16] 19 | 7D | AC | 8A | D1 | 8F | D8 | 4E | A8 | AE | CA | 81 | 86 | 06 | 87 | D1 | +CryptDecode(method,data,key,result)=32 / ok +Text restored: +Let's encrypt this message + +... + +- CRYPT_HASH_MD5, key required: false +File=MQL5Book/clock10.htm.MD5 / ok +FileLoad(File,data)=16 / ok +WARNING: hashes can not be used to restore data! CryptDecode will fail. +CryptDecode(method,data,key,result)=0 / INVALID_PARAMETER(4003) + +*/ +//+------------------------------------------------------------------+ diff --git a/CryptEncode.mq5 b/CryptEncode.mq5 new file mode 100644 index 0000000..54c1e7f --- /dev/null +++ b/CryptEncode.mq5 @@ -0,0 +1,255 @@ +//+------------------------------------------------------------------+ +//| CryptEncode.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Use CryptEncode to protect/convert given data with different methods." +#property script_show_inputs + +#include +#include +#include + +#define KEY_REQUIRED(C) ((C) == CRYPT_DES || (C) == CRYPT_AES128 || (C) == CRYPT_AES256) +#define IS_HASH(C) ((C) == CRYPT_HASH_MD5 || (C) == CRYPT_HASH_SHA1 || (C) == CRYPT_HASH_SHA256) + +enum ENUM_CRYPT_METHOD_EXT +{ + _CRYPT_ALL = 0xFF, // Try All in a Loop + _CRYPT_DES = CRYPT_DES, // DES (key required, 7 bytes) + _CRYPT_AES128 = CRYPT_AES128, // AES128 (key required, 16 bytes) + _CRYPT_AES256 = CRYPT_AES256, // AES256 (key required, 32 bytes) + _CRYPT_HASH_MD5 = CRYPT_HASH_MD5, // MD5 + _CRYPT_HASH_SHA1 = CRYPT_HASH_SHA1, // SHA1 + _CRYPT_HASH_SHA256 = CRYPT_HASH_SHA256, // SHA256 + _CRYPT_ARCH_ZIP = CRYPT_ARCH_ZIP, // ZIP + _CRYPT_BASE64 = CRYPT_BASE64, // BASE64 +}; + +enum DUMMY_KEY_LENGTH +{ + DUMMY_KEY_0 = 0, // 0 bytes (no key) + DUMMY_KEY_7 = 7, // 7 bytes (sufficient for DES) + DUMMY_KEY_16 = 16, // 16 bytes (sufficient for AES128) + DUMMY_KEY_32 = 32, // 32 bytes (sufficient for AES256) + DUMMY_KEY_CUSTOM, // use CustomKey +}; + +input string Text = "Let's encrypt this message"; // Text (empty to process File) +input string File = "MQL5Book/clock10.htm"; // File (used only if Text is empty) +input ENUM_CRYPT_METHOD_EXT Method = _CRYPT_ALL; +input DUMMY_KEY_LENGTH GenerateKey = DUMMY_KEY_CUSTOM; // GenerateKey (length, or take from CustomKey) +input string CustomKey = "My top secret key is very strong"; // CustomKey (can be a non-printable binary, but not supported in the demo) +input bool DisableCRCinZIP = false; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + ENUM_CRYPT_METHOD method = 0; + int methods[]; + uchar key[] = {}; // key is empty by default: ok for hashing, zip, base64 + uchar zip[], opt[] = {1, 0, 0, 0}; + uchar data[], result[]; + + if(!StringLen(Text) && !StringLen(File)) + { + Alert("Please specify either Text or File to encode"); + return; + } + + if(GenerateKey == DUMMY_KEY_CUSTOM) + { + if(StringLen(CustomKey)) + { + PRTF(CustomKey); + StringToCharArray(CustomKey, key, 0, -1, CP_UTF8); + ArrayResize(key, ArraySize(key) - 1); + } + } + else if(GenerateKey != DUMMY_KEY_0) + { + // normally the key should be a secret, hard to guess, random selected token, + // here we generate it in straightforward manner for your easy testing + ArrayResize(key, GenerateKey); + for(int i = 0; i < GenerateKey; ++i) key[i] = (uchar)i; + } + + if(ArraySize(key)) + { + Print("Key (bytes):"); + ByteArrayPrint(key); + } + else + { + Print("Key is not provided"); + } + + if(StringLen(Text)) + { + PRTF(Text); + PRTF(StringToCharArray(Text, data, 0, -1, CP_UTF8)); + ArrayResize(data, ArraySize(data) - 1); + } + else if(StringLen(File)) + { + PRTF(File); + if(PRTF(FileLoad(File, data)) <= 0) + { + return; // error + } + } + + // loop through all supported methods or run single time for specific 'Method' + const int n = (Method == _CRYPT_ALL) ? EnumToArray(method, methods, 0, UCHAR_MAX) : 1; + ResetLastError(); + for(int i = 0; i < n; ++i) + { + method = (ENUM_CRYPT_METHOD)((Method == _CRYPT_ALL) ? methods[i] : Method); + Print("- ", i, " ", EnumToString(method), ", key required: ", KEY_REQUIRED(method)); + + if(method == CRYPT_ARCH_ZIP) + { + // a special key is supported for CRYPT_ARCH_ZIP: + // at least 4 bytes with 1-st nonzero is used to disable specific to MQL5 + // Adler32 checksum embedding into the compressed data, + // needed for compatibility with standard ZIP archives, + // storing CRCs in their own headers, not in the data + if(DisableCRCinZIP) + { + ArrayCopy(zip, opt); // make dynamic copy of array (dynamic needed for ArraySwap) + } + ArraySwap(key, zip); // substitute the key with empty or optional + } + + if(PRTF(CryptEncode(method, data, key, result))) + { + if(StringLen(Text)) + { + // use Latin (Western) codepage just for consistency between users + // with different locales on their PCs, anyway this is a binary data, + // which should not be normally printed in the log, we do it just for the demo + Print(CharArrayToString(result, 0, WHOLE_ARRAY, 1252)); + ByteArrayPrint(result); + if(method != CRYPT_BASE64) + { + // All methods except for Base64 produce binary results, + // so if one of these methods is exclusively selected, + // make result printable by additional convertion into Base64 + const uchar dummy[] = {}; + uchar readable[]; + if(PRTF(CryptEncode(CRYPT_BASE64, result, dummy, readable))) + { + PrintFormat("Try to decode this with CryptDecode.mq5 (%s):", EnumToString(method)); + // we can pass only plain text into string input's, + // so Base64 is the only option here to accept encoded data back + Print("base64:'" + CharArrayToString(readable, 0, WHOLE_ARRAY, 1252) + "'"); + } + } + } + else + { + string parts[]; + const string filename = File + "." + parts[StringSplit(EnumToString(method), '_', parts) - 1]; + if(PRTF(FileSave(filename, result))) + { + Print("File saved: ", filename); + if(IS_HASH(method)) + { + ByteArrayPrint(result, 1000, ""); + } + } + } + } + } +} + +//+------------------------------------------------------------------+ +/* + +CustomKey=My top secret key is very strong / ok +Key (bytes): +[00] 4D | 79 | 20 | 74 | 6F | 70 | 20 | 73 | 65 | 63 | 72 | 65 | 74 | 20 | 6B | 65 | +[16] 79 | 20 | 69 | 73 | 20 | 76 | 65 | 72 | 79 | 20 | 73 | 74 | 72 | 6F | 6E | 67 | +Text=Let's encrypt this message / ok +StringToCharArray(Text,data,0,-1,CP_UTF8)=26 / ok +- 0 CRYPT_BASE64, key required: false +CryptEncode(method,data,key,result)=36 / ok +TGV0J3MgZW5jcnlwdCB0aGlzIG1lc3NhZ2U= +[00] 54 | 47 | 56 | 30 | 4A | 33 | 4D | 67 | 5A | 57 | 35 | 6A | 63 | 6E | 6C | 77 | +[16] 64 | 43 | 42 | 30 | 61 | 47 | 6C | 7A | 49 | 47 | 31 | 6C | 63 | 33 | 4E | 68 | +[32] 5A | 32 | 55 | 3D | +- 1 CRYPT_AES128, key required: true +CryptEncode(method,data,key,result)=32 / ok +ВЇT* Г‹[3hГџ Гѓ/-C }¬ŠÑГN¨®Ê† ‡Ñ +[00] 01 | 0B | AF | 54 | 2A | 12 | CB | 5B | 33 | 68 | DF | 0E | C3 | 2F | 2D | 43 | +[16] 19 | 7D | AC | 8A | D1 | 8F | D8 | 4E | A8 | AE | CA | 81 | 86 | 06 | 87 | D1 | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_AES128): +base64:'AQuvVCoSy1szaN8Owy8tQxl9rIrRj9hOqK7KgYYGh9E=' +- 2 CRYPT_AES256, key required: true +CryptEncode(method,data,key,result)=32 / ok +ГёвЂULȃsГ«DC‰ô В¬.K)Е’ГЅГЃ LГЎВё +< !DГЇ +[00] F8 | 91 | 55 | 4C | BB | C9 | 73 | EB | 44 | 43 | 89 | F4 | 06 | 13 | AC | 2E | +[16] 4B | 29 | 8C | FD | C1 | 11 | 4C | E1 | B8 | 05 | 2B | 3C | 14 | 21 | 44 | EF | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_AES256): +base64:'+JFVTLvJc+tEQ4n0BhOsLkspjP3BEUzhuAUrPBQhRO8=' +- 3 CRYPT_DES, key required: true +CryptEncode(method,data,key,result)=32 / ok +Вµ Вќb &“#ÇÅ+ГЅВє'ВҐ B8fВЎrГ-PГЁ<6âì‚Ë£ +[00] B5 | 06 | 9D | 62 | 11 | 26 | 93 | 23 | C7 | C5 | 2B | FD | BA | 27 | A5 | 10 | +[16] 42 | 38 | 66 | A1 | 72 | D8 | 2D | 50 | E8 | 3C | 36 | E2 | EC | 82 | CB | A3 | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_DES): +base64:'tQadYhEmkyPHxSv9uielEEI4ZqFy2C1Q6Dw24uyCy6M=' +- 4 CRYPT_HASH_SHA1, key required: false +CryptEncode(method,data,key,result)=20 / ok +§ßö*©ºø +€|)bГ‹bzÇÍ Û€ +[00] A7 | DF | F6 | 2A | A9 | BA | F8 | 0A | 80 | 7C | 29 | 62 | CB | 62 | 7A | C7 | +[16] CD | 0E | DB | 80 | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=28 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_HASH_SHA1): +base64:'p9/2Kqm6+AqAfCliy2J6x80O24A=' +- 5 CRYPT_HASH_SHA256, key required: false +CryptEncode(method,data,key,result)=32 / ok +ГљZ2š€»”¾7 €… ñ–ÄÁ´˜¦“ome2r@¾ô®³” +[00] DA | 5A | 32 | 9A | 80 | BB | 94 | BE | 37 | 0C | 80 | 85 | 07 | F1 | 96 | C4 | +[16] C1 | B4 | 98 | A6 | 93 | 6F | 6D | 65 | 32 | 72 | 40 | BE | F4 | AE | B3 | 94 | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=44 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_HASH_SHA256): +base64:'2loymoC7lL43DICFB/GWxMG0mKaTb21lMnJAvvSus5Q=' +- 6 CRYPT_HASH_MD5, key required: false +CryptEncode(method,data,key,result)=16 / ok +zIGT…  FГ»;—3ГѕГЁГҐ +[00] 7A | 49 | 47 | 54 | 85 | 1B | 7F | 11 | 46 | FB | 3B | 97 | 33 | FE | E8 | E5 | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=24 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_HASH_MD5): +base64:'eklHVIUbfxFG+zuXM/7o5Q==' +- 7 CRYPT_ARCH_ZIP, key required: false +CryptEncode(method,data,key,result)=34 / ok +x^ГіI-Q/VHГЌK.ВЄ,(Q(ÉÈ,VГ€M-.NLO +[00] 78 | 5E | F3 | 49 | 2D | 51 | 2F | 56 | 48 | CD | 4B | 2E | AA | 2C | 28 | 51 | +[16] 28 | C9 | C8 | 2C | 56 | C8 | 4D | 2D | 2E | 4E | 4C | 4F | 05 | 00 | 80 | 07 | +[32] 09 | C2 | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=48 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_ARCH_ZIP): +base64:'eF7zSS1RL1ZIzUsuqiwoUSjJyCxWyE0tLk5MTwUAgAcJwg==' + +... + +- 7 CRYPT_ARCH_ZIP, key required: false +CryptEncode(method,data,key,result)=28 / ok +ГіI-Q/VHГЌK.ВЄ,(Q(ÉÈ,VГ€M-.NLO +[00] F3 | 49 | 2D | 51 | 2F | 56 | 48 | CD | 4B | 2E | AA | 2C | 28 | 51 | 28 | C9 | +[16] C8 | 2C | 56 | C8 | 4D | 2D | 2E | 4E | 4C | 4F | 05 | 00 | +CryptEncode(CRYPT_BASE64,result,dummy,readable)=40 / ok +Try to decode this with CryptDecode.mq5 (CRYPT_ARCH_ZIP): +base64:'80ktUS9WSM1LLqosKFEoycgsVshNLS5OTE8FAA==' + +*/ +//+------------------------------------------------------------------+ diff --git a/CryptPNG.mq5 b/CryptPNG.mq5 new file mode 100644 index 0000000..c34ff09 --- /dev/null +++ b/CryptPNG.mq5 @@ -0,0 +1,45 @@ +//+------------------------------------------------------------------+ +//| CryptPNG.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#resource "\\Images\\euro.bmp" + +#include +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + uchar null[]; // empty key + uchar result[]; // receiving 'packed' array + uint data[]; // source pixels + uchar bytes[]; // source bytes + int width, height; + PRTF(ResourceReadImage("::Images\\euro.bmp", data, width, height)); + + ArrayResize(bytes, ArraySize(data) * 3 + width); + ArrayInitialize(bytes, 0); + int j = 0; + for(int i = 0; i < ArraySize(data); ++i) + { + if(i % width == 0) bytes[j++] = 0; // prepend 0 filter-type byte to each scanline + const uint c = data[i]; + // bytes[j++] = (uchar)((c >> 24) & 0xFF); // alpha, for PNG_CTYPE_TRUECOLORALPHA (ARGB) + bytes[j++] = (uchar)((c >> 16) & 0xFF); + bytes[j++] = (uchar)((c >> 8) & 0xFF); + bytes[j++] = (uchar)(c & 0xFF); + } + + PRTF(CryptEncode(CRYPT_ARCH_ZIP, bytes, null, result)); + + int h = PRTF(FileOpen("my.png", FILE_BIN | FILE_WRITE)); + + PNG::Image image(width, height, result); // by default PNG_CTYPE_TRUECOLOR (RGB) + image.write(h); + + FileClose(h); +} +//+------------------------------------------------------------------+ diff --git a/CustomOrderSend (1).mq5 b/CustomOrderSend (1).mq5 new file mode 100644 index 0000000..c331869 --- /dev/null +++ b/CustomOrderSend (1).mq5 @@ -0,0 +1,131 @@ +//+------------------------------------------------------------------+ +//| CustomOrderSend.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Construct MqlTradeRequest from user input and send it to server via OrderSend or OrderSendAsync." + +#include +#include +#include + +input bool Async = false; +input ENUM_TRADE_REQUEST_ACTIONS Action = TRADE_ACTION_DEAL; +input ulong Magic; +input ulong Order; +input string Symbol; // Symbol (empty = current _Symbol) +input double Volume; // Volume (0 = minimal lot) +input double Price; // Price (0 = current Ask) +input double StopLimit; +input double SL; +input double TP; +input ulong Deviation; +input ENUM_ORDER_TYPE Type; +input ENUM_ORDER_TYPE_FILLING Filling; +input ENUM_ORDER_TYPE_TIME ExpirationType; +input datetime ExpirationTime; +input string Comment; +input ulong Position; +input ulong PositionBy; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(AccountInfoInteger(ACCOUNT_TRADE_MODE) != ACCOUNT_TRADE_MODE_DEMO) + { + Alert("This is a test EA! Run it on a DEMO account only!"); + return INIT_FAILED; + } + + // setup timer for postponed execution + EventSetTimer(1); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Timer event handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + // once executed do nothing, wait for another setup from user + EventKillTimer(); + + // initialize structs with zeros + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + // default values + const bool kindOfBuy = (Type & 1) == 0 && Type < ORDER_TYPE_CLOSE_BY; + const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol; + const double volume = Volume == 0 ? SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN) : Volume; + const double price = Price == 0 ? SymbolInfoDouble(symbol, kindOfBuy ? SYMBOL_ASK : SYMBOL_BID) : Price; + + TU::SymbolMetrics sm(symbol); + + // fill the struct + request.action = Action; + request.magic = Magic; + request.order = Order; + request.symbol = symbol; + request.volume = sm.volume(volume); + request.price = sm.price(price); + request.stoplimit = sm.price(StopLimit); + request.sl = sm.price(SL); + request.tp = sm.price(TP); + request.deviation = Deviation; + request.type = Type; + request.type_filling = Filling; + request.type_time = ExpirationType; + request.expiration = ExpirationTime; + request.comment = Comment; + request.position = Position; + request.position_by = PositionBy; + + // send request and print out results + ResetLastError(); + if(Async) + { + PRTF(OrderSendAsync(request, result)); + } + else + { + PRTF(OrderSend(request, result)); + } + Print(TU::StringOf(request)); + Print(TU::StringOf(result)); +} + +/* // Facultative study: uncomment to see trade events in the log +//+------------------------------------------------------------------+ +//| Trade transactions handler | +//+------------------------------------------------------------------+ +void OnTradeTransaction(const MqlTradeTransaction &transaction, + const MqlTradeRequest &request, + const MqlTradeResult &result) +{ + static ulong count = 0; + Print(count++); + Print(TU::StringOf(transaction)); + Print(TU::StringOf(request)); + Print(TU::StringOf(result)); +} +*/ +//+------------------------------------------------------------------+ +/* + example output (default settings - Async=false): + + OrderSend(request,result)=true / ok + TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.12462 + DONE, D=1250236209, #=1267684253, V=0.01, @ 1.12462, Bid=1.12456, Ask=1.12462, Request executed, Req=1 + + example output (altered settings - Async=true): + + OrderSendAsync(request,result)=true / ok + TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.12449 + PLACED, Order placed, Req=2 +*/ +//+------------------------------------------------------------------+ diff --git a/CustomOrderSend.mq5 b/CustomOrderSend.mq5 new file mode 100644 index 0000000..f4ef938 --- /dev/null +++ b/CustomOrderSend.mq5 @@ -0,0 +1,119 @@ +//+------------------------------------------------------------------+ +//| CustomOrderSend.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Simulate trades by market and pending orders in internal account classes." +#property description "Trades are performed manually (ad hoc from keyboard), which mimics a Trade Panel." + +#define SHOW_WARNINGS // output extended info into the log, with changes in data state +#define WARNING Print // use simple Print for warnings (instead of a built-in format with line numbers etc.) +#include +#include + +input double Volume; // Volume (0 = minimal lot) +input int Distance2SLTP = 0; // Distance to SL/TP in points (0 = no) + +const double Lot = Volume == 0 ? SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) : Volume; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(AccountInfoInteger(ACCOUNT_TRADE_MODE) != ACCOUNT_TRADE_MODE_DEMO) + { + Alert("This is a test EA! Run it on a DEMO account only!"); + return INIT_FAILED; + } + + // setup timer for periodic trade state display + EventSetTimer(1); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Keyboard scan codes | +//+------------------------------------------------------------------+ +#define KEY_B 66 +#define KEY_C 67 +#define KEY_D 68 +#define KEY_L 76 +#define KEY_R 82 +#define KEY_S 83 +#define KEY_U 85 + +//+------------------------------------------------------------------+ +//| Chart events handler for keyboard monitoring | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + if(id == CHARTEVENT_KEYDOWN) + { + MqlTradeRequestSync request; + const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + switch((int)lparam) + { + case KEY_B: + request.buy(Lot, 0, + Distance2SLTP ? ask - point * Distance2SLTP : Distance2SLTP, + Distance2SLTP ? ask + point * Distance2SLTP : Distance2SLTP); + break; + case KEY_S: + request.sell(Lot, 0, + Distance2SLTP ? bid + point * Distance2SLTP : Distance2SLTP, + Distance2SLTP ? bid - point * Distance2SLTP : Distance2SLTP); + break; + case KEY_U: + if(Distance2SLTP) + { + request.buyLimit(Lot, ask - point * Distance2SLTP); + } + break; + case KEY_L: + if(Distance2SLTP) + { + request.sellLimit(Lot, bid + point * Distance2SLTP); + } + break; + case KEY_C: + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + request.close(PositionGetTicket(i)); + } + break; + case KEY_D: + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + request.remove(OrderGetTicket(i)); + } + break; + case KEY_R: + CustomTrade::PrintTradeHistory(); + break; + } + } +} + +//+------------------------------------------------------------------+ +//| Timer event handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + Comment(CustomTrade::ReportTradeState()); + CustomTrade::DisplayTrades(); +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + Comment(""); +} +//+------------------------------------------------------------------+ diff --git a/CustomSymbolCreateDelete.mq5 b/CustomSymbolCreateDelete.mq5 new file mode 100644 index 0000000..3356ee7 --- /dev/null +++ b/CustomSymbolCreateDelete.mq5 @@ -0,0 +1,45 @@ +//+------------------------------------------------------------------+ +//| CustomSymbolCreateDelete.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Create or delete specified custom symbol." +#property script_show_inputs + +#include + +input string CustomSymbol = "Dummy"; // Custom Symbol Name +input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder +input string Origin; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + bool custom = false; + if(!PRTF(SymbolExist(CustomSymbol, custom))) + { + if(IDYES == MessageBox("Create new custom symbol?", "Please, confirm", MB_YESNO)) + { + PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, Origin)); + } + } + else + { + if(custom) + { + if(IDYES == MessageBox("Delete existing custom symbol?", "Please, confirm", MB_YESNO)) + { + PRTF(CustomSymbolDelete(CustomSymbol)); + } + } + else + { + Print("Can't delete non-custom symbol"); + } + } +} +//+------------------------------------------------------------------+ diff --git a/CustomSymbolFilterTicks.mq5 b/CustomSymbolFilterTicks.mq5 new file mode 100644 index 0000000..2f68660 --- /dev/null +++ b/CustomSymbolFilterTicks.mq5 @@ -0,0 +1,143 @@ +//+------------------------------------------------------------------+ +//| CustomSymbolFilterTicks.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Create specified custom symbol based on current chart's symbol with prunned ticks for faster tests and optimizations." +#property script_show_inputs + +//#include +#define PRTF +#include +#include +#include + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder +input datetime _Start; // Start (default: 120 days back) +input TickFilter::FILTER_MODE Mode = TickFilter::SEQUENCE; + +//+------------------------------------------------------------------+ +//| Globals | +//+------------------------------------------------------------------+ +string CustomSymbol = _Symbol + ".TckFltr" + "-" + EnumToString(Mode); +const uint DailySeconds = 60 * 60 * 24; +datetime Start = _Start == 0 ? TimeCurrent() - DailySeconds * 120 : _Start; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + bool custom = false; + if(PRTF(SymbolExist(CustomSymbol, custom)) && custom) + { + if(IDYES == MessageBox(StringFormat("Delete existing custom symbol '%s'?", CustomSymbol), + "Please, confirm", MB_YESNO)) + { + PRTF(SymbolSelect(CustomSymbol, false)); + PRTF(CustomRatesDelete(CustomSymbol, 0, LONG_MAX)); + PRTF(CustomTicksDelete(CustomSymbol, 0, LONG_MAX)); + PRTF(CustomSymbolDelete(CustomSymbol)); + } + else + { + return; + } + } + + if(IDYES == MessageBox(StringFormat("Create new custom symbol '%s'?", CustomSymbol), + "Please, confirm", MB_YESNO)) + { + if(PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol))) + { + + //PRTF(CustomSymbolSetDouble(CustomSymbol, SYMBOL_TRADE_TICK_VALUE_PROFIT, 1.0)); + + // BUG: some properties are not set as expected, including very important ones: + // SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE, etc. which remain zeros, + // so we need to edit them "manually" + SymbolMonitor sm; + CustomSymbolMonitor csm(CustomSymbol, &sm); + int props[] = {SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE/*, + SYMBOL_TRADE_TICK_VALUE_PROFIT, SYMBOL_TRADE_TICK_VALUE_LOSS*/}; + const int d1 = csm.verify(props); + if(d1) + { + Print("Number of found descrepancies: ", d1); + if(csm.verify(props)) // check again + { + Alert("Custom symbol can not be created, internal error!"); + return; + } + Print("Fixed"); + } + + CustomSymbolSetString(CustomSymbol, SYMBOL_DESCRIPTION, "Prunned ticks by " + EnumToString(Mode)); + + if(GenerateTickData()) + { + SymbolSelect(CustomSymbol, true); + ChartOpen(CustomSymbol, PERIOD_H1); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Day-by-day tick data processor | +//+------------------------------------------------------------------+ +bool GenerateTickData() +{ + bool result = true; + datetime from = Start / DailySeconds * DailySeconds; // round up to a day boundary + ulong read = 0, written = 0; + uint day = 0; + const uint total = (uint)((TimeCurrent() - from) / DailySeconds + 1); + Timing timing; + MqlTick array[]; + + while(!IsStopped() && from < TimeCurrent()) + { + Comment(TimeToString(from, TIME_DATE), " ", day++, "/", total, + " elapsed: ", timing.elapsed(), ", remain: ", timing.remain(day * 1.0f / total)); + + const int r = PRTF(CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, + from * 1000L, (from + DailySeconds) * 1000L - 1)); + if(r < 0) + { + Alert("Error reading ticks at ", TimeToString(from, TIME_DATE)); + result = false; + break; + } + read += r; + + if(r > 0) + { + const int t = PRTF(TickFilter::filter(Mode, array)); + const int w = PRTF(CustomTicksReplace(CustomSymbol, + from * 1000L, (from + DailySeconds) * 1000L - 1, array)); + if(w <= 0) + { + Alert("Error writing custom ticks at ", TimeToString(from, TIME_DATE)); + result = false; + break; + } + written += w; + } + from += DailySeconds; + } + + if(read > 0) + { + PrintFormat("Done ticks - read: %lld, written: %lld, ratio: %.1f%%", + read, written, written * 100.0 / read); + } + Comment(""); + return result; +} +//+------------------------------------------------------------------+ diff --git a/CustomSymbolProperties.mq5 b/CustomSymbolProperties.mq5 new file mode 100644 index 0000000..84a10e1 --- /dev/null +++ b/CustomSymbolProperties.mq5 @@ -0,0 +1,64 @@ +//+------------------------------------------------------------------+ +//| CustomSymbolProperties.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Create or delete specified custom symbol based on current chart's symbol." +#property script_show_inputs + +#include +#include + +input string CustomSymbol = "Dummy"; // Custom Symbol Name +input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder +input bool AutoUnselectInMarketWatch = true; +input bool ReverseOrder = false; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + bool custom = false; + if(!PRTF(SymbolExist(CustomSymbol, custom))) + { + if(IDYES == MessageBox(StringFormat("Create new custom symbol based on %s?", _Symbol), + "Please, confirm", MB_YESNO)) + { + if(PRTF(CustomSymbolCreate(CustomSymbol, CustomPath))) + { + // this will virtually move the symbol into _Symbol's path inside Custom folder + CustomSymbolMonitor cs(CustomSymbol, _Symbol); + cs.setAll(ReverseOrder); + } + } + } + else + { + if(custom) + { + if(IDYES == MessageBox("Delete existing custom symbol?", "Please, confirm", MB_YESNO)) + { + if(AutoUnselectInMarketWatch) // without this we can't delete implicitly selected symbol + { + CustomSymbolMonitor cs(CustomSymbol); + if(PRTF(cs.get(SYMBOL_SELECT)) && PRTF(!cs.get(SYMBOL_VISIBLE))) + { + Print("Unselecting implicitly selected symbol"); + // SYMBOL_SELECT is read-only + // PRTF(cs.set(SYMBOL_SELECT, false)); + PRTF(SymbolSelect(CustomSymbol, false)); + } + } + PRTF(CustomSymbolDelete(CustomSymbol)); + } + } + else + { + Print("Can't delete non-custom symbol"); + } + } +} +//+------------------------------------------------------------------+ diff --git a/CustomSymbolRandomRates.mq5 b/CustomSymbolRandomRates.mq5 new file mode 100644 index 0000000..7b75de9 --- /dev/null +++ b/CustomSymbolRandomRates.mq5 @@ -0,0 +1,205 @@ +//+------------------------------------------------------------------+ +//| CustomSymbolRandomRates.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Create custom symbol with randomized quotes based on current chart's symbol." +#property script_show_inputs + +#include + +enum RANDOMIZATION +{ + ORIGINAL, + RANDOM_WALK, + FUZZY_WEAK, + FUZZY_STRONG, +}; + +input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder +input RANDOMIZATION RandomFactor = RANDOM_WALK; +input datetime _From; // From (default: 120 days back) +input datetime _To; // To (default: up to now) +input uint RandomSeed = 0; // Random Seed (0 means random) + +datetime From; +datetime To; +const string CustomSymbol = _Symbol + "." + EnumToString(RandomFactor) + + (RandomSeed ? "_" + (string)RandomSeed : ""); + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + From = _From == 0 ? TimeCurrent() - 60 * 60 * 24 * 120 : _From; // 120 days by default + To = _To == 0 ? TimeCurrent() / 60 * 60 : _To; // till current time + if(From > To) + { + Alert("Date range must include From <= To"); + return; + } + + if(RandomSeed != 0) MathSrand(RandomSeed); + + bool custom = false; + if(PRTF(SymbolExist(CustomSymbol, custom)) && custom) + { + if(IDYES == MessageBox(StringFormat("Delete custom symbol '%s'?", CustomSymbol), + "Please, confirm", MB_YESNO)) + { + if(CloseChartsForSymbol(CustomSymbol)) + { + Sleep(500); // wait changes to take effect (unreliable) + // we need to wipe out rates because otherwise the rates + // remain in the base file on the disk for some time + // and can be picked up by the following creation procedure + PRTF(CustomRatesDelete(CustomSymbol, 0, LONG_MAX)); + PRTF(SymbolSelect(CustomSymbol, false)); + PRTF(CustomSymbolDelete(CustomSymbol)); + } + } + } + + if(IDYES == MessageBox(StringFormat("Create new custom symbol '%s'?", CustomSymbol), + "Please, confirm", MB_YESNO)) + { + if(PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol))) + { + if(RandomFactor == RANDOM_WALK) + { + CustomSymbolSetInteger(CustomSymbol, SYMBOL_DIGITS, 8); + } + + CustomSymbolSetString(CustomSymbol, SYMBOL_DESCRIPTION, "Randomized quotes"); + + const int n = GenerateQuotes(); + Print("Bars M1 generated: ", n); + if(n > 0) + { + SymbolSelect(CustomSymbol, true); + ChartOpen(CustomSymbol, PERIOD_M1); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Chart management function | +//+------------------------------------------------------------------+ +bool CloseChartsForSymbol(const string symbol) +{ + long id = ChartFirst(); + long match = -1; + while(id != -1) + { + if(ChartSymbol(id) == symbol) + { + if(id == ChartID()) + { + Alert("Can't close itself: start this script on another chart"); + return false; + } + else + { + match = id; + } + } + id = ChartNext(id); + if(match != -1) + { + ChartClose(match); + match = -1; + } + } + ResetLastError(); // clear CHART_NOT_FOUND (4103) + return true; +} + +//+------------------------------------------------------------------+ +//| Get value mangled with nonuniform random noice | +//+------------------------------------------------------------------+ +double RandomWalk(const double p) +{ + const static double factor[] = {0.0, 0.1, 0.01, 0.05}; + const static double f = factor[RandomFactor] / 100; + const double r = (rand() - 16383.0) / 16384.0; // [-1,+1] + const int sign = r >= 0 ? +1 : -1; + + if(r != 0) + { + return p + p * sign * f * sqrt(-log(sqrt(fabs(r)))); + } + return p; +} + +//+------------------------------------------------------------------+ +//| Request, modify, commit MqlRates for custom symbol | +//+------------------------------------------------------------------+ +int GenerateQuotes() +{ + MqlRates rates[]; + MqlRates zero = {}; + datetime start; + double price; + + if(RandomFactor != RANDOM_WALK) + { + // NB: terminal settings are in effect here, including bar limit + if(PRTF(CopyRates(_Symbol, PERIOD_M1, From, To, rates)) <= 0) + { + return 0; // error + } + if(RandomFactor == ORIGINAL) + { + return PRTF(CustomRatesReplace(CustomSymbol, From, To, rates)); + } + price = rates[0].open; + start = rates[0].time; + } + else + { + ArrayResize(rates, (int)((To - From) / 60) + 1); + price = 1.0; + start = From - 60; + } + + const int size = ArraySize(rates); + + double hlc[3]; + for(int i = 0; i < size; ++i) + { + if(RandomFactor == RANDOM_WALK) + { + rates[i] = zero; + rates[i].time = start += 60; + rates[i].open = price; + hlc[0] = RandomWalk(price); + hlc[1] = RandomWalk(price); + hlc[2] = RandomWalk(price); + } + else + { + double delta = 0; + if(i > 0) + { + delta = rates[i].open - price; // cumulative correction + } + rates[i].open = price; + hlc[0] = RandomWalk(rates[i].high - delta); + hlc[1] = RandomWalk(rates[i].low - delta); + hlc[2] = RandomWalk(rates[i].close - delta); + } + ArraySort(hlc); + + rates[i].high = fmax(hlc[2], rates[i].open); + rates[i].low = fmin(hlc[0], rates[i].open); + rates[i].close = price = hlc[1]; + rates[i].tick_volume = 4; + } + + return PRTF(CustomRatesReplace(CustomSymbol, From, To, rates)); +} +//+------------------------------------------------------------------+ diff --git a/CustomTester.mq5 b/CustomTester.mq5 new file mode 100644 index 0000000..50de537 --- /dev/null +++ b/CustomTester.mq5 @@ -0,0 +1,279 @@ +//+------------------------------------------------------------------+ +//| CustomTester.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Generates custom symbol from real Ticks of current chart's symbol.\n" +#property description "Ticks are coming slowly, producing a kind of visual back test chart, but with full support of interactive events and up to the current day (that's not available in the standard tester).\n" + +#include +#include +#include +#include + +#define EVENT_KEY 0xDED + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder +input datetime _Start; // Start (default: 120 days back) +input ENUM_TIMEFRAMES Timeframe = PERIOD_H1; + +//+------------------------------------------------------------------+ +//| Globals (names in CamelCase) | +//+------------------------------------------------------------------+ +string CustomSymbol = _Symbol + ".Tester"; +const uint DailySeconds = 60 * 60 * 24; +datetime Start = _Start == 0 ? TimeCurrent() - DailySeconds * 120 : _Start; +bool FirstCopy = true; +// step back 1 day because without this new ticks will not update the chart +datetime Cursor = (Start / DailySeconds - 1) * DailySeconds; // round up to a day boundary +MqlTick Ticks[]; // ticks for current day +int Index = 0; // position inside the day +int Step = 32; // advancing by 32 Ticks at once (by default) +int StepRestore = 0; // remember recent speed during pause +long Chart = 0; // newly created chart with custom symbol +bool InitDone = false; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +void OnInit() +{ + EventSetMillisecondTimer(100); +} + +//+------------------------------------------------------------------+ +//| Timer handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + if(!GenerateData()) + { + EventKillTimer(); + } +} + +//+------------------------------------------------------------------+ +//| Helper function to speed up/slow down Ticks replay by keys | +//+------------------------------------------------------------------+ +//| ATTENTION! Keyboard works only when the chart has focus, | +//| so you need to click it by mouse for the keys to take effect. | +//+------------------------------------------------------------------+ +void CheckKeys(const long key) +{ + if(key == VK_DOWN) + { + Step /= 2; + if(Step > 0) + { + Print("Slow down: ", Step); + ChartSetString(Chart, CHART_COMMENT, "Speed: " + (string)Step); + } + else + { + Print("Paused"); + ChartSetString(Chart, CHART_COMMENT, "Paused"); + ChartRedraw(Chart); + } + } + else if(key == VK_UP) + { + if(Step == 0) + { + Step = 1; + Print("Resumed"); + ChartSetString(Chart, CHART_COMMENT, "Resumed"); + } + else + { + Step *= 2; + Print("Spead up: ", Step); + ChartSetString(Chart, CHART_COMMENT, "Speed: " + (string)Step); + } + } + else if(key == VK_PAUSE) + { + if(Step > 0) + { + StepRestore = Step; + Step = 0; + Print("Paused"); + ChartSetString(Chart, CHART_COMMENT, "Paused"); + ChartRedraw(Chart); + } + else + { + Step = StepRestore; + Print("Resumed"); + ChartSetString(Chart, CHART_COMMENT, "Speed: " + (string)Step); + } + } +} + +//+------------------------------------------------------------------+ +//| Main function to produce the custom symbol and emit Ticks | +//+------------------------------------------------------------------+ +bool GenerateData() +{ + if(!InitDone) + { + bool custom = false; + if(PRTF(SymbolExist(CustomSymbol, custom)) && custom) + { + if(IDYES == MessageBox(StringFormat("Clean up existing custom symbol '%s'?", CustomSymbol), + "Please, confirm", MB_YESNO)) + { + PRTF(CustomRatesDelete(CustomSymbol, 0, LONG_MAX)); + PRTF(CustomTicksDelete(CustomSymbol, 0, LONG_MAX)); + Sleep(1000); + MqlRates rates[1]; + MqlTick tcks[]; + if(PRTF(CopyRates(CustomSymbol, PERIOD_M1, 0, 1, rates)) == 1 + || PRTF(CopyTicks(CustomSymbol, tcks) > 0)) + { + Alert("Can't delete rates and Ticks, internal error"); + ExpertRemove(); + } + } + else + { + return false; + } + } + else + if(!PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol))) + { + return false; + } + + // some properties, including very important ones can be not applied + // right away after calling CustomSymbolCreate, so we need to check them + // and try to apply "manually" + SymbolMonitor sm; + CustomSymbolMonitor csm(CustomSymbol, &sm); + int props[] = {SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE}; + const int d1 = csm.verify(props); + if(d1) + { + Print("Number of found descrepancies: ", d1); + if(csm.verify(props)) // check again + { + Alert("Custom symbol can not be created, internal error!"); + return false; + } + Print("Fixed"); + } + + Print(TimeToString(SymbolInfoInteger(CustomSymbol, SYMBOL_TIME))); + SymbolSelect(CustomSymbol, true); + Chart = ChartOpen(CustomSymbol, Timeframe); + const int handle = iCustom(CustomSymbol, Timeframe, "MQL5Book/p7/KeyboardSpy", ChartID(), EVENT_KEY); + ChartIndicatorAdd(Chart, 0, handle); + ChartSetString(Chart, CHART_COMMENT, "Custom Tester"); + ChartSetInteger(Chart, CHART_SHOW_OBJECT_DESCR, true); + ChartRedraw(Chart); + + InitDone = true; + } + else + { + for(int i = 0; i <= (Step - 1) / 256; ++i) + if(Step > 0 && !GenerateTicks()) + { + return false; + } + } + return true; +} + +//+------------------------------------------------------------------+ +//| Helper function to read real symbol Ticks by chunks day by day | +//+------------------------------------------------------------------+ +bool FillTickBuffer() +{ + int r; + ArrayResize(Ticks, 0); + do + { + r = PRTF(CopyTicksRange(_Symbol, Ticks, COPY_TICKS_ALL, Cursor * 1000L, (Cursor + DailySeconds) * 1000L - 1)); + if(r > 0 && FirstCopy) + { + // MQL5 bug workaround: this preliminary call is needed for the chart to leave "Waiting for Update" state + PRTF(CustomTicksReplace(CustomSymbol, Cursor * 1000L, (Cursor + DailySeconds) * 1000L - 1, Ticks)); + FirstCopy = false; + r = 0; + } + Cursor += DailySeconds; + } + while(r == 0 && Cursor < TimeCurrent()); // skip non-trading days + Index = 0; + return r > 0; +} + +//+------------------------------------------------------------------+ +//| Helper function to emit Ticks by small packets to custom symbol | +//+------------------------------------------------------------------+ +bool GenerateTicks() +{ + if(Index >= ArraySize(Ticks)) + { + if(!FillTickBuffer()) return false; + } + + const int m = ArraySize(Ticks); + MqlTick array[]; + const int n = ArrayCopy(array, Ticks, 0, Index, fmin(fmin(Step, 256), m)); + if(n <= 0) return false; + + ResetLastError(); + if(CustomTicksAdd(CustomSymbol, array) != ArraySize(array) || _LastError != 0) + { + Print(_LastError); // in case we get ERR_CUSTOM_TICKS_WRONG_ORDER (5310) + ExpertRemove(); + } + + Comment("Spead: ", (string)Step, " / ", STR_TIME_MSC(array[n - 1].time_msc)); + Index += Step; + + return true; +} + +//+------------------------------------------------------------------+ +//| Chart events handler: | +//| - interactive keyboard presses (when the chart is active) | +//| - remote keyboard events on dependent custom symbol chart | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + if(id == CHARTEVENT_CUSTOM + EVENT_KEY) // this notification comes from dependent chart + { + // NB: MT5 limitation: since our MQL-program generates custom symbol + // and normally does this in background (its chart is not an active chart), + // TerminalInfoInteger(TERMINAL_KEYSTATE_) does not work, + // that is it returns 0 always, hence we can't detect + // Ctrl/Shift and other key states, and use only plain alphanumeric keys + CheckKeys(lparam); + } + else if(id == CHARTEVENT_KEYDOWN) // this only fires when this chart is active + { + // when the chart is active, only at that mements we could + // get meaningful data from TerminalInfoInteger(TERMINAL_KEYSTATE_), + // but since it's unavailable via iCustom indicators, we don't use it here as well + CheckKeys(lparam); + } +} + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + if(Chart != 0) + { + ChartClose(Chart); + } + Comment(""); +} +//+------------------------------------------------------------------+ diff --git a/DBQuotesIntradayBackAndForward.mq5 b/DBQuotesIntradayBackAndForward.mq5 new file mode 100644 index 0000000..ec97c92 --- /dev/null +++ b/DBQuotesIntradayBackAndForward.mq5 @@ -0,0 +1,265 @@ +//+------------------------------------------------------------------+ +//| DBQuotesIntradayBackAndForward.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Display profit estimation on backtest and forward tests performed on quotes groupped by LAGs at intraday time and day of week.\nUse DBquotesImport.mq5 to generate and populate the database beforehand." +#property script_show_inputs + +#include +#include + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +input string Database = "MQL5Book/DB/Quotes"; +input datetime BacktestStart = D'2015.01.01'; +input datetime ForwardStart = D'2021.01.01'; + +const string Table = "MqlRatesDB"; + +#resource "DBQuotesIntradayBackAndForward.sql" as string sql1 + +/* + Copy & paste example for SQL query in MetaEditor DB viewer + +SELECT * FROM +( + SELECT + AVG(product) / STDDEV(product) AS objective, + SUM(estimate) AS backtest_profit, + SUM(CASE WHEN estimate >= 0 THEN estimate ELSE 0 END) / SUM(CASE WHEN estimate < 0 THEN -estimate ELSE 0 END) AS backtest_PF, + intraday, day + FROM + ( + SELECT + time, + TIME(time, 'unixepoch') AS intraday, + STRFTIME('%w', time, 'unixepoch') AS day, + (LAG(open,-1) OVER (ORDER BY time) - open) * (open - LAG(open) OVER (ORDER BY time)) AS product, + (LAG(open,-1) OVER (ORDER BY time) - open) * SIGN(open - LAG(open) OVER (ORDER BY time)) AS estimate + FROM MqlRatesDB + WHERE (time >= STRFTIME('%s', '2015-01-01') AND time < STRFTIME('%s', '2021-01-01')) + ) + GROUP BY intraday, day +) backtest +JOIN +( + SELECT + AVG(product) / STDDEV(product) AS objective2, + SUM(estimate) AS forward_profit, + SUM(CASE WHEN estimate >= 0 THEN estimate ELSE 0 END) / SUM(CASE WHEN estimate < 0 THEN -estimate ELSE 0 END) AS forward_PF, + intraday, day + FROM + ( + SELECT + time, + TIME(time, 'unixepoch') AS intraday, + STRFTIME('%w', time, 'unixepoch') AS day, + (LAG(open,-1) OVER (ORDER BY time) - open) * (open - LAG(open) OVER (ORDER BY time)) AS product, + (LAG(open,-1) OVER (ORDER BY time) - open) * SIGN(open - LAG(open) OVER (ORDER BY time)) AS estimate + FROM MqlRatesDB + WHERE (time >= STRFTIME('%s', '2021-01-01')) + ) + GROUP BY intraday, day +) forward +USING(intraday, day) +ORDER BY objective DESC + +*/ + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + Print(""); + DBSQLite db(Database + _Symbol + PeriodToString()); + if(!PRTF(db.isOpen())) return; + if(!PRTF(db.hasTable(Table))) return; + + // custom "preparation" of SQL-query for formatting + string sqlrep = sql1; + // single percent sign would be consumed by StringFormat, + // we need to preserve it 'as is' for proper SQL execution + StringReplace(sqlrep, "%", "%%"); + StringReplace(sqlrep, "?1", "%ld"); + StringReplace(sqlrep, "?2", "%ld"); + + // actual parameter substitution + const string sqlfmt = StringFormat(sqlrep, BacktestStart, ForwardStart, ForwardStart); + Print(sqlfmt); + + // SQL-query execution and print out + DatabasePrint(db.getHandle(), sqlfmt, 0); +} +//+------------------------------------------------------------------+ +/* + +db.isOpen()=true / ok +db.hasTable(Table)=true / ok +SELECT * FROM +( + SELECT + AVG(product) / STDDEV(product) AS objective, + SUM(estimate) AS backtest_profit, + SUM(CASE WHEN estimate >= 0 THEN estimate ELSE 0 END) / SUM(CASE WHEN estimate < 0 THEN -estimate ELSE 0 END) AS backtest_PF, + intraday, day + FROM + ( + SELECT + time, + TIME(time, 'unixepoch') AS intraday, + STRFTIME('%w', time, 'unixepoch') AS day, + (LAG(open,-1) OVER (ORDER BY time) - open) * (open - LAG(open) OVER (ORDER BY time)) AS product, + (LAG(open,-1) OVER (ORDER BY time) - open) * SIGN(open - LAG(open) OVER (ORDER BY time)) AS estimate + FROM MqlRatesDB + WHERE (time >= 1420070400 AND time < 1609459200) + ) + GROUP BY intraday, day +) backtest +JOIN +( + SELECT + SUM(estimate) AS forward_profit, + SUM(CASE WHEN estimate >= 0 THEN estimate ELSE 0 END) / SUM(CASE WHEN estimate < 0 THEN -estimate ELSE 0 END) AS forward_PF, + intraday, day + FROM + ( + SELECT + time, + TIME(time, 'unixepoch') AS intraday, + STRFTIME('%w', time, 'unixepoch') AS day, + (LAG(open,-1) OVER (ORDER BY time) - open) * SIGN(open - LAG(open) OVER (ORDER BY time)) AS estimate + FROM MqlRatesDB + WHERE (time >= 1609459200) + ) + GROUP BY intraday, day +) forward +USING(intraday, day) +ORDER BY objective DESC + #| objective backtest_profit backtest_PF intraday day forward_profit forward_PF +---+------------------------------------------------------------------------------------------------------------------- + 1| 0.16713214428916 0.073200000000001 1.46040631486258 16:00:00 5 0.00492000000000048 1.12852664576804 + 2| 0.118128291843983 0.0433099999999995 1.33678071539657 20:00:00 3 0.00788000000000055 1.277856135402 + 3| 0.103701251751617 0.00929999999999853 1.14148790506616 05:00:00 2 0.00221000000000082 1.12149532710285 + 4| 0.102930330078208 0.0164399999999973 1.1932071923845 08:00:00 4 0.00140999999999969 1.07253086419751 + 5| 0.089531492651001 0.0064300000000006 1.10167615433271 07:00:00 2 -0.00911999999999869 0.561749159058204 + 6| 0.0827628326995007 -8.99999999970369e-05 0.999601152226913 17:00:00 4 0.00907000000000091 1.18809622563254 + 7| 0.0823433025146974 0.0159700000000012 1.21665988332657 21:00:00 1 0.0025099999999999 1.12131464475591 + 8| 0.0767938336191962 0.00522999999999874 1.04226945769012 13:00:00 1 -0.00849000000000055 0.753913043478245 + 9| 0.0657741522256548 0.0162299999999986 1.09699976093712 15:00:00 2 0.0142399999999997 1.34979120609187 + 10| 0.0635243373432768 0.00932000000000044 1.08294766820933 22:00:00 3 -0.0045699999999993 0.828967065868293 + 11| 0.0623455237646223 0.0154000000000003 1.10044351682755 15:00:00 1 -0.020020000000001 0.568534482758611 + 12| 0.0611311003782229 0.0191399999999957 1.10094404303568 17:00:00 3 -0.00448999999999944 0.932389700346342 + 13| 0.0603091366315941 0.0277700000000005 1.41929639136343 05:00:00 5 -0.0029899999999996 0.837852494577024 + 14| 0.0585389379786665 0.00334999999999996 1.03894443152755 04:00:00 3 0.00153999999999987 1.07519531249999 + 15| 0.057377314038757 0.0193600000000012 1.23684854416444 04:00:00 4 -0.0151399999999986 0.514588008977275 + 16| 0.0573063422330552 0.0117299999999985 1.08600337268127 12:00:00 4 -0.00972999999999979 0.750640697078428 + 17| 0.0572803333187238 0.0144899999999994 1.28108632395731 06:00:00 5 0.000499999999999945 1.03192848020434 + 18| 0.0543313045372004 0.0231100000000006 1.13573358393046 10:00:00 2 0.019229999999998 1.42106415590097 + 19| 0.0522234308193375 0.00711999999999868 1.08198986642099 08:00:00 3 -0.0075500000000005 0.705423332032761 + 20| 0.0509042604023954 0.0146599999999975 1.09737628694784 11:00:00 4 0.00931000000000037 1.28349573690623 + 21| 0.050544127390581 0.0134200000000027 1.11372881355935 12:00:00 2 0.00378999999999963 1.1078849985767 + 22| 0.0505094536724595 0.0120799999999981 1.175555878506 05:00:00 3 0.00445000000000073 1.29845741113353 + 23| 0.0492348110892364 -0.00231000000000092 0.962711864406766 06:00:00 3 -0.00483999999999896 0.698066126013787 + 24| 0.0479673290683895 0.00780000000000114 1.12751348700346 02:00:00 2 0.00957000000000052 1.93639921722122 + 25| 0.0462716009204473 0.0299700000000018 1.18895403820693 16:00:00 1 -0.019829999999999 0.647278548559248 + 26| 0.0458139381349133 0.00218999999999991 1.01262466132472 21:00:00 3 0.0115300000000005 1.27354685646502 + 27| 0.0408907469745224 0.000309999999998034 1.0049871299871 07:00:00 3 0.000669999999999837 1.0384394721744 + 28| 0.0393633749488827 -0.00718000000000019 0.924221635883902 03:00:00 5 -0.00324999999999998 0.879718726868986 + 29| 0.0369741494098486 -0.0319400000000005 0.870173156653929 16:00:00 4 -0.0142600000000006 0.778192564940107 + 30| 0.0353247937148132 0.0161000000000002 1.08557457212714 16:00:00 3 -0.0183900000000006 0.674109516214764 + 31| 0.0321292435692497 0.00850000000000062 1.06220270764728 13:00:00 4 0.0109700000000008 1.40212609970678 + 32| 0.0305136698247891 0.0232900000000011 1.20561490244549 21:00:00 4 -0.010350000000001 0.639874739039643 + 33| 0.0289062786935775 0.0255699999999999 1.21062602965404 13:00:00 2 0.0141600000000007 1.5627980922099 + 34| 0.0271064989563544 -0.0347399999999973 0.778344924392285 09:00:00 3 -0.0066799999999998 0.820478366030643 + 35| 0.0266151766395079 -0.0027100000000011 0.981655723278947 11:00:00 5 0.0109199999999983 1.37956204379554 + 36| 0.0257887732684008 0.00848999999999878 1.0626892121391 09:00:00 5 -1.00000000000655e-05 0.999681933842237 + 37| 0.0245688226705506 0.00143000000000026 1.02301255230126 07:00:00 5 -0.0033999999999994 0.803126809496275 + 38| 0.0239247828463491 -0.0229999999999997 0.842541247347165 13:00:00 5 0.00840999999999936 1.29939480242076 + 39| 0.0214966998043054 -0.0251300000000025 0.869914069779468 15:00:00 3 -0.00419000000000047 0.929991645781112 + 40| 0.0208336620016311 0.00290999999999753 1.02191430077564 12:00:00 1 -0.01241 0.723423222643192 + 41| 0.0206033857840952 -0.00157000000000052 0.991458571350849 16:00:00 2 -0.0195599999999984 0.641429880843286 + 42| 0.0198926317510929 -0.000850000000000684 0.988288784789189 02:00:00 4 -0.00311999999999912 0.854748603351994 + 43| 0.0182102258283443 -0.00463000000000169 0.960376551133918 19:00:00 1 -0.00382999999999944 0.883657351154331 + 44| 0.0161172998833366 0.0147599999999994 1.17186772240335 04:00:00 2 0.00600999999999941 1.31765327695558 + 45| 0.0149682763181311 -0.0395100000000017 0.84912937223155 15:00:00 4 -0.0263600000000002 0.619295205083764 + 46| 0.0145351167678307 0.00109999999999699 1.00680608835538 10:00:00 3 -0.0100600000000002 0.775346136668152 + 47| 0.0115935896337062 -0.00084000000000084 0.98976109215016 23:00:00 3 -0.00368000000000013 0.800650054171171 + 48| 0.0101605494765125 -0.0217299999999982 0.886224409654964 14:00:00 4 0.00175999999999976 1.04548979064357 + 49| 0.00958770083330551 0.00705999999999918 1.13908589440503 01:00:00 3 -0.00507999999999986 0.693236714975862 + 50| 0.00608558150441834 0.00773000000000157 1.13345994475141 07:00:00 1 -0.00625999999999949 0.681100356597068 + 51| 0.00492752747188311 0.0188500000000016 1.24222564893346 04:00:00 1 0.00463000000000058 1.19037828947371 + 52| 0.00119434960576392 0.0206499999999974 1.18782972530469 19:00:00 5 -0.0048499999999978 0.834527465029073 + 53| -0.000838154722261324 0.0116900000000013 1.05888575458393 10:00:00 4 0.00574999999999859 1.13026733121882 + 54| -0.00202838775890642 -0.00484999999999958 0.923969274180913 07:00:00 4 -0.00330000000000008 0.83128834355828 + 55| -0.00211381131508719 0.0123100000000016 1.06296353127718 17:00:00 2 -0.00424999999999975 0.922289266776381 + 56| -0.00295885735463623 0.0110399999999997 1.19031201516979 01:00:00 4 -0.000290000000001012 0.980218281036766 + 57| -0.00300915367363466 -0.0132099999999999 0.872305461575641 21:00:00 2 0.00117000000000056 1.05058365758757 + 58| -0.00442017218158207 0.012690000000003 1.09391651865011 14:00:00 2 -0.00929000000000046 0.757060669456058 + 59| -0.00454035248777325 0.00692999999999855 1.04569130348783 09:00:00 2 -0.00823999999999925 0.812129502963995 + 60| -0.00597400371437388 -0.00529000000000268 0.964899475814461 18:00:00 5 0.000800000000001244 1.02171552660155 + 61| -0.00739364354810872 -0.0152200000000016 0.881951446521357 14:00:00 1 -0.00385999999999975 0.899792315680172 + 62| -0.00901765750534236 -0.0267299999999984 0.774316109422505 22:00:00 5 0.00232000000000032 1.11611611611614 + 63| -0.00930357306364694 -0.0085399999999991 0.86239123428941 23:00:00 1 0.00185999999999886 1.1510966693744 + 64| -0.00943902403661846 -0.0468 0.747545582047685 23:00:00 5 -0.0334499999999986 0.3552428681573 + 65| -0.0154584740504693 0.00992000000000326 1.09304943251105 03:00:00 1 -0.00683000000000078 0.78945745992599 + 66| -0.0157180125534758 -0.00272999999999612 0.978291984732855 12:00:00 3 -0.00719999999999976 0.771718452758407 + 67| -0.0170471590021677 -0.0230099999999998 0.695070235886563 23:00:00 2 -0.00379999999999892 0.755627009646364 + 68| -0.0183799552509626 -0.0104800000000005 0.921456943715802 13:00:00 3 0.00522000000000056 1.15086705202314 + 69| -0.0209693895581613 0.00687999999999778 1.05500919485086 14:00:00 3 -0.000919999999999366 0.971250000000019 + 70| -0.0211148080619913 0.00145000000000417 1.01785934228358 22:00:00 2 0.00246000000000068 1.1538461538462 + 71| -0.022375359030735 -0.0162200000000015 0.798007471980061 02:00:00 5 -0.00108999999999981 0.939712389380541 + 72| -0.0276963976069807 -0.00566999999999873 0.931405758528929 03:00:00 2 -0.000949999999998896 0.964150943396268 + 73| -0.0297160844533128 -0.0166299999999993 0.886158269441406 20:00:00 4 0.00743000000000027 1.30117551682206 + 74| -0.0301792471418656 0.011540000000001 1.08227577356339 09:00:00 1 0.00107999999999997 1.03134978229318 + 75| -0.0332789718520231 -0.00461000000000089 0.92851604899983 01:00:00 5 -0.0025400000000011 0.822625698323958 + 76| -0.033767151799259 -0.00590000000000157 0.895036470378908 01:00:00 2 7.0000000002457e-05 1.00585284280957 + 77| -0.0351809058498753 -0.0296499999999968 0.679494108745024 04:00:00 5 -0.00198999999999927 0.911239964317603 + 78| -0.0358166251670762 -0.0191800000000029 0.743102062684134 05:00:00 1 -0.000670000000001503 0.968003820439281 + 79| -0.038052881227643 -0.0114300000000001 0.838399547575284 23:00:00 4 -0.00961000000000034 0.568089887640445 + 80| -0.038397862746323 -0.0167500000000012 0.889875082182767 11:00:00 3 0.0152699999999998 1.46063348416289 + 81| -0.039724911138701 -0.00453999999999999 0.940513626834382 22:00:00 1 -0.0185900000000001 0.387882779058273 + 82| -0.0408406951795748 -0.0580999999999992 0.804961562993053 15:00:00 5 0.0119299999999998 1.21163739577789 + 83| -0.0443689865111622 -0.0186799999999998 0.78261375538229 05:00:00 4 -0.00776999999999961 0.738823529411779 + 84| -0.0448716595913865 0.00143999999999878 1.01407074457689 20:00:00 5 0.00618000000000096 1.23180795198804 + 85| -0.044984167587657 -0.0308200000000005 0.776666666666663 20:00:00 2 -0.00129000000000079 0.945454545454513 + 86| -0.0463706489926068 -0.00487999999999933 0.979101537407394 17:00:00 5 0.013159999999999 1.2471825694966 + 87| -0.0519635249552873 -0.0152199999999982 0.861837327523617 20:00:00 1 -0.00120999999999971 0.946436476316967 + 88| -0.0525362253765298 -0.0242299999999984 0.851468154232828 18:00:00 2 -0.01322 0.725896744764672 + 89| -0.0532284987864957 -0.0200199999999997 0.864802809292276 18:00:00 1 -0.0088199999999995 0.764862703279134 + 90| -0.0533104397953623 0.00575000000000125 1.06522972206468 01:00:00 1 0.0186700000000004 2.24466666666674 + 91| -0.0553214020292255 -3.00000000008627e-05 0.999647556390967 08:00:00 1 -0.00504000000000038 0.7618147448015 + 92| -0.0587282865228479 -0.00770000000000004 0.947321611821851 11:00:00 1 -0.0224199999999999 0.560994713138833 + 93| -0.0606411180940267 -0.0611000000000004 0.647594878301995 09:00:00 4 -0.000249999999998973 0.993981704381344 + 94| -0.0606727956038308 -0.047079999999998 0.697778918988326 18:00:00 3 -0.01135 0.739857895943161 + 95| -0.0658053715209465 -0.0170099999999997 0.90405009025271 18:00:00 4 0.01947 1.52721364744111 + 96| -0.0660313886783148 -0.00735000000000152 0.958920187793419 17:00:00 1 0.0168900000000005 1.51212856276532 + 97| -0.0663811860556746 -0.014619999999999 0.92068572668584 11:00:00 2 0.00511000000000006 1.12865055387714 + 98| -0.066476045086213 -0.0240500000000008 0.654900272635954 00:00:00 4 -0.00852999999999948 0.345356868764446 + 99| -0.0666325857658802 -0.0165500000000016 0.915776081424929 10:00:00 1 0.000679999999998904 1.0144037280237 +100| -0.0691986027849773 -0.0189199999999987 0.760506329113941 02:00:00 3 -0.00666000000000067 0.619428571428557 +101| -0.0697287093075506 -0.00283999999999929 0.952563888424931 06:00:00 1 -0.00201000000000051 0.887142055025236 +102| -0.0698406627783849 -0.0243999999999995 0.873823559830388 10:00:00 5 -0.00396999999999958 0.919878910191734 +103| -0.0700099737151066 0.00629999999999775 1.07211538461536 03:00:00 3 0.000419999999998866 1.02045786653672 +104| -0.0734717138519144 -0.00658999999999721 0.951040118870748 19:00:00 2 0.00441999999999831 1.15552427867693 +105| -0.0750612413078805 -0.00437000000000043 0.95588532202705 03:00:00 4 -0.00525000000000064 0.817136886102382 +106| -0.0759898392818295 -0.00520000000000231 0.919728311207127 06:00:00 2 -0.00207000000000002 0.890069038767926 +107| -0.0775250903401206 -0.0100600000000002 0.923730098559513 14:00:00 5 0.00632999999999839 1.14911660777381 +108| -0.0800435048271025 -0.0124500000000001 0.905688962957352 19:00:00 3 0.00657999999999914 1.26028481012654 +109| -0.0804303804315245 -0.0245099999999987 0.764440172993765 08:00:00 5 -0.00529000000000091 0.786865431103914 +110| -0.0814131025461459 -0.0189100000000015 0.820605255668329 21:00:00 5 -0.0018800000000001 0.918579471632738 +111| -0.0899571263478305 -0.0321900000000028 0.721250432975386 22:00:00 4 0.00919000000000092 1.44938875305629 +112| -0.0909772560603298 -0.0226100000000016 0.851161872161138 19:00:00 4 0.00591999999999948 1.16403435854806 +113| -0.0961794181717023 -0.00846999999999931 0.936377976414036 12:00:00 5 0.00716999999999968 1.26206140350876 +114| -0.108868074018582 -0.0246099999999998 0.634920634920637 00:00:00 5 -0.00812999999999997 0.409586056644885 +115| -0.109368419185336 -0.0250700000000013 0.744496534855268 08:00:00 2 -0.00333999999999979 0.845441925034718 +116| -0.121893581607986 -0.0234599999999998 0.610945273631843 00:00:00 3 -0.00710000000000099 0.408333333333295 +117| -0.135416609546408 -0.0898899999999971 0.343437294573087 00:00:00 1 -0.0282299999999989 0.126276694521833 +118| -0.142128458003631 -0.0255200000000018 0.681835182645536 06:00:00 4 -0.000400000000000178 0.975874547647758 +119| -0.142196924506816 -0.0205700000000004 0.629769618430515 00:00:00 2 -0.00465999999999966 0.507919746568069 +120| -0.15200009633513 -0.0301499999999988 0.708864426419475 02:00:00 1 0.00109000000000137 1.06385471587589 + +*/ +//+------------------------------------------------------------------+ diff --git a/DBQuotesIntradayLag.mq5 b/DBQuotesIntradayLag.mq5 new file mode 100644 index 0000000..f3e7e89 --- /dev/null +++ b/DBQuotesIntradayLag.mq5 @@ -0,0 +1,97 @@ +//+------------------------------------------------------------------+ +//| DBQuotesIntradayLag.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Display quotes imported into a database, with LAGs.\nUse DBquotesImport.mq5 to generate and populate the database beforehand." +#property script_show_inputs + +#include +#include + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +input string Database = "MQL5Book/DB/Quotes"; +input datetime SubsetStart = D'2022.01.01'; +input datetime SubsetStop = D'2023.01.01'; +input int Limit = 10; + +const string Table = "MqlRatesDB"; + +#resource "DBQuotesIntradayLag.sql" as string sql1 + +/* + Copy & paste example for SQL query in MetaEditor DB viewer + + SELECT + DATETIME(time, 'unixepoch') as datetime, + time, + TIME(time, 'unixepoch') AS intraday, + STRFTIME('%w', time, 'unixepoch') AS day, + (LAG(open,-1) OVER (ORDER BY time) - open) AS delta, + SIGN(open - LAG(open) OVER (ORDER BY time)) AS direction, + (LAG(open,-1) OVER (ORDER BY time) - open) * (open - LAG(open) OVER (ORDER BY time)) AS product, + (LAG(open,-1) OVER (ORDER BY time) - open) * SIGN(open - LAG(open) OVER (ORDER BY time)) AS estimate + FROM MqlRatesDB + WHERE (time >= STRFTIME('%s', '2015-01-01') AND time < STRFTIME('%s', '2021-01-01')) +*/ + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + Print(""); + DBSQLite db(Database + _Symbol + PeriodToString()); + if(!PRTF(db.isOpen())) return; + if(!PRTF(db.hasTable(Table))) return; + + // custom "preparation" of SQL-query for formatting + string sqlrep = sql1; + // single percent sign would be consumed by StringFormat, + // we need to preserve it 'as is' for proper SQL execution + StringReplace(sqlrep, "%", "%%"); + StringReplace(sqlrep, "?1", "%ld"); + StringReplace(sqlrep, "?2", "%ld"); + StringReplace(sqlrep, "?3", "%d"); + + // actual parameter substitution + const string sqlfmt = StringFormat(sqlrep, SubsetStart, SubsetStop, Limit); + Print(sqlfmt); + + // SQL-query execution and print out + DatabasePrint(db.getHandle(), sqlfmt, 0); +} +//+------------------------------------------------------------------+ +/* + + db.isOpen()=true / ok + db.hasTable(Table)=true / ok + SELECT + DATETIME(time, 'unixepoch') as datetime, + time, + TIME(time, 'unixepoch') AS intraday, + STRFTIME('%w', time, 'unixepoch') AS day, + (LAG(open,-1) OVER (ORDER BY time) - open) AS delta, + SIGN(open - LAG(open) OVER (ORDER BY time)) AS direction, + (LAG(open,-1) OVER (ORDER BY time) - open) * (open - LAG(open) OVER (ORDER BY time)) AS product, + (LAG(open,-1) OVER (ORDER BY time) - open) * SIGN(open - LAG(open) OVER (ORDER BY time)) AS estimate + FROM MqlRatesDB + WHERE (time >= 1640995200 AND time < 1672531200) + ORDER BY time LIMIT 10; + #| datetime open time intraday day delta direction product estimate + --+-------------------------------------------------------------------------------------------------------------------------------- + 1| 2022-01-03 00:00:00 1.13693 1641168000 00:00:00 1 0.000320000000000098 + 2| 2022-01-03 01:00:00 1.13725 1641171600 01:00:00 1 2.99999999999745e-05 1 9.59999999999478e-09 2.99999999999745e-05 + 3| 2022-01-03 02:00:00 1.13728 1641175200 02:00:00 1 -0.00106000000000006 1 -3.17999999999748e-08 -0.00106000000000006 + 4| 2022-01-03 03:00:00 1.13622 1641178800 03:00:00 1 -0.000340000000000007 -1 3.60400000000028e-07 0.000340000000000007 + 5| 2022-01-03 04:00:00 1.13588 1641182400 04:00:00 1 -0.00157999999999991 -1 5.37199999999982e-07 0.00157999999999991 + 6| 2022-01-03 05:00:00 1.1343 1641186000 05:00:00 1 0.000529999999999919 -1 -8.37399999999827e-07 -0.000529999999999919 + 7| 2022-01-03 06:00:00 1.13483 1641189600 06:00:00 1 -0.000769999999999937 1 -4.08099999999905e-07 -0.000769999999999937 + 8| 2022-01-03 07:00:00 1.13406 1641193200 07:00:00 1 -0.000260000000000149 -1 2.00200000000098e-07 0.000260000000000149 + 9| 2022-01-03 08:00:00 1.1338 1641196800 08:00:00 1 0.00051000000000001 -1 -1.32600000000079e-07 -0.00051000000000001 + 10| 2022-01-03 09:00:00 1.13431 1641200400 09:00:00 1 0.000480000000000036 1 2.44800000000023e-07 0.000480000000000036 + +*/ +//+------------------------------------------------------------------+ diff --git a/DBcreateTable.mq5 b/DBcreateTable.mq5 new file mode 100644 index 0000000..8345d31 --- /dev/null +++ b/DBcreateTable.mq5 @@ -0,0 +1,42 @@ +//+------------------------------------------------------------------+ +//| DBcreateTable.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Creates a table in a given database. If the database doesn't exist, it creates it beforehand." +#property script_show_inputs + +input string Database = "MQL5Book/DB/Example1"; +input string Table = "table1"; + +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + DBSQLite db(Database); + if(db.isOpen()) + { + PRTF(db.execute(StringFormat("CREATE TABLE %s (msg text)", Table))); + // the following modification will not throw error + // on attempt of creating already existing table + // PRTF(db.execute(StringFormat("CREATE TABLE IF NOT EXISTS %s (msg text)", Table))); + PRTF(db.hasTable(Table)); + } +} +//+------------------------------------------------------------------+ +/* + 1-st run with default inputs + + db.execute(StringFormat(CREATE TABLE %s (msg text),Table))=true / ok + db.hasTable(Table)=true / ok + + 2-nd run with default inputs + + database error, table table1 already exists + db.execute(StringFormat(CREATE TABLE %s (msg text),Table))=false / DATABASE_ERROR(5601) + db.hasTable(Table)=true / ok +*/ +//+------------------------------------------------------------------+ diff --git a/DBcreateTableFromStruct.mq5 b/DBcreateTableFromStruct.mq5 new file mode 100644 index 0000000..7d397ee --- /dev/null +++ b/DBcreateTableFromStruct.mq5 @@ -0,0 +1,74 @@ +//+------------------------------------------------------------------+ +//| DBcreateTableFromStruct.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Creates a table based on struct declaration. If the database doesn't exist, it creates it beforehand." +#property script_show_inputs + +#include + +input string Database = "MQL5Book/DB/Example1"; + +//+------------------------------------------------------------------+ +//| Example struct with common field types | +//+------------------------------------------------------------------+ +struct Struct +{ + long id; + string name; + double income; + datetime time; +}; + +// Unfortunately we can't declare the fields once (inside the struct) +// because every macro (see below) creates a templated struct underneath, +// which is, because of nesting, makes the holding struct +// complex and incompatible with DB-binding. + +// NB: if PRIMARY_KEY constaint is not specified for one of integer fields, +// implicit 'rowid' column will be added as primary key automatically by SQL + +DB_FIELD_C1(Struct, long, id, DB_CONSTRAINT::PRIMARY_KEY); +DB_FIELD(Struct, string, name); +DB_FIELD(Struct, double, income); +DB_FIELD(Struct, string, time); + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + DBSQLite db(Database); + if(db.isOpen()) + { + PRTF(db.createTable()); + // the following modification will not throw error + // on attempt of creating already existing table + // PRTF(db.createTable(true)); + PRTF(db.hasTable(TYPENAME(Struct))); + } +} +//+------------------------------------------------------------------+ +/* + 1-st run with default inputs + + sql=CREATE TABLE Struct (id INTEGER PRIMARY KEY, + name TEXT , + income REAL , + time TEXT ); / ok + db.createTable()=true / ok + db.hasTable(typename(Struct))=true / ok + + 2-nd run with default inputs + + sql=CREATE TABLE Struct (id INTEGER PRIMARY KEY, + name TEXT , + income REAL , + time TEXT ); / ok + database error, table Struct already exists + db.createTable()=false / DATABASE_ERROR(5601) + db.hasTable(typename(Struct))=true / ok + +*/ +//+------------------------------------------------------------------+ diff --git a/DBinit.mq5 b/DBinit.mq5 new file mode 100644 index 0000000..49c0e52 --- /dev/null +++ b/DBinit.mq5 @@ -0,0 +1,22 @@ +//+------------------------------------------------------------------+ +//| DBinit.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Creates a new or opens existing database." +#property script_show_inputs + +input string Database = "MQL5Book/DB/Example1"; + +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + DBSQLite db(Database); + PRTF(db.getHandle()); // 65537 / ok + PRTF(FileIsExist(Database + ".sqlite")); // true / ok +} +//+------------------------------------------------------------------+ diff --git a/DBquickStartWithStruct.mq5 b/DBquickStartWithStruct.mq5 new file mode 100644 index 0000000..6d6b7c3 --- /dev/null +++ b/DBquickStartWithStruct.mq5 @@ -0,0 +1,150 @@ +//+------------------------------------------------------------------+ +//| DBquickStartWithStruct.mq5 | +//| Copyright 2019-2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2019-2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.0" + +//+------------------------------------------------------------------+ +//| Data structure for single record in database | +//+------------------------------------------------------------------+ +struct Person +{ + int id; + string name; + int age; + string address; + double salary; +}; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const string filename = "company.sqlite"; + // create or open database + const int db = DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); + if(db == INVALID_HANDLE) + { + Print("DB: ", filename, " open failed with code ", _LastError); + return; + } + + // if the table COMPANY exists, delete it + if(DatabaseTableExists(db, "COMPANY")) + { + if(!DatabaseExecute(db, "DROP TABLE COMPANY")) + { + Print("Failed to drop table COMPANY with code ", _LastError); + DatabaseClose(db); + return; + } + } + + // create the table COMPANY (empty) with 5 columns + if(!DatabaseExecute(db, + "CREATE TABLE COMPANY(" + "ID INT PRIMARY KEY NOT NULL," + "NAME TEXT NOT NULL," + "AGE INT NOT NULL," + "ADDRESS CHAR(50)," + "SALARY REAL);")) + { + Print("DB: ", filename, " create table failed with code ", _LastError); + DatabaseClose(db); + return; + } + + // add some data (4 records) in the table + if(!DatabaseExecute(db, + "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1,'Paul',32,'California',25000.00); " + "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (2,'Allen',25,'Texas',15000.00); " + "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (3,'Teddy',23,'Norway',20000.00);" + "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (4,'Mark',25,'Rich-Mond',65000.00);")) + { + Print("DB: ", filename, " insert failed with code ", _LastError); + DatabaseClose(db); + return; + } + + // prepare SQL query and get a handle for it + int request = DatabasePrepare(db, "SELECT * FROM COMPANY WHERE SALARY>15000"); + if(request == INVALID_HANDLE) + { + Print("DB: ", filename, " request failed with code ", _LastError); + DatabaseClose(db); + return; + } + + // print all records with salary larger than 15000 + Person person; + Print("Persons with salary > 15000:"); + // keep reading while DatabaseReadBind returns true and fills the struct person with data + for(int i = 0; DatabaseReadBind(request, person); i++) + { + Print(i, ": ", person.id, " ", person.name, " ", person.age, " ", person.address, " ", person.salary); + } + // release query handle after use + DatabaseFinalize(request); + + Print("Some statistics:"); + // prepare new query for sum of salaries + request = DatabasePrepare(db, "SELECT SUM(SALARY) FROM COMPANY"); + if(request == INVALID_HANDLE) + { + Print("DB: ", filename, " request failed with code ", _LastError); + DatabaseClose(db); + return; + } + // this should be a single "record" with a single value + if(DatabaseRead(request)) + { + double sum; + DatabaseColumnDouble(request, 0, sum); + Print("Total salary=", sum); + } + else + { + Print("DB: DatabaseRead() failed with code ", _LastError); + } + // release the handle after use + DatabaseFinalize(request); + + // prepare another query for average salary + request = DatabasePrepare(db, "SELECT AVG(SALARY) FROM COMPANY"); + if(request == INVALID_HANDLE) + { + Print("DB: ", filename, " request failed with code ", _LastError); + DatabaseClose(db); + return; + } + if(DatabaseRead(request)) + { + double average; + DatabaseColumnDouble(request, 0, average); + Print("Average salary=", average); + } + else + { + Print("DB: DatabaseRead() failed with code ", _LastError); + } + DatabaseFinalize(request); + + // close the database + DatabaseClose(db); +} +//+------------------------------------------------------------------+ +/* + example output: + Persons with salary > 15000: + 0: 1 Paul 32 California 25000.0 + 1: 3 Teddy 23 Norway 20000.0 + 2: 4 Mark 25 Rich-Mond 65000.0 + Some statistics: + Total salary=125000.0 + Average salary=31250.0 +*/ +//+------------------------------------------------------------------+ diff --git a/DBquotesImport.mq5 b/DBquotesImport.mq5 new file mode 100644 index 0000000..3fa5c93 --- /dev/null +++ b/DBquotesImport.mq5 @@ -0,0 +1,139 @@ +//+------------------------------------------------------------------+ +//| DBquotesImport.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Creates and populates a database table with specific quotes." +#property script_show_inputs + +#define PRTF // disable bulk logging +#include +#undef PRTF +#include +#include + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +input string Database = "MQL5Book/DB/Quotes"; +input int TransactionSize = 1000; + +//+------------------------------------------------------------------+ +//| Example struct with common field types | +//+------------------------------------------------------------------+ +struct MqlRatesDB: public MqlRates +{ + /* reference: + + datetime time; + double open; + double high; + double low; + double close; + long tick_volume; + int spread; + long real_volume; + */ + + bool bindAll(DBQuery &q) const + { + return q.bind(0, time) + && q.bind(1, open) + && q.bind(2, high) + && q.bind(3, low) + && q.bind(4, close) + && q.bind(5, tick_volume) + && q.bind(6, spread) + && q.bind(7, real_volume); + } + + long rowid(const long setter = 0) + { + // rowid is assigned by the time + return time; + } +}; + +DB_FIELD_C1(MqlRatesDB, datetime, time, DB_CONSTRAINT::PRIMARY_KEY); +DB_FIELD(MqlRatesDB, double, open); +DB_FIELD(MqlRatesDB, double, high); +DB_FIELD(MqlRatesDB, double, low); +DB_FIELD(MqlRatesDB, double, close); +DB_FIELD(MqlRatesDB, long, tick_volume); +DB_FIELD(MqlRatesDB, int, spread); +DB_FIELD(MqlRatesDB, long, real_volume); + + +//+------------------------------------------------------------------+ +//| Read a bunch of MqlRates[] and write to the database | +//+------------------------------------------------------------------+ +bool ReadChunk(DBSQLite &db, const int offset, const int size) +{ + MqlRates rates[]; + MqlRatesDB ratesDB[]; + const int n = CopyRates(_Symbol, PERIOD_CURRENT, offset, size, rates); + if(n > 0) + { + DBTransaction tr(db, true); + Print(rates[0].time); + ArrayResize(ratesDB, n); + for(int i = 0; i < n; ++i) + { + ratesDB[i] = rates[i]; + } + + return db.insert(ratesDB); + } + else + { + // normally finishes with HISTORY_NOT_FOUND + // when maximal number of bars loaded + // according to terminal settings + Print("CopyRates failed: ", _LastError, " ", E2S(_LastError)); + } + return false; +} + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + Print(""); + DBSQLite db(Database + _Symbol + PeriodToString()); + if(!PRTF(db.isOpen())) return; + + // remove the table (if exists) + PRTF(db.deleteTable(TYPENAME(MqlRatesDB))); + + // create empty table + if(!PRTF(db.createTable(true))) return; + + int offset = 0; + while(ReadChunk(db, offset, TransactionSize) && !IsStopped()) + { + offset += TransactionSize; + } + + DBRow *rows[]; + if(db.prepare(StringFormat("SELECT COUNT(*) FROM %s", + TYPENAME(MqlRatesDB))).readAll(rows)) + { + Print("Records added: ", rows[0][0].integer_value); + } +} +//+------------------------------------------------------------------+ +/* + + db.isOpen()=true / ok + db.deleteTable(typename(MqlRatesDB))=true / ok + db.createTable(true)=true / ok + 2022.06.29 20:00:00 + 2022.05.03 04:00:00 + 2022.03.04 10:00:00 + ... + CopyRates failed: 4401 HISTORY_NOT_FOUND + Records added: 100000 + +*/ +//+------------------------------------------------------------------+ diff --git a/DeltaPrice.mq5 b/DeltaPrice.mq5 new file mode 100644 index 0000000..6ff8ad3 --- /dev/null +++ b/DeltaPrice.mq5 @@ -0,0 +1,108 @@ +//+------------------------------------------------------------------+ +//| DeltaPrice.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property indicator_separate_window +#property indicator_buffers 1 +#property indicator_plots 1 +#property indicator_applied_price PRICE_CLOSE + +#property indicator_type1 DRAW_LINE +#property indicator_color1 clrDodgerBlue +#property indicator_width1 2 +#property indicator_style1 STYLE_SOLID + +#include + +input int Differencing = 1; + +int handle = 0; + +// indicator buffer +double Buffer[]; + +//+------------------------------------------------------------------+ +//| Helper function to get relative location inside MQL5 folder | +//+------------------------------------------------------------------+ +string GetMQL5Path() +{ + static const string MQL5 = "\\MQL5\\"; + static const int length = StringLen(MQL5) - 1; + static const string path = MQLInfoString(MQL_PROGRAM_PATH); + const int start = StringFind(path, MQL5); + if(start != -1) + { + return StringSubstr(path, start + length); + } + return path; +} + +//+------------------------------------------------------------------+ +//| Indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(Differencing < 0) return INIT_PARAMETERS_INCORRECT; + + // NB! If you want to request MQLInfoString(MQL_PROGRAM_NAME) + // to get indicator filename, you should do it before any call to + // IndicatorSetString(INDICATOR_SHORTNAME, "string"), + // because the last will change MQL_PROGRAM_NAME to the specified "string" + // instead of default "filename" (taken from the original 'filename.ex5') + + const string label = "DeltaPrice (" + (string)Differencing + "/" + APPLIED_TO_STR() + ")"; + IndicatorSetString(INDICATOR_SHORTNAME, label); + PlotIndexSetString(0, PLOT_LABEL, label); + IndicatorSetInteger(INDICATOR_DIGITS, _Digits); + + SetIndexBuffer(0, Buffer); + if(Differencing > 1) + { + handle = iCustom(_Symbol, _Period, GetMQL5Path(), Differencing - 1); + if(handle == INVALID_HANDLE) + { + return INIT_FAILED; + } + } + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Indicator calculation function | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + for(int i = fmax(prev_calculated - 1, 1); i < rates_total; ++i) + { + if(Differencing > 1) + { + static double value[2]; + if(CopyBuffer(handle, 0, rates_total - i - 1, 2, value) == 2) + { + Buffer[i] = value[1] - value[0]; + } + } + else if(Differencing == 1) + { + Buffer[i] = price[i] - price[i - 1]; + } + else + { + Buffer[i] = price[i]; + } + } + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Indicator finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + IndicatorRelease(handle); +} +//+------------------------------------------------------------------+ diff --git a/DeltaVolumeProfile.mq5 b/DeltaVolumeProfile.mq5 new file mode 100644 index 0000000..e6e985d --- /dev/null +++ b/DeltaVolumeProfile.mq5 @@ -0,0 +1,320 @@ +//+------------------------------------------------------------------+ +//| DeltaVolumeProfile.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +// indicator settings +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +// includes +#include +#include + +// inputs +input bool ShowSplittedDelta = true; +input COPY_TICKS TickType = INFO_TICKS; // TickType (use TRADE_TICKS if real volumes available) + +//+------------------------------------------------------------------+ +//| Class for volume delta calculation | +//+------------------------------------------------------------------+ +class DeltaVolumeProfile +{ + const COPY_TICKS tickType; + const ENUM_SYMBOL_CHART_MODE barType; + const bool delta; + + static const string prefix; + +protected: + double price(const MqlTick &tick) + { + return barType == SYMBOL_CHART_MODE_LAST ? tick.last : tick.bid; + } + + // main tick processing (on history only) + void calcProfile(const int b, const datetime time, const MqlTick &ticks[]) + { + const string name = prefix + (string)(ulong)time; + const double high = iHigh(_Symbol, _Period, b); + const double low = iLow(_Symbol, _Period, b); + const double range = high - low; + + if(fabs(range) < DBL_EPSILON) return; + if(ArraySize(ticks) == 0) return; + + ObjectCreate(0, name, OBJ_BITMAP, 0, time, high); + + int x1, y1, x2, y2; + ChartTimePriceToXY(0, 0, time, high, x1, y1); + ChartTimePriceToXY(0, 0, time, low, x2, y2); + + const int h = y2 - y1 + 1; + const int w = (int)(ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) + / ChartGetInteger(0, CHART_WIDTH_IN_BARS)); + + if(h <= 0) + { + Print("Bad data: ", high, " ", low, " ", y1, " ", y2); + DebugBreak(); + return; + } + + uint data[]; + ArrayResize(data, w * h); + ArrayInitialize(data, 0); + ResourceCreate(name + (string)ChartID(), data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE); + + ObjectSetString(0, name, OBJPROP_BMPFILE, "::" + name + (string)ChartID()); + ObjectSetInteger(0, name, OBJPROP_XSIZE, w); + ObjectSetInteger(0, name, OBJPROP_YSIZE, h); + ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_UPPER); + + long plus[], minus[], max = 0; + ArrayResize(plus, h); + ArrayResize(minus, h); + ArrayInitialize(plus, 0); + ArrayInitialize(minus, 0); + + const int n = ArraySize(ticks); + for(int j = 0; j < n; ++j) + { + const double p1 = price(ticks[j]); + /*const*/ int index = (int)((high - p1) / range * (h - 1)); + if(index >= h) + { + Print("Correction: index=", index, " ", high, " ", p1, " ", h); + index = h - 1; + DebugBreak(); + } + if(index < 0) + { + Print("Correction: index=", index, " ", high, " ", p1, " ", h); + index = 0; + DebugBreak(); + } + // when real volumes are expected to be available, check them in the ticks + if(tickType == TRADE_TICKS) + { + // accumulate volumes for buy and sell deals separately + if((ticks[j].flags & TICK_FLAG_BUY) != 0) + { + plus[index] += (long)ticks[j].volume; + } + if((ticks[j].flags & TICK_FLAG_SELL) != 0) + { + minus[index] += (long)ticks[j].volume; + } + } + else // tickType == INFO_TICKS or tickType == ALL_TICKS + if(j > 0) + { + // when real volumes are unavailable, use price moves up/down to estimate volume change + if((ticks[j].flags & (TICK_FLAG_ASK | TICK_FLAG_BID)) != 0) + { + const double d = (((ticks[j].ask + ticks[j].bid) + - (ticks[j - 1].ask + ticks[j - 1].bid)) / _Point); + if(d > 0) + plus[index] += (long)d; + else + minus[index] -= (long)d; + } + } + + if(delta) + { + if(plus[index] > max) max = plus[index]; + if(minus[index] > max) max = minus[index]; + } + else + { + if(fabs(plus[index] - minus[index]) > max) max = fabs(plus[index] - minus[index]); + } + } + + if(max == 0) + { + Print("No tick volumes for ", (string)time); + return; + } + + for(int i = 0; i < h; i++) + { + if(delta) + { + const int dp = (int)(plus[i] * w / 2 / max); + const int dm = (int)(minus[i] * w / 2 / max); + for(int j = 0; j < dp; j++) + { + data[i * w + w / 2 + j] = ColorToARGB(clrBlue); + } + for(int j = 0; j < dm; j++) + { + data[i * w + w / 2 - j] = ColorToARGB(clrRed); + } + } + else + { + const int d = (int)((plus[i] - minus[i]) * w / 2 / max); + const int sign = d > 0 ? +1 : -1; + for(int j = 0; j < fabs(d); j++) + { + data[i * w + w / 2 + j * sign] = ColorToARGB(clrGreen); + } + } + } + ResourceCreate(name + (string)ChartID(), data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE); + } + +public: + DeltaVolumeProfile(const COPY_TICKS type, const bool d) : + tickType(type), delta(d), + barType((ENUM_SYMBOL_CHART_MODE)SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE)) + { + } + + ~DeltaVolumeProfile() + { + // resources are not deleted with objects, + // so calling ObjectsDeleteAll(0, prefix, 0); is not enough + const int n = ObjectsTotal(0, 0); + for(int i = n - 1; i >= 0; --i) + { + const string name = ObjectName(0, i, 0); + if(StringFind(name, prefix) == 0) + { + ObjectDelete(0, name); + ResourceFree("::" + name + (string)ChartID()); + } + } + } + + // get ticks for specific bar on the history + int createProfileBar(const int i) + { + MqlTick ticks[]; + const datetime time = iTime(_Symbol, _Period, i); + // prev and next are timestamps of the bar boundaries + const datetime prev = time; + const datetime next = prev + PeriodSeconds(); + ResetLastError(); + const int n = CopyTicksRange(_Symbol, ticks, COPY_TICKS_ALL, prev * 1000, next * 1000 - 1); + if(n > -1 && _LastError == 0) + { + calcProfile(i, time, ticks); + } + else + { + return -_LastError; + } + + return n; + } + + // update bars where objects with histograms located + void updateProfileBars() + { + const int n = ObjectsTotal(0, 0); + for(int i = n - 1; i >= 0; --i) + { + const string name = ObjectName(0, i, 0); + if(StringFind(name, prefix) == 0) + { + const datetime dt = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 0); + const int bar = iBarShift(_Symbol, _Period, dt, true); + if(createProfileBar(bar) < 0) + { + ObjectDelete(0, name); + ResourceFree("::" + name + (string)ChartID()); + } + } + } + } +}; + +static const string DeltaVolumeProfile::prefix = "DVP"; + +//+------------------------------------------------------------------+ +//| Global variables | +//+------------------------------------------------------------------+ +DeltaVolumeProfile deltas(TickType, ShowSplittedDelta); + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + if(_Period >= PERIOD_D1) + { + Alert("Use intraday timeframe smaller than D1, please"); + return INIT_FAILED; + } + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Custom indicator iteration function | +//+------------------------------------------------------------------+ +int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Helper data function | +//+------------------------------------------------------------------+ + +#define TRY_AGAIN 0xAAA + +void RequestData(const int b, const datetime time, const int count = 0) +{ + Comment("Requesting ticks for ", time); + if(deltas.createProfileBar(b) <= 0) + { + Print("No data on bar ", b, ", at ", TimeToString(time), + ". Sending event for refresh..."); + ChartSetSymbolPeriod(0, _Symbol, _Period); // refresh myself + EventChartCustom(0, TRY_AGAIN, b, count + 1, NULL); + } + Comment(""); +} + +//+------------------------------------------------------------------+ +//| Chart event handler | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + if(id == CHARTEVENT_CLICK) + { + datetime time; + double price; + int window; + ChartXYToTimePrice(0, (int)lparam, (int)dparam, window, time, price); + time += PeriodSeconds() / 2; + const int b = iBarShift(_Symbol, _Period, time, true); + if(b != -1 && window == 0) + { + RequestData(b, iTime(_Symbol, _Period, b)); + } + } + else if(id == CHARTEVENT_CHART_CHANGE) + { + deltas.updateProfileBars(); + } + else if(id == CHARTEVENT_CUSTOM + TRY_AGAIN) + { + Print("Refreshing... ", (int)dparam); + const int b = (int)lparam; + if((int)dparam < 5) + { + RequestData(b, iTime(_Symbol, _Period, b), (int)dparam); + } + else + { + Print("Give up. Check tick history manually, please, then click the bar again"); + } + } +} +//+------------------------------------------------------------------+ diff --git a/EASY BUY SELL SIGNAL CORRECTED.mq5 b/EASY BUY SELL SIGNAL CORRECTED.mq5 new file mode 100644 index 0000000..9326062d --- /dev/null +++ b/EASY BUY SELL SIGNAL CORRECTED.mq5 @@ -0,0 +1,69 @@ +//+------------------------------------------------------------------+ +//| EASY BUY SELL SIGNAL CORRECTED.mq5 | +//| Copyright 2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "" +#property link "https://fxdreema.com" +#property description "" +#property version "1.0" +#property strict + +// Input Parameters +input bool ENABLE_SPREAD_METER = true; +input bool ENABLE_STATUS = true; +input bool ENABLE_TEST_INDICATORS = true; +input bool ENABLE_EVENT_TICK = true; // enable "Tick" event +input bool ENABLE_EVENT_TRADE = false; // enable "Trade" event +input bool ENABLE_EVENT_TIMER = false; // enable "Timer" event +input bool VIRTUAL_STOPS_ENABLED = false; // enable virtual stops +input int VIRTUAL_STOPS_TIMEOUT = 0; // virtual stops timeout +input string USE_EMERGENCY_STOPS = "no"; // "yes" to use emergency (hard stops) when virtual stops are in use. "always" to use EMERGENCY_STOPS_ADD as emergency stops when there is no virtual stop. +input int EMERGENCY_STOPS_REL = 0; // use 0 to disable hard stops when virtual stops are enabled. Use a value >=0 to automatically set hard stops with virtual. Example: if 2 is used, then hard stops will be 2 times bigger than virtual ones. +input int EMERGENCY_STOPS_ADD = 0; // add pips to relative size of emergency stops (hard stops) +input bool ON_TRADE_REALTIME = false; +input int ON_TIMER_PERIOD = 60; // Timer event period (in seconds) + +int easyBuySellHandle; + +int OnInit() +{ + easyBuySellHandle = iCustom(Symbol(), PERIOD_CURRENT, "Market\\Easy Buy Sell Signal"); + if (easyBuySellHandle == INVALID_HANDLE) + { + Print("Error loading Easy Buy Sell Signal indicator"); + return(INIT_FAILED); + } + return(INIT_SUCCEEDED); +} + +void OnDeinit(const int reason) +{ + if (easyBuySellHandle != INVALID_HANDLE) + { + IndicatorRelease(easyBuySellHandle); + } +} + +void OnTick() +{ + double buySignal[], sellSignal[]; + if (CopyBuffer(easyBuySellHandle, 0, 0, 1, buySignal) <= 0 || + CopyBuffer(easyBuySellHandle, 1, 0, 1, sellSignal) <= 0) + { + Print("Error reading indicator buffers"); + return; + } + + // Example logic based on indicator signals + if (buySignal[0] > 0) + { + // Execute buy trade + Print("Buy signal detected"); + } + else if (sellSignal[0] > 0) + { + // Execute sell trade + Print("Sell signal detected"); + } +} \ No newline at end of file diff --git a/EASY BUY SELL SIGNAL EA.mq5 b/EASY BUY SELL SIGNAL EA.mq5 new file mode 100644 index 0000000..f4f133d --- /dev/null +++ b/EASY BUY SELL SIGNAL EA.mq5 @@ -0,0 +1,78 @@ +//+------------------------------------------------------------------+ +//| EasyEA.mq5 | +//| Copyright 2024, MetaTrader 5 | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include + +// Create an instance of CTrade +CTrade trade; + +// Indicator handle +int handle; +datetime lastsignal + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { + string name = "Market\\Easy Buy Sell Signal.ex5" + // Initialize the indicator + handle = iCustom(_Symbol, PERIOD_CURRENT, name); + + // Check if the handle is valid + if(handle == INVALID_HANDLE) + { + Print("Failed to initialize indicator"); + return(INIT_FAILED); + } + + return(INIT_SUCCEEDED); + } +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + // Release the indicator handle + IndicatorRelease(handle); + } +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() + { + // Retrieve the latest signals + double buySignal = iCustom(NULL, 0, "Easy Buy Sell Signal", 0, 0); + double sellSignal = iCustom(NULL, 0, "Easy Buy Sell Signal", 1, 0); + + // Check for buy signal + if(buySignal > 0) + { + // Open a buy trade + if(trade.Buy(0.1, NULL, 0, 0, 0, "Buy Signal")) + { + Print("Buy order opened successfully"); + } + else + { + Print("Error opening buy order: ", GetLastError()); + } + } + + // Check for sell signal + if(sellSignal > 0) + { + // Open a sell trade + if(trade.Sell(0.1, NULL, 0, 0, 0, "Sell Signal")) + { + Print("Sell order opened successfully"); + } + else + { + Print("Error opening sell order: ", GetLastError()); + } + } + } +//+------------------------------------------------------------------+ diff --git a/EASY_BUY_SELL_SIGNAL_EA.mq5 b/EASY_BUY_SELL_SIGNAL_EA.mq5 new file mode 100644 index 0000000..6387d46 --- /dev/null +++ b/EASY_BUY_SELL_SIGNAL_EA.mq5 @@ -0,0 +1,149 @@ +#include + +// Create an instance of CTrade +CTrade trade; + +// Indicator handle +int handle; + +// Previous signals to track changes +double previousBuySignal = 0; +double previousSellSignal = 0; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + string name = "Market\\Easy Buy Sell Signal.ex5"; + // Initialize the indicator + handle = iCustom(_Symbol, PERIOD_CURRENT, name, 9, 14); + + // Check if the handle is valid + if (handle == INVALID_HANDLE) + { + Print("Failed to initialize indicator. Error: ", GetLastError()); + return(INIT_FAILED); + } + + // Initialize previous signals + previousBuySignal = 0; + previousSellSignal = 0; + + return(INIT_SUCCEEDED); +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + // Release the indicator handle + IndicatorRelease(handle); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + // Retrieve the latest signals + double buySignal = iCustom(_Symbol, PERIOD_CURRENT, handle, 0); + double sellSignal = iCustom(_Symbol, PERIOD_CURRENT, handle, 1); + + // Debugging: Print signal values + Print("Buy Signal: ", buySignal, " Sell Signal: ", sellSignal); + + // Check if there's an existing position + bool positionExists = false; + ENUM_POSITION_TYPE currentPositionType = POSITION_TYPE_BUY; + ulong ticket = 0; + + if (PositionsTotal() > 0) + { + if (PositionSelect(_Symbol)) + { + ticket = PositionGetInteger(POSITION_TICKET); + currentPositionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + positionExists = true; + } + } + + // Trade execution logic based on signal changes + if (buySignal > 0 && buySignal != previousBuySignal) + { + if (positionExists) + { + if (currentPositionType == POSITION_TYPE_SELL) + { + if (trade.PositionClose(ticket)) + { + Print("Sell position closed successfully"); + if (trade.Buy(0.01, NULL, 0, 0, 0, "Buy Signal")) + { + Print("Buy order opened successfully"); + } + else + { + Print("Error opening buy order: ", GetLastError()); + } + } + else + { + Print("Error closing sell position: ", GetLastError()); + } + } + } + else + { + if (trade.Buy(0.01, NULL, 0, 0, 0, "Buy Signal")) + { + Print("Buy order opened successfully"); + } + else + { + Print("Error opening buy order: ", GetLastError()); + } + } + } + else if (sellSignal > 0 && sellSignal != previousSellSignal) + { + if (positionExists) + { + if (currentPositionType == POSITION_TYPE_BUY) + { + if (trade.PositionClose(ticket)) + { + Print("Buy position closed successfully"); + if (trade.Sell(0.01, NULL, 0, 0, 0, "Sell Signal")) + { + Print("Sell order opened successfully"); + } + else + { + Print("Error opening sell order: ", GetLastError()); + } + } + else + { + Print("Error closing buy position: ", GetLastError()); + } + } + } + else + { + if (trade.Sell(0.01, NULL, 0, 0, 0, "Sell Signal")) + { + Print("Sell order opened successfully"); + } + else + { + Print("Error opening sell order: ", GetLastError()); + } + } + } + + // Update previous signals + previousBuySignal = buySignal; + previousSellSignal = sellSignal; +} diff --git a/EmbeddedIndicator.mq5 b/EmbeddedIndicator.mq5 new file mode 100644 index 0000000..259816a --- /dev/null +++ b/EmbeddedIndicator.mq5 @@ -0,0 +1,73 @@ +//+------------------------------------------------------------------+ +//| EmbeddedIndicator.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +input int Reference = 0; + +int handle = 0; + +//+------------------------------------------------------------------+ +//| Helper function to get relative location inside MQL5 folder | +//+------------------------------------------------------------------+ +string GetMQL5Path() +{ + static const string MQL5 = "\\MQL5\\"; + static const int length = StringLen(MQL5) - 1; + static const string path = MQLInfoString(MQL_PROGRAM_PATH); + const int start = StringFind(path, MQL5); + if(start != -1) + { + return StringSubstr(path, start + length); + } + return path; +} + +//+------------------------------------------------------------------+ +//| Indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + Print(Reference); + Print("Name: " + MQLInfoString(MQL_PROGRAM_NAME)); + Print("Full path: " + MQLInfoString(MQL_PROGRAM_PATH)); + + const string location = GetMQL5Path(); + Print("Location in MQL5:" + location); + + if(Reference == 0) + { + handle = iCustom(_Symbol, _Period, location, 1); + if(handle == INVALID_HANDLE) + { + return INIT_FAILED; + } + } + Print("Success"); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Indicator calculation function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Indicator finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + Print("Deinit ", Reference); + IndicatorRelease(handle); +} +//+------------------------------------------------------------------+ diff --git a/EqualVolumeBars.mq5 b/EqualVolumeBars.mq5 new file mode 100644 index 0000000..d3336ee --- /dev/null +++ b/EqualVolumeBars.mq5 @@ -0,0 +1,510 @@ +//+------------------------------------------------------------------+ +//| EqualVolumeBars.mq5 | +//| Copyright В© 2008-2022, MetaQuotes Ltd. | +//| https://www.mql5.com/ | +//+------------------------------------------------------------------+ +#property copyright "Copyright В© 2008-2022, MetaQuotes Ltd." +#property link "https://www.mql5.com/" +#property description "Non-trading EA generating equivolume and/or range bars as a custom symbol.\n" + +#define TICKS_ARRAY 10000 // size of tick buffer + +//+------------------------------------------------------------------+ +//| Supported types of custom charts | +//+------------------------------------------------------------------+ +enum mode +{ + EqualTickVolumes = 0, + EqualRealVolumes = 1, + RangeBars = 2 +}; + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +input mode WorkMode = EqualTickVolumes; +input int TicksInBar = 1000; +input datetime StartDate = 0; // StartDate (default: 30 days back) +input string CustomPath = "MQL5Book\\Part7"; + +const uint DailySeconds = 60 * 60 * 24; +const string Suffixes[] = {"_Eqv", "_Qrv", "_Rng"}; +datetime Start; +string SymbolName; +int BarCount; +bool InitDone = false; + +//+------------------------------------------------------------------+ +//| Virtual time and OHLCTV values of current bar for custom symbol | +//+------------------------------------------------------------------+ +datetime now_time; +double now_close, now_open, now_low, now_high, now_real; +long now_volume; + +//+------------------------------------------------------------------+ +//| Custom symbol reset | +//+------------------------------------------------------------------+ +bool Reset() +{ + int size; + do + { + ResetLastError(); + int deleted = CustomRatesDelete(SymbolName, 0, LONG_MAX); + int err = GetLastError(); + if(err != ERR_SUCCESS) + { + Alert("CustomRatesDelete failed, ", err); + return false; + } + else + { + Print("Rates deleted: ", deleted); + } + + ResetLastError(); + deleted = CustomTicksDelete(SymbolName, 0, LONG_MAX); + if(deleted == -1) + { + Print("CustomTicksDelete failed ", GetLastError()); + return false; + } + else + { + Print("Ticks deleted: ", deleted); + } + + // wait for changes to take effect in the core threads + Sleep(1000); + + MqlTick _array[]; + size = CopyTicks(SymbolName, _array, COPY_TICKS_ALL, 0, 10); + Print("Remaining ticks: ", size); + } + while(size > 0 && !IsStopped()); + // NB. this can not work everytime as expected + // if getting ERR_CUSTOM_TICKS_WRONG_ORDER or similar error - the last resort + // is to wipe out the custom symbol manually from GUI, and then restart this EA + + return size > -1; // success +} + +//+------------------------------------------------------------------+ +//| Process history of real ticks | +//+------------------------------------------------------------------+ +void BuildHistory(const datetime start) +{ + ulong cursor = start * 1000; + uint trap = GetTickCount(); + + Print("Processing tick history..."); + Comment("Processing tick history, this may take a while..."); + TicksBuffer tb; + + while(tb.fill(cursor, true) && !IsStopped()) + { + MqlTick t; + while(tb.read(t)) + { + HandleTick(t, true); + } + } + Comment(""); + + Print("Bar 0: ", now_time, " ", now_volume, " ", now_real); + if(now_volume > 0) + { + // write latest (incomplete) bar to the chart + WriteToChart(now_time, now_open, now_low, now_high, now_close, now_volume, (long)now_real); + + // show stats + Print(BarCount, " bars written in ", (GetTickCount() - trap) / 1000, " sec"); + } + else + { + Alert("No data"); + } +} + +//+------------------------------------------------------------------+ +//| Start from scratch | +//+------------------------------------------------------------------+ +datetime Init(const datetime start) +{ + now_time = start; + now_close = 0; + now_open = 0; + now_low = DBL_MAX; + now_high = 0; + now_volume = 0; + now_real = 0; + return start; +} + +//+------------------------------------------------------------------+ +//| Rough estimation of continuation | +//+------------------------------------------------------------------+ +datetime Resume(const datetime start) +{ + MqlRates rates[2]; + if(CopyRates(SymbolName, PERIOD_M1, 0, 2, rates) != 2) return Init(start); + + ArrayPrint(rates); // tail + + // rescan the last bar + // (but we don't know which tick inside the single minute rates[1].time + // did actually form this equal volume bar) + now_time = rates[1].time; + now_close = rates[1].open; + now_open = rates[1].open; + now_low = rates[1].open; + now_high = rates[1].open; + now_volume = 0; // rates[1].tick_volume; + now_real = 0; // (double)rates[1].real_volume; + + Print("Resuming from ", rates[1].time); + + return rates[1].time; +} + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + InitDone = false; + EventSetTimer(1); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Timer event handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + if(!TerminalInfoInteger(TERMINAL_CONNECTED)) + { + Print("Waiting for connection..."); + return; + } + + if(!SymbolIsSynchronized(_Symbol)) + { + Print("Unsynchronized, skipping ticks..."); + return; + } + + EventKillTimer(); + + BarCount = 0; + Start = StartDate == 0 ? TimeCurrent() - DailySeconds * 30 : StartDate; + SymbolName = _Symbol + Suffixes[WorkMode] + (string)TicksInBar; + + bool justCreated = false; + if(!SymbolSelect(SymbolName, true)) + { + Print("Creating \"", SymbolName, "\""); + + if(!CustomSymbolCreate(SymbolName, CustomPath, _Symbol) + && !SymbolSelect(SymbolName, true)) + { + Alert("Can't select symbol:", SymbolName, " err:", GetLastError()); + return; + } + justCreated = true; + Start = Init(Start); + } + else + { + if(IDYES == MessageBox(SymbolName + " exists. Rebuild?", NULL, MB_YESNO)) + { + Print("Resetting \"", SymbolName, "\""); + if(!Reset()) return; + Start = Init(Start); + } + else + { + // find existing tail of custom quotes to supersede Start + Start = Resume(Start); + } + } + + BuildHistory(Start); + + if(IsStopped()) + { + Print("Interrupted. The custom symbol data is inconsistent - please, delete"); + return; + } + + Print("Open \"", SymbolName, "\" chart to view results"); + + if(justCreated) + { + OpenCustomChart(); + RefreshWindow(now_time); + } + + InitDone = true; +} + +//+------------------------------------------------------------------+ +//| Ticks buffer (read from history by chunks) | +//+------------------------------------------------------------------+ +class TicksBuffer +{ +private: + MqlTick array[]; + int tick; + +public: + bool fill(ulong &cursor, const bool history = false) + { + int size = history ? CopyTicks(_Symbol, array, COPY_TICKS_ALL, cursor, TICKS_ARRAY) : + CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, cursor); + if(size == -1) + { + Print("CopyTicks failed: ", _LastError); + return false; + } + else if(size == 0) + { + if(history) + { + Print("End of CopyTicks at ", (datetime)(cursor / 1000), " ", _LastError); + } + return false; + } + + if((ulong)array[0].time_msc < cursor) + { + Print("Tick rewind bug, ", (datetime)(cursor / 1000)); + return false; + } + cursor = array[size - 1].time_msc + 1; + tick = 0; + + return true; + } + + bool read(MqlTick &t) + { + if(tick < ArraySize(array)) + { + t = array[tick++]; + return true; + } + return false; + } +}; + +//+------------------------------------------------------------------+ +//| Helper function to open custom symbol chart | +//+------------------------------------------------------------------+ +void OpenCustomChart() +{ + const long id = ChartOpen(SymbolName, PERIOD_M1); + if(id == 0) + { + Alert("Can't open new chart for ", SymbolName, ", code: ", _LastError); + } + else + { + Sleep(1000); + ChartSetSymbolPeriod(id, SymbolName, PERIOD_M1); + ChartSetInteger(id, CHART_MODE, CHART_CANDLES); + } +} + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + Comment(""); +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(!InitDone) return; + + static ulong cursor = 0; + MqlTick t; + + if(cursor == 0) + { + if(SymbolInfoTick(_Symbol, t)) + { + HandleTick(t); + cursor = t.time_msc + 1; + } + } + else + { + TicksBuffer tb; + while(tb.fill(cursor)) + { + while(tb.read(t)) + { + HandleTick(t); + } + } + } + + RefreshWindow(now_time); +} + +//+------------------------------------------------------------------+ +//| Process incoming ticks one by one | +//+------------------------------------------------------------------+ +inline void HandleTick(const MqlTick &t, const bool history = false) +{ + now_volume++; + now_real += t.volume_real; + // (long)t.volume; // NB: use 'long volume' to eliminate floating point error accumulation + const double bid = t.last != 0 ? t.last : t.bid; + + if(!IsNewBar()) // bar continues + { + if(bid < now_low) now_low = bid; + if(bid > now_high) now_high = bid; + now_close = bid; + + if(!history) + { + // write bar 0 to chart (-1 for volume stands for upcoming refresh) + WriteToChart(now_time, now_open, now_low, now_high, now_close, now_volume - !history, (long)now_real); + } + } + else // new bar tick + { + do + { + if(history) + { + BarCount++; + + if((BarCount % 10) == 0) + { + Comment(t.time, " -> ", now_time, " [", BarCount, "]"); + } + } + else + { + Comment("Complete bar: ", now_time); + } + + if(WorkMode == RangeBars) + { + FixRange(); + } + // write bar 1 + WriteToChart(now_time, now_open, now_low, now_high, now_close, + WorkMode == EqualTickVolumes ? TicksInBar : now_volume, + WorkMode == EqualRealVolumes ? TicksInBar : (long)now_real); + + // normalize down to a minute + datetime time = t.time / 60 * 60; + + // eliminate bars with equal or too old times + if(time <= now_time) time = now_time + 60; + + now_time = time; + now_open = bid; + now_low = bid; + now_high = bid; + now_close = bid; + now_volume = 1; + if(WorkMode == EqualRealVolumes) now_real -= TicksInBar; + + // write bar 0 (-1 for volume stands for upcoming refresh) + WriteToChart(now_time, now_open, now_low, now_high, now_close, now_volume - !history, (long)now_real); + } + while(IsNewBar() && WorkMode == EqualRealVolumes); + } +} + +//+------------------------------------------------------------------+ +//| Simulate new tick on custom symbol chart | +//+------------------------------------------------------------------+ +void RefreshWindow(const datetime t) +{ + MqlTick ta[1]; + SymbolInfoTick(_Symbol, ta[0]); + ta[0].time = t; + ta[0].time_msc = t * 1000; + if(CustomTicksAdd(SymbolName, ta) == -1) // NB! this call may increment number of ticks per bar + { + Print("CustomTicksAdd failed:", _LastError, " ", (long) ta[0].time); + ArrayPrint(ta); + } +} + +//+------------------------------------------------------------------+ +//| Add bar (MqlRates element) to custom symbol chart | +//+------------------------------------------------------------------+ +void WriteToChart(datetime t, double o, double l, double h, double c, long v, long m = 0) +{ + MqlRates r[1]; + + r[0].time = t; + r[0].open = o; + r[0].low = l; + r[0].high = h; + r[0].close = c; + r[0].tick_volume = v; + r[0].spread = 0; + r[0].real_volume = m; + + if(CustomRatesUpdate(SymbolName, r) < 1) + { + Print("CustomRatesUpdate failed: ", _LastError); + } +} + +//+------------------------------------------------------------------+ +//| Check condition for new virtual bar formation according to mode | +//+------------------------------------------------------------------+ +bool IsNewBar() +{ + if(WorkMode == EqualTickVolumes) + { + if(now_volume > TicksInBar) return true; + } + else if(WorkMode == EqualRealVolumes) + { + if(now_real > TicksInBar) return true; + } + else if(WorkMode == RangeBars) + { + if((now_high - now_low) / _Point > TicksInBar) return true; + } + + return false; +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void FixRange() +{ + const int excess = (int)((now_high + (_Point / 2)) / _Point) + - (int)((now_low + (_Point / 2)) / _Point) - TicksInBar; + if(excess > 0) + { + if(now_close > now_open) + { + now_high -= excess * _Point; + if(now_high < now_close) now_close = now_high; + } + else if(now_close < now_open) + { + now_low += excess * _Point; + if(now_low > now_close) now_close = now_low; + } + } +} +//+------------------------------------------------------------------+ diff --git a/ExpertEvents.mq5 b/ExpertEvents.mq5 new file mode 100644 index 0000000..4965690 --- /dev/null +++ b/ExpertEvents.mq5 @@ -0,0 +1,78 @@ +//+------------------------------------------------------------------+ +//| ExpertEvents.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Display events of common types in multiline comments." + +#define N_LINES 25 +#include + +//+------------------------------------------------------------------+ +//| Custom multiline comment | +//+------------------------------------------------------------------+ +void Display(const string message) +{ + ChronoComment((string)GetTickCount() + ": " + message); +} + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +void OnInit() +{ + Print(__FUNCTION__); + EventSetTimer(2); + if(!MarketBookAdd(_Symbol)) + { + Print("MarketBookAdd failed:", _LastError); + } +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + Display(__FUNCTION__); +} + +//+------------------------------------------------------------------+ +//| Timer event handler | +//+------------------------------------------------------------------+ +void OnTimer() +{ + Display(__FUNCTION__); +} + +//+------------------------------------------------------------------+ +//| Market book handler | +//+------------------------------------------------------------------+ +void OnBookEvent(const string &symbol) +{ + if(symbol == _Symbol) // process only book for requested symbol + { + Display(__FUNCTION__); + } +} + +//+------------------------------------------------------------------+ +//| Chart event handler | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + Display(__FUNCTION__); +} + +//+------------------------------------------------------------------+ +//| Finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + Print(__FUNCTION__); + MarketBookRelease(_Symbol); + Comment(""); +} +//+------------------------------------------------------------------+ diff --git a/FaultyIndicator.mq5 b/FaultyIndicator.mq5 new file mode 100644 index 0000000..44f92c2 --- /dev/null +++ b/FaultyIndicator.mq5 @@ -0,0 +1,45 @@ +//+------------------------------------------------------------------+ +//| FaultyIndicator.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +#resource "SubFolder\\NonEmbeddedIndicator.ex5" + +int handle; + +//+------------------------------------------------------------------+ +//| Indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + handle = iCustom(_Symbol, _Period, "::SubFolder\\NonEmbeddedIndicator.ex5"); + if(handle == INVALID_HANDLE) + { + return INIT_FAILED; + } + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Indicator calculation function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Indicator finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + IndicatorRelease(handle); +} +//+------------------------------------------------------------------+ diff --git a/FrameTransfer.mq5 b/FrameTransfer.mq5 new file mode 100644 index 0000000..d00fd6d --- /dev/null +++ b/FrameTransfer.mq5 @@ -0,0 +1,208 @@ +//+------------------------------------------------------------------+ +//| FrameTransfer.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Example of sending and receiving data frames during EA optimization." + +#property tester_set "FrameTransfer.set" +#property tester_no_cache + +#include + +input bool Parameter0; +input long Parameter1; +input double Parameter2; +input string Parameter3; + +#define MY_FILE_ID 100 +#define MY_TIME_ID 101 + +ulong startup; // single pass timing (just a demo data) +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + startup = GetMicrosecondCount(); + MathSrand((uint)startup); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Test completion event handler | +//+------------------------------------------------------------------+ +double OnTester() +{ + // send a file in one frame + const static string filename = "binfile"; + int h = FileOpen(filename, FILE_WRITE | FILE_BIN | FILE_ANSI); + FileWriteString(h, StringFormat("Random: %d", MathRand())); + FileClose(h); + FrameAdd(filename, MY_FILE_ID, MathRand(), filename); + + // send an array in another frame + ulong dummy[1]; + dummy[0] = GetMicrosecondCount() - startup; + FrameAdd("timing", MY_TIME_ID, 0, dummy); + + return (Parameter2 + 1) * (Parameter1 + 2); +} + +//+------------------------------------------------------------------+ +//| Adjust optimization range to the limit (to simplify the demo) | +//+------------------------------------------------------------------+ +template +void LimitParameterCount(const string name, const int limit) +{ + bool enabled; + T value, start, step, stop; + if(ParameterGetRange(name, enabled, value, start, step, stop)) + { + if(enabled && step > 0) + { + if((stop - start) / step > limit) + { + ParameterSetRange(name, enabled, value, start, step, start + step * limit); + } + } + } +} + +int handle; // a file to collect all custom results + +//+------------------------------------------------------------------+ +//| Optimization start | +//+------------------------------------------------------------------+ +void OnTesterInit() +{ + handle = FileOpen("output.csv", FILE_WRITE | FILE_CSV | FILE_ANSI, ","); + LimitParameterCount("Parameter1", 10); + LimitParameterCount("Parameter2", 10); +} + +//+------------------------------------------------------------------+ +//| Receive data with MY_FILE_ID | +//+------------------------------------------------------------------+ +void ProcessFileFrames() +{ + static ulong framecount = 0; // our own counter of frame count + + // frame fields + ulong pass; + string name; + long id; + double value; + // when the array is used to read a file from a frame, + // the datatype of the array must have a size that + // the file size is divisible by it without a remainder, + // so most universal types are: + // - uchar for binary files and ANSI text files + // - ushort for text files in Unicode + // otherwise you'll get INVALID_ARRAY(4006) error + uchar data[]; + + // input parameters for the pass where the frame belongs + string params[]; + uint count; + + ResetLastError(); + + while(FrameNext(pass, name, id, value, data)) + { + PrintFormat("Pass: %lld Frame: %s Value:%f", pass, name, value); + if(id != MY_FILE_ID) continue; + if(FrameInputs(pass, params, count)) + { + string header, record; + if(framecount == 0) // prepare CSV header + { + header = "Counter,Pass ID,"; + } + record = (string)framecount + "," + (string)pass + ","; + // let collect values of optimized parameters + for(uint i = 0; i < count; i++) + { + string name2value[]; + int n = StringSplit(params[i], '=', name2value); + if(n == 2) + { + long pvalue, pstart, pstep, pstop; + bool enabled = false; + if(ParameterGetRange(name2value[0], enabled, pvalue, pstart, pstep, pstop)) + { + if(enabled) + { + if(framecount == 0) // prepare CSV header + { + header += name2value[0] + ","; + } + record += name2value[1] + ","; // data field + } + } + } + } + if(framecount == 0) // prepare CSV header + { + FileWriteString(handle, header + "Value,File Content\n"); + } + // write data records into CSV + FileWriteString(handle, record + DoubleToString(value) + "," + + CharArrayToString(data) + "\n"); + } + framecount++; + } + + if(_LastError != 4000 && _LastError != 0) + { + Print("Error: ", E2S(_LastError)); + } +} + +//+------------------------------------------------------------------+ +//| Optimization pass (frame) | +//+------------------------------------------------------------------+ +void OnTesterPass() +{ + ProcessFileFrames(); // standard processing of frames +} + +//+------------------------------------------------------------------+ +//| End of optimization | +//+------------------------------------------------------------------+ +void OnTesterDeinit() +{ + ProcessFileFrames(); // final clean-up: some frames may be late + FileClose(handle); // close CSV-file + + ulong pass; + string name; + long id; + double value; + ulong data[]; // use the same datatype as it was at sending of the array + + FrameFilter("timing", MY_TIME_ID); // rewind to first frames + + ulong count = 0; + ulong total = 0; + // loop through 'timing' frames only + while(FrameNext(pass, name, id, value, data)) + { + if(ArraySize(data) == 1) + { + total += data[0]; + } + else + { + total += (ulong)value; + } + ++count; + } + if(count > 0) + { + PrintFormat("Average timing: %lld", total / count); + } +} +//+------------------------------------------------------------------+ diff --git a/Indices Tester.mq5 b/Indices Tester.mq5 new file mode 100644 index 0000000..1dd6497 Binary files /dev/null and b/Indices Tester.mq5 differ diff --git a/KA-Gold Bot.mq5 b/KA-Gold Bot.mq5 new file mode 100644 index 0000000..8e007fb Binary files /dev/null and b/KA-Gold Bot.mq5 differ diff --git a/KeyboardSpy.mq5 b/KeyboardSpy.mq5 new file mode 100644 index 0000000..b067403 --- /dev/null +++ b/KeyboardSpy.mq5 @@ -0,0 +1,58 @@ +//+------------------------------------------------------------------+ +//| KeyboardSpy.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property description "Intercepts interactive keyboard presses and sends notification about them into specified chart 'HostID' using 'EventID' event.\n" +#property description "Allows for control another MQL-programm running on an inactive chart (which are not receiving keyboard events)." +#property indicator_chart_window +#property indicator_plots 0 + +input long HostID; +input ushort EventID; + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + Print("init ", ChartID()); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Custom indicator iteration function | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Chart event handler | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + if(id == CHARTEVENT_KEYDOWN) + { + // NB: MT5 limitation: TerminalInfoInteger(TERMINAL_KEYSTATE_) does not work + // in indicators created by iCustom/IndicatorCreate, that is + // the function return 0 always for all keys, so we can't detect + // Ctrl/Shift and other key states and use symbol alphanumeric keys + EventChartCustom(HostID, EventID, lparam, + (double)(ushort)TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL), // this is always 0 inside iCustom + sparam); + } +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + Print("deinit ", ChartID()); +} +//+------------------------------------------------------------------+ diff --git a/Lession 1_ Bullish and Bearllish Candle.mq5 b/Lession 1_ Bullish and Bearllish Candle.mq5 new file mode 100644 index 0000000..55194cf --- /dev/null +++ b/Lession 1_ Bullish and Bearllish Candle.mq5 @@ -0,0 +1,300 @@ +//+------------------------------------------------------------------+ +//| Lession 1_ Bullish and Bearllish Candle.mq5 | +//| Copyright 2023, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2023, ThanhTan_ProVlog" +#property link "https://www.mql5.com" +#property version "1.00" +#include ; +#include ; +#include ; + +CTrade trade; +CSymbolInfo m_symbol; +CPositionInfo m_position; +string input aa = "-----------------------SETTINGS---------------------------"; +string input BOT_NAME = "Bullish and Bearllish Candle"; +// Input parameter declaration +input ENUM_TIMEFRAMES Trading_timframe= PERIOD_M5; +input double lot_size=0.002;// Lot sise +input double SL_Factor= 600; // Stop loss factor +input double TP_Factor=6000; // Take profit factor +input double Trailling= 300;// Trailling Pipi +input double Trailling_Step=5;// Trailling step +input ulong m_magic=123456789;// magic number + +//+------------------------------------------------------------------+ +//| Global variable declaration. | +//+------------------------------------------------------------------+ +double Extstoploss=0; +double Exttakeprofit=0; +double ExtTraill_Stop=0.0; +double ExtTraill_Step=0.0; +double m_adjustpoint=0; +ulong slippage=10; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { +//--- +if(!m_symbol.Name(Symbol())) + return INIT_FAILED; + + trade.SetExpertMagicNumber(m_magic); + trade.SetTypeFillingBySymbol(m_symbol.Name()); + trade.SetDeviationInPoints(slippage); + + // Retur 3 || 5 digit + int digit_adjust=1; + if(m_symbol.Digits()==3 || m_symbol.Digits()==5) + digit_adjust=10; + + m_adjustpoint=digit_adjust*m_symbol.Point(); + + Extstoploss=m_adjustpoint*SL_Factor; + Exttakeprofit= m_adjustpoint*TP_Factor; + ExtTraill_Stop=m_adjustpoint*Trailling; + ExtTraill_Step=m_adjustpoint*Trailling_Step; +//--- + return(INIT_SUCCEEDED); + } +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { +//--- + + } +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() + { +//+------------------------------------------------------------------+ +//|Candle declaration | +//+------------------------------------------------------------------+ + +double Open[],Close[],High[],Low[]; +ArraySetAsSeries(Open,true) ; ArraySetAsSeries(Close,true) ; +ArraySetAsSeries(High,true) ; ArraySetAsSeries(Low,true); +CopyOpen(Symbol(),Trading_timframe,0,1000,Open); +CopyClose(Symbol(),Trading_timframe,0,1000,Close); +CopyHigh(Symbol(),Trading_timframe,0,1000,High); +CopyLow(Symbol(),Trading_timframe,0,1000,Low); + +//+------------------------------------------------------------------+ +//|Count buy & count sell & trailling declaration | +//+------------------------------------------------------------------+ + +int count_buy=0; int count_sell=0; +count_position(count_buy,count_sell); + +//+------------------------------------------------------------------+ +//|Main Trading function | +//+------------------------------------------------------------------+ + if(Open_bar(Symbol(),Trading_timframe))// Only buy or sell at new candle + { + if(count_buy==0 && count_sell==0) // Only buy at no longer position + { + if(bullish(Open,Close,High,Low,1)) + { + double entryprice = SymbolInfoDouble(Symbol(),SYMBOL_ASK); + double sl = entryprice-Extstoploss; + double tp =entryprice+Exttakeprofit; + double max_lot= SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); + + if(CheckVolumeValue(lot_size)) + { + trade.Buy(lot_size,Symbol(),entryprice,sl,tp," Buy Mr.Tan 0964299486 "); + } + + + } + + } + if(count_sell==0 && count_buy==0)// Only sell at no longer position + { + if(bearlish(Open,Close,High,Low,1)) + { + double entryprice = SymbolInfoDouble(Symbol(),SYMBOL_BID); + double sl = entryprice+Extstoploss; + double tp =entryprice-Exttakeprofit; + double max_lot= SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); + + if(CheckVolumeValue(lot_size)) + { + trade.Sell(lot_size,Symbol(),entryprice,sl,tp," Sell Mr.Tan 0964299486 "); + } + + } + + } + } + + } + +//+------------------------------------------------------------------+ +//|Count position and Trailling Functiom | +//+------------------------------------------------------------------+ + +void count_position(int &count_buy, int &count_sell) + + { + count_buy=0; count_sell=0; + int total_postion=PositionsTotal(); + double cp=0.0, op=0.0, sl=0.0,tp=0.0; ulong ticket=0.0; + for ( int i=total_postion-1; i>=0; i--) + { + if(m_position.SelectByIndex(i)) + { + if(m_position.Symbol()==m_symbol.Name() && m_position.Magic()== m_magic) + cp=m_position.PriceCurrent();op=m_position.PriceOpen();sl=m_position.StopLoss();tp=m_position.TakeProfit();ticket=m_position.Ticket(); + { + if(m_position.PositionType()== POSITION_TYPE_BUY) + { + count_buy++; + double Traill= cp- ExtTraill_Stop; + if(cp>sl+ExtTraill_Step && Traill>sl&& PositionModifyCheck(ticket,Traill,tp,_Symbol)) + { + trade.PositionModify(ticket,Traill,tp); + } + } + else + if(m_position.PositionType()== POSITION_TYPE_SELL) + { + count_sell++; + double Traill= cp+ ExtTraill_Stop; + if(cpopen[index] && close[index]>(close[index+1]+midle_candle)) + return true; + } + return false; + + } + +//+------------------------------------------------------------------+ +//|Bearllish detected | +//+------------------------------------------------------------------+ + + bool bearlish(double &open[], double &close[], double &high[], double &low[], int index) + + { + double midle_candle= MathAbs((close[index+1]-open[index+1])/2); + bool uptrend =(close[index+1]>open[index+1] && close[index+2]>open[index+2] && close[index+3]>open[index+3]); + + if(uptrend) + { + if(close[index]point); + //--- if there are any changes in levels + if(StopLossChanged)// || TakeProfitChanged) + return(true); // position can be modified + //--- there are no changes in the StopLoss and Takeprofit levels + else + //--- notify about the error + PrintFormat("Order #%d already has levels of Open=%.5f SL=.5f TP=%.5f", + ticket,order.StopLoss(),order.TakeProfit()); + } + } +//--- came to the end, no changes for the order + return(false); // no point in modifying + } + +//+------------------------------------------------------------------+ +//| Check the correctness of the order volume | +//+------------------------------------------------------------------+ +bool CheckVolumeValue(double volume) { + +//--- minimal allowed volume for trade operations + double min_volume=SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + if(volume < min_volume) + { + //description = StringFormat("Volume is less than the minimal allowed SYMBOL_VOLUME_MIN=%.2f",min_volume); + return(false); + } + +//--- maximal allowed volume of trade operations + double max_volume=SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + if(volume>max_volume) + { + //description = StringFormat("Volume is greater than the maximal allowed SYMBOL_VOLUME_MAX=%.2f",max_volume); + return(false); + } + +//--- get minimal step of volume changing + double volume_step=SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + + int ratio = (int)MathRound(volume/volume_step); + if(MathAbs(ratio*volume_step-volume)>0.0000001) + { + //description = StringFormat("Volume is not a multiple of the minimal step SYMBOL_VOLUME_STEP=%.2f, the closest correct volume is %.2f", volume_step,ratio*volume_step); + return(false); + } + + return(true); +} + diff --git a/Lesson 2_ Trading RSI Pattern Confirmation two Bottom.mq5 b/Lesson 2_ Trading RSI Pattern Confirmation two Bottom.mq5 new file mode 100644 index 0000000..6fc7528 Binary files /dev/null and b/Lesson 2_ Trading RSI Pattern Confirmation two Bottom.mq5 differ diff --git a/Lesson 3_ Price Action.mq5 b/Lesson 3_ Price Action.mq5 new file mode 100644 index 0000000..8b18655 Binary files /dev/null and b/Lesson 3_ Price Action.mq5 differ diff --git a/Lesson 4_ Price Action & MA Filter.mq5 b/Lesson 4_ Price Action & MA Filter.mq5 new file mode 100644 index 0000000..a2757a0 Binary files /dev/null and b/Lesson 4_ Price Action & MA Filter.mq5 differ diff --git a/Lesson 6 Trailling ATR.mq5 b/Lesson 6 Trailling ATR.mq5 new file mode 100644 index 0000000..933c556 Binary files /dev/null and b/Lesson 6 Trailling ATR.mq5 differ diff --git a/Lesson 7 Price action Ket hop Volume VSA.mq5 b/Lesson 7 Price action Ket hop Volume VSA.mq5 new file mode 100644 index 0000000..4e556f6 Binary files /dev/null and b/Lesson 7 Price action Ket hop Volume VSA.mq5 differ diff --git a/Lesson 8 how to Buy stop.mq5 b/Lesson 8 how to Buy stop.mq5 new file mode 100644 index 0000000..749d40d Binary files /dev/null and b/Lesson 8 how to Buy stop.mq5 differ diff --git a/LibClipboard.mq5 b/LibClipboard.mq5 new file mode 100644 index 0000000..b7a614d --- /dev/null +++ b/LibClipboard.mq5 @@ -0,0 +1,48 @@ +//+------------------------------------------------------------------+ +//| LibClipboard.mq5 | +//| Copyright 2021-2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| DLL-related permissions are required! | +//| Clipboard reading is the task implemented by DLLs. | +//+------------------------------------------------------------------+ +#include +#include + +//+------------------------------------------------------------------+ +//| We need this define and import for accessing Windows clipboard | +//+------------------------------------------------------------------+ +#define CF_UNICODETEXT 13 // one of standard clipboard formats +#import "kernel32.dll" +string lstrcatW(PVOID string1, const string string2); +#import + +//+------------------------------------------------------------------+ +//| Example function to use DLL for reading Windows clipboard | +//+------------------------------------------------------------------+ +void ReadClipboard() +{ + if(OpenClipboard(NULL)) + { + HANDLE h = GetClipboardData(CF_UNICODETEXT); + PVOID p = GlobalLock(h); + if(p != 0) + { + const string text = lstrcatW(p, ""); + Print("Clipboard: ", text); + GlobalUnlock(h); + } + CloseClipboard(); + } +} + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + ReadClipboard(); +} +//+------------------------------------------------------------------+ diff --git a/LibHoughChannel.mq5 b/LibHoughChannel.mq5 new file mode 100644 index 0000000..a311513 --- /dev/null +++ b/LibHoughChannel.mq5 @@ -0,0 +1,202 @@ +//+------------------------------------------------------------------+ +//| LibHoughChannel.mq5 | +//| Copyright (c) 2015-2022, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2015-2022, Marketeer" +#property link "https://www.mql5.com/en/users/marketeer" +#property version "1.0" +#property description "Create 2+ trend lines on highs and lows using Hough transform." + +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +//#define LIB_HOUGH_IMPL_DEBUG +#include +#include + +//+------------------------------------------------------------------+ +//| I N P U T S | +//+------------------------------------------------------------------+ +input int BarOffset = 0; +input int BarCount = 21; +input int MaxLines = 3; + +//+------------------------------------------------------------------+ +//| Custom implementation of HoughImage based on quotes | +//+------------------------------------------------------------------+ +class HoughQuotes: public HoughImage +{ +public: + enum PRICE_LINE + { + HighLow = 0, // Bar Range |High..Low| + OpenClose = 1, // Bar Body |Open..Close| + LowLow = 2, // Bar Lows + HighHigh = 3, // Bar Highs + }; + +protected: + int size; + int offset; + int step; + double base; + PRICE_LINE type; + +public: + HoughQuotes(int startbar, int barcount, PRICE_LINE price) + { + offset = startbar; + size = barcount; + type = price; + int hh = iHighest(NULL, 0, MODE_HIGH, size, startbar); + int ll = iLowest(NULL, 0, MODE_LOW, size, startbar); + int pp = (int)((iHigh(NULL, 0, hh) - iLow(NULL, 0, ll)) / _Point); + step = pp / size; + base = iLow(NULL, 0, ll); + } + + virtual int getWidth() const override + { + return size; + } + + virtual int getHeight() const override + { + return size; + } + + virtual int get(int x, int y) const override + { + if(offset + x >= iBars(NULL, 0)) return 0; + + const double price = convert(y); + if(type == HighLow) + { + if(price >= iLow(NULL, 0, offset + x) && price <= iHigh(NULL, 0, offset + x)) + { + return 1; + } + } + else if(type == OpenClose) + { + if(price >= fmin(iOpen(NULL, 0, offset + x), iClose(NULL, 0, offset + x)) + && price <= fmax(iOpen(NULL, 0, offset + x), iClose(NULL, 0, offset + x))) + { + return 1; + } + } + else if(type == LowLow) + { + if(iLow(NULL, 0, offset + x) >= price - step * _Point / 2 + && iLow(NULL, 0, offset + x) <= price + step * _Point / 2) + { + return 1; + } + } + else if(type == HighHigh) + { + if(iHigh(NULL, 0, offset + x) >= price - step * _Point / 2 + && iHigh(NULL, 0, offset + x) <= price + step * _Point / 2) + { + return 1; + } + } + return 0; + } + + // index by Y (quantized) -> continuous value (such as price) + double convert(const double y) const + { + return base + y * step * _Point; + } +}; + +//+------------------------------------------------------------------+ +//| G L O B A L S | +//+------------------------------------------------------------------+ +const string Prefix = "HoughChannel-"; +HoughTransform *ht; + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + ht = createHoughTransform(BarCount); + HoughInfo info = getHoughInfo(); + Print(info.dimension, " per ", info.about); + return CheckPointer(ht) != POINTER_INVALID ? INIT_SUCCEEDED : INIT_FAILED; +} + +//+------------------------------------------------------------------+ +//| Custom indicator iteration function | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + static datetime now = 0; + if(now != iTime(NULL, 0, 0)) + { + HoughQuotes highs(BarOffset, BarCount, HoughQuotes::HighHigh); + HoughQuotes lows(BarOffset, BarCount, HoughQuotes::LowLow); + static double result[]; + int n; + n = ht.transform(highs, result, fmin(MaxLines, 5)); + if(n) + { + for(int i = 0; i < n; ++i) + { + DrawLine(highs, Prefix + "Highs-" + (string)i, result[i * 2 + 0], result[i * 2 + 1], clrBlue, 5 - i); + } + } + else + { + Print("No solution for Highs"); + } + n = ht.transform(lows, result, fmin(MaxLines, 5)); + if(n) + { + for(int i = 0; i < n; ++i) + { + DrawLine(lows, Prefix + "Lows-" + (string)i, result[i * 2 + 0], result[i * 2 + 1], clrRed, 5 - i); + } + } + else + { + Print("No solution for Lows"); + } + now = iTime(NULL, 0, 0); + } + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Single line visual presenter | +//+------------------------------------------------------------------+ +void DrawLine(HoughQuotes "es, const string name, const double a, const double b, + const color clr, const int width) +{ + ObjectCreate(0, name, OBJ_TREND, 0, 0, 0); + ObjectSetInteger(0, name, OBJPROP_TIME, 0, iTime(NULL, 0, BarOffset + BarCount - 1)); + ObjectSetDouble(0, name, OBJPROP_PRICE, 0, quotes.convert(a * (BarCount - 1) + b)); + + ObjectSetInteger(0, name, OBJPROP_TIME, 1, iTime(NULL, 0, BarOffset)); + ObjectSetDouble(0, name, OBJPROP_PRICE, 1, quotes.convert(a * 0 + b)); + + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); + ObjectSetInteger(0, name, OBJPROP_WIDTH, width); +} + +//+------------------------------------------------------------------+ +//| Finalization handler | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + AutoPtr destructor(ht); + ObjectsDeleteAll(0, Prefix); +} +//+------------------------------------------------------------------+ diff --git a/LibHoughTransform.mq5 b/LibHoughTransform.mq5 new file mode 100644 index 0000000..1f634fc --- /dev/null +++ b/LibHoughTransform.mq5 @@ -0,0 +1,227 @@ +//+------------------------------------------------------------------+ +//| LibHoughTransform.mq5 | +//| Copyright (c) 2015-2022, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//+------------------------------------------------------------------+ +#property library + +#include + +//+------------------------------------------------------------------+ +//| Aux 2D-array class | +//+------------------------------------------------------------------+ +template +class Plain2DArray +{ +protected: + T subarray[]; + int sizex, sizey; + +public: + Plain2DArray() {}; + Plain2DArray(int x, int y) + { + allocate(x, y); + } + + void allocate(int x, int y) + { + sizex = x; + sizey = y; + ArrayResize(subarray, x * y); + zero(); + } + + T get(int x, int y) const + { + return subarray[x + y * sizex]; + } + + void set(int x, int y, T v) + { + subarray[x + y * sizex] = v; + } + + void inc(int x, int y, T a = (T)1) + { + subarray[x + y * sizex] += a; + } + + void getSizes(int &x, int &y) const + { + x = sizex; + y = sizey; + } + + bool isAllocated() const + { + return ArraySize(subarray) > 0; + } + + void zero() + { + ZeroMemory(subarray); + } +}; + +//+------------------------------------------------------------------+ +//| Main worker class for Linear Hough Transfrom | +//+------------------------------------------------------------------+ +template +class LinearHoughTransform: public HoughTransformConcrete +{ +protected: + int size; + Plain2DArray data; + Plain2DArray trigonometric; + + void init() + { + data.allocate(size, size); + trigonometric.allocate(2, size); + double t, d = M_PI / size; + int i; + for(i = 0, t = 0; i < size; i++, t += d) + { + trigonometric.set(0, i, MathCos(t)); + trigonometric.set(1, i, MathSin(t)); + } + } + + virtual bool findMax(int &x, int &y) + { + T max = (T)0; + bool done = false; + for(int r = 0; r < size; r++) + { + for(int i = 0; i < size; i++) + { + if(data.get(r, i) > max) + { + max = data.get(r, i); + x = r; + y = i; + done = true; + } + } + } + + if(done) + for(int r = -1; r < 2; r++) + { + for(int i = -1; i < 2; i++) + { + if(x + r >= 0 && y + i >= 0 && x + r < size && y + i < size) + { + data.set(x + r, y + i, 0); + } + } + } + return done; + } + +public: + LinearHoughTransform(const int quants): size(quants) + { + init(); + } + + // 2 params per line, 8 lines means 16 parameters + virtual int extract(const HoughImage &image, double &result[], const int lines = 8) override + { + ArrayResize(result, lines * 2); + ArrayInitialize(result, 0); + data.zero(); + + const int w = image.getWidth(); + const int h = image.getHeight(); + const double d = M_PI / size; // 180 / 36 = 5 degree, for example + const double rstep = MathSqrt(w * w + h * h) / size; + double r, t; + int i; + + // find straight lines + for(int x = 0; x < w; x++) + { + for(int y = 0; y < h; y++) + { + T v = image.get(x, y); + if(v == (T)0) continue; + + for(i = 0, t = 0; i < size; i++, t += d) // t < Math.PI + { + r = (x * trigonometric.get(0, i) + y * trigonometric.get(1, i)); + r = MathRound(r / rstep); // range is [-size, +size] + r += size; // [0, +2size] + r /= 2; + + if((int)r < 0) r = 0; + if((int)r >= size) r = size - 1; + if(i < 0) i = 0; + if(i >= size) i = size - 1; + + data.inc((int)r, i, v); + } + } + } + + // y = a * x + b + // y = (-cos(t)/sin(t)) * x + (r/sin(t)) + + // save 8 lines as features (2 params per line) + for(i = 0; i < lines; i++) + { + int x, y; + if(!findMax(x, y)) + { + return i; + } + + double a = 0, b = 0; + if(MathSin(y * d) != 0) + { + a = -1.0 * MathCos(y * d) / MathSin(y * d); + b = (x * 2 - size) * rstep / MathSin(y * d); + } + if(fabs(a) < DBL_EPSILON && fabs(b) < DBL_EPSILON) + { + i--; + continue; + } + result[i * 2 + 0] = a; + result[i * 2 + 1] = b; + } + + return i; + } +}; + +//+------------------------------------------------------------------+ +//| Fabric function for Hough transform objects | +//+------------------------------------------------------------------+ +HoughTransform *createHoughTransform(const int quants, const ENUM_DATATYPE type = TYPE_INT) export +{ + switch(type) + { + case TYPE_INT: + return new LinearHoughTransform(quants); + case TYPE_DOUBLE: + return new LinearHoughTransform(quants); + // ... more types can be supported + } + return NULL; +} + +//+------------------------------------------------------------------+ +//| Built-in meta-information about implemented transform | +//+------------------------------------------------------------------+ +HoughInfo getHoughInfo() export +{ +#ifdef LIB_HOUGH_IMPL_DEBUG + Print("inline library (debug)"); +#else + Print("standalone library (production)"); +#endif + return HoughInfo(2, "Line: y = a * x + b; a = p[0]; b = p[1];"); +} +//+------------------------------------------------------------------+ diff --git a/LibRand.mq5 b/LibRand.mq5 new file mode 100644 index 0000000..e349050 --- /dev/null +++ b/LibRand.mq5 @@ -0,0 +1,101 @@ +//+------------------------------------------------------------------+ +//| LibRand.mq5 | +//| Copyright 2021-2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property library + +#include + +string StringPatternAlpha(const STRING_PATTERN _case = STRING_PATTERN_MIXEDCASE) export +{ + string result = ""; + static const short delta = 'A' - 'a'; + for(short i = 'a'; i <= 'z'; ++i) + { + if((bool)(_case & STRING_PATTERN_LOWERCASE)) + result += ShortToString((ushort)(i)); + if((bool)(_case & STRING_PATTERN_UPPERCASE)) + result += ShortToString((ushort)(i + delta)); + } + return result; +} + +string StringPatternDigit() export +{ + string result = ""; + for(short i = '0'; i <= '9'; ++i) + { + result += ShortToString(i); + } + return result; +} + +string RandomString(const int length, string pattern = NULL) export +{ + if(StringLen(pattern) == 0) + { + pattern = StringPatternAlpha() + StringPatternDigit(); + } + const int size = StringLen(pattern); + string result = ""; + for(int i = 0; i < length; ++i) + { + result += ShortToString(pattern[rand() % size]); + } + return result; +} + +void RandomStrings(string &array[], const int n, const int minlength, const int maxlength, string pattern = NULL) export +{ + if(StringLen(pattern) == 0) + { + pattern = StringPatternAlpha() + StringPatternDigit(); + } + ArrayResize(array, n); + for(int j = 0; j < n; ++j) + { + array[j] = RandomString(rand() * (maxlength - minlength) / 32768 + minlength, pattern); + } +} + +double DefaultMean = 0.0; +double DefaultSigma = 1.0; + +void PseudoNormalDefaultMean(const double mean = 0.0) export +{ + DefaultMean = mean; +} + +void PseudoNormalDefaultSigma(const double sigma = 1.0) export +{ + DefaultSigma = sigma; +} + +double PseudoNormalDefaultValue() export +{ + return PseudoNormalValue(DefaultMean, DefaultSigma); +} + +double PseudoNormalValue(const double mean = 0.0, const double sigma = 1.0, const bool rooted = false) export +{ + const double s = !rooted ? sqrt(sigma) : sigma; // allow to get ready-made sqrt in massive calculations + const double r = (rand() - 16383.5) / 16384.0; // [-1,+1] excluding boundaries, cause of infinity + const double x = -(log(1 / ((r + 1) / 2) - 1) * s) / M_PI * M_E + mean; + return x; +} + +bool PseudoNormalArray(double &array[], const int n, + const double mean = 0.0, const double sigma = 1.0) export +{ + bool success = true; + const double s = sqrt(fabs(sigma)); // calculate ready-made sqrt value once + ArrayResize(array, n); + for(int i = 0; i < n; ++i) + { + array[i] = PseudoNormalValue(mean, s, true); + success = success && MathIsValidNumber(array[i]); + } + return success; +} +//+------------------------------------------------------------------+ diff --git a/LibRandTest.mq5 b/LibRandTest.mq5 new file mode 100644 index 0000000..b4845f3 --- /dev/null +++ b/LibRandTest.mq5 @@ -0,0 +1,131 @@ +//+------------------------------------------------------------------+ +//| LibRandTest.mq5 | +//| Copyright 2021-2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property script_show_inputs + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +input int N = 10000; +input double Mean = 0.0; +input double Sigma = 1.0; +input double HistogramStep = 0.5; +input int RandomSeed = 0; + +//+------------------------------------------------------------------+ +//| Includes | +//+------------------------------------------------------------------+ +#include +#include +#include + +#define COMMA , + +//+------------------------------------------------------------------+ +//| Special map with sorting | +//+------------------------------------------------------------------+ +template +class MyMapArray: public MapArray +{ +public: + void sort() + { + SORT_STRUCT(Pair, array, key); + } +}; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const uint seed = RandomSeed ? RandomSeed : GetTickCount(); + Print("Random seed: ", seed); + MathSrand(seed); + + // call 2 library functions + Print("Random HEX-string: ", RandomString(30, StringPatternDigit() + "ABCDEF")); + Print("Random strings:"); + string text[]; + RandomStrings(text, 5, 10, 20); + ArrayPrint(text); + + // call another library function + double x[]; + PseudoNormalArray(x, N, Mean, Sigma); + + // now check distribution of generated values + Print("Random pseudo-gaussian histogram: "); + + // use 'long' as type for keys because 'int' is used for direct access by index + MyMapArray map; + + for(int i = 0; i < N; ++i) + { + map.inc((long)MathRound(x[i] / HistogramStep)); + } + map.sort(); // sort by key value for display + + int max = 0; // use max value for scaling + for(int i = 0; i < map.getSize(); ++i) + { + max = fmax(max, map.getValue(i)); + } + + const double scale = fmax(max / 80, 1); // max 80 chars in histogram + + for(int i = 0; i < map.getSize(); ++i) // print histogram + { + const int p = (int)MathRound(map.getValue(i) / scale); + string filler; + StringInit(filler, p, '*'); + Print(StringFormat("%+.2f (%4d)", + map.getKey(i) * HistogramStep, map.getValue(i)), " ", filler); + } +} +//+------------------------------------------------------------------+ +/* + +Random seed: 8859858 +Random HEX-string: E58B125BCCDA67ABAB2F1C6D6EC677 +Random strings: +"K4ZOpdIy5yxq4ble2" "NxTrVRl6q5j3Hr2FY" "6qxRdDzjp3WNA8xV" "UlOPYinnGd36" "6OCmde6rvErGB3wG" +Random pseudo-gaussian histogram: +-9.50 ( 2) +-8.50 ( 1) +-8.00 ( 1) +-7.00 ( 1) +-6.50 ( 5) +-6.00 ( 10) * +-5.50 ( 10) * +-5.00 ( 24) * +-4.50 ( 28) ** +-4.00 ( 50) *** +-3.50 ( 100) ****** +-3.00 ( 195) *********** +-2.50 ( 272) *************** +-2.00 ( 510) **************************** +-1.50 ( 751) ****************************************** +-1.00 (1029) ********************************************************* +-0.50 (1288) ************************************************************************ ++0.00 (1457) ********************************************************************************* ++0.50 (1263) ********************************************************************** ++1.00 (1060) *********************************************************** ++1.50 ( 772) ******************************************* ++2.00 ( 480) *************************** ++2.50 ( 280) **************** ++3.00 ( 172) ********** ++3.50 ( 112) ****** ++4.00 ( 52) *** ++4.50 ( 43) ** ++5.00 ( 10) * ++5.50 ( 8) ++6.00 ( 8) ++6.50 ( 2) ++7.00 ( 3) ++7.50 ( 1) + +*/ +//+------------------------------------------------------------------+ diff --git a/LibWindowTree.mq5 b/LibWindowTree.mq5 new file mode 100644 index 0000000..2e7f54c --- /dev/null +++ b/LibWindowTree.mq5 @@ -0,0 +1,101 @@ +//+------------------------------------------------------------------+ +//| LibWindowTree.mq5 | +//| Copyright 2021-2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ + +#include + +//+------------------------------------------------------------------+ +//| Obtain window class name and title by handle | +//+------------------------------------------------------------------+ +void GetWindowData(HANDLE w, string &clazz, string &title) +{ + static ushort receiver[MAX_PATH]; + if(GetWindowTextW(w, receiver, MAX_PATH)) + { + title = ShortArrayToString(receiver); + } + if(GetClassNameW(w, receiver, MAX_PATH)) + { + clazz = ShortArrayToString(receiver); + } +} + +//+------------------------------------------------------------------+ +//| Walk up through parent windows | +//+------------------------------------------------------------------+ +HANDLE TraverseUp(HANDLE w) +{ + HANDLE p = 0; + while(w != 0) + { + p = w; + string clazz, title; + GetWindowData(w, clazz, title); + Print("'", clazz, "' '", title, "'"); + w = GetParent(w); + } + return p; +} + +//+------------------------------------------------------------------+ +//| Walk down through child windows | +//+------------------------------------------------------------------+ +HANDLE TraverseDown(const HANDLE w, const int level = 0) +{ + HANDLE child = FindWindowExW(w, NULL, NULL, NULL); // get 1-st child window (if any) + while(child) // keep going while children exist + { + string clazz, title; + GetWindowData(child, clazz, title); + Print(StringFormat("%*s", level * 2, ""), "'", clazz, "' '", title, "'"); + TraverseDown(child, level + 1); + child = FindWindowExW(w, child, NULL, NULL); // get next child window + } + return child; +} + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + // to get the main window could call + // FindWindowW("MetaQuotes::MetaTrader::5.00", NULL); + // or traverse up: + HANDLE h = TraverseUp(ChartGetInteger(0, CHART_WINDOW_HANDLE)); + Print("Main window handle: ", h); + TraverseDown(h, 1); +} +//+------------------------------------------------------------------+ +/* + +'AfxFrameOrView140su' '' +'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,H1' +'MDIClient' '' +'MetaQuotes::MetaTrader::5.00' '12345678 - MetaQuotes-Demo: Demo Account - Hedge - MetaQuotes Software Corp. - [EURUSD,H1]' +Main window handle: 263576 + 'msctls_statusbar32' 'For Help, press F1' + 'AfxControlBar140su' 'Standard' + 'ToolbarWindow32' 'Timeframes' + 'ToolbarWindow32' 'Line Studies' + 'ToolbarWindow32' 'Standard' + 'AfxControlBar140su' 'Toolbox' + 'Afx:000000013F110000:b:0000000000010003:0000000000000000:0000000000000000' 'Toolbox' + 'AfxWnd140su' '' + 'ToolbarWindow32' '' +... + 'MDIClient' '' + 'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,H1' + 'AfxFrameOrView140su' '' + 'Edit' '0.00' + 'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'XAUUSD,Daily' + 'AfxFrameOrView140su' '' + 'Edit' '0.00' + 'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,M15' + 'AfxFrameOrView140su' '' + 'Edit' '0.00' + +*/ +//+------------------------------------------------------------------+ diff --git a/LifeCycle.mq5 b/LifeCycle.mq5 new file mode 100644 index 0000000..7350de3 --- /dev/null +++ b/LifeCycle.mq5 @@ -0,0 +1,46 @@ +//+------------------------------------------------------------------+ +//| LifeCycle.mq5 | +//| Copyright 2021, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ + +#include + +input int Fake = 0; + +//+------------------------------------------------------------------+ +//| Global initialization/finalization trap | +//+------------------------------------------------------------------+ +class Loader +{ + static Loader object; + + Loader() + { + Print(__FUNCSIG__); + } + ~Loader() + { + Print(__FUNCSIG__); + } +}; + +static Loader Loader::object; + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +void OnInit() +{ + Print(__FUNCSIG__, " ", Fake, " ", + EnumToString((ENUM_DEINIT_REASON)_UninitReason)); +} + +//+------------------------------------------------------------------+ +//| Finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + Print(__FUNCSIG__, " ", EnumToString((ENUM_DEINIT_REASON)reason)); +} +//+------------------------------------------------------------------+ diff --git a/MTC Neural network plus MACD.mq5 b/MTC Neural network plus MACD.mq5 new file mode 100644 index 0000000..413ef95 Binary files /dev/null and b/MTC Neural network plus MACD.mq5 differ diff --git a/MainIndicator.mq5 b/MainIndicator.mq5 new file mode 100644 index 0000000..9418eff --- /dev/null +++ b/MainIndicator.mq5 @@ -0,0 +1,45 @@ +//+------------------------------------------------------------------+ +//| MainIndicator.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +#resource "SubFolder\\EmbeddedIndicator.ex5" + +int handle; + +//+------------------------------------------------------------------+ +//| Indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + handle = iCustom(_Symbol, _Period, "::SubFolder\\EmbeddedIndicator.ex5"); + if(handle == INVALID_HANDLE) + { + return INIT_FAILED; + } + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Indicator calculation function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Indicator finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + IndicatorRelease(handle); +} +//+------------------------------------------------------------------+ diff --git a/Martingale Trade Simulator.mq5 b/Martingale Trade Simulator.mq5 new file mode 100644 index 0000000..8f6aa79 --- /dev/null +++ b/Martingale Trade Simulator.mq5 @@ -0,0 +1,618 @@ +//+------------------------------------------------------------------+ +//| Martingale Trade Simulator.mq5 | +//| Copyright 2023, drdz9876@gmail.com | +//| | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2023, drdz9876@gmail.com" +#property version "1.0" +#property strict +#include CTrade trade; + +input const string Initial="//---------------- Initial Trade Settings ----------------//"; +input double Lots = 0.01; //Lots +input double StopLoss = 500; //Stoploss +input double TakeProfit = 500; //TakeProfit + +input const string Trail="//---------------- Trail Settings ----------------//"; +input int TrailingStop = 50; //Trailing Stop +input int TrailingStep = 20; //Trailing Step + +input const string Martingale="//---------------- Martingale Settings ----------------//"; +input double nextLot = 1.2; //Lot Multiplier +input int StepPips = 150; //Pip Step +input int TPPlus = 50; //TP Average + +long chartID = 0; +string prefix = "Martingale Trade Sim"; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { + trade.SetExpertMagicNumber(0); +//--- + if(MQLInfoInteger(MQL_TESTER)) + { + testerPanel(); + } + else + { + Alert("Only works on Strategy Tester Mode"); + return(INIT_FAILED); + } +//--- + return(INIT_SUCCEEDED); + } +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { +//--- + string lookFor = prefix; + ObjectsDeleteAll(chartID,lookFor,0,-1); + + return; + } +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() + { + double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); + double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); + double point = SymbolInfoDouble(Symbol(),SYMBOL_POINT); + int digits =(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS); +//--- + if(MQLInfoInteger(MQL_TESTER)) + { + if(MQLInfoInteger(MQL_VISUAL_MODE)) + { + + ResetLastError(); + ChartRedraw(chartID); + + long lparam = 0; + double dparam = 0.0; + string sparam1 = ""; + string sparam2 = ""; + string sparam3 = ""; + string sparam4 = ""; + //--- + sparam1 = prefix+"Buy"; + sparam2 = prefix+"Sell"; + sparam3 = prefix+"Martingale"; + sparam4 = prefix+"Trail"; + + double SLBuy = 0, SLSell = 0, TPBuy = 0, TPSell = 0; + if(StopLoss > 0) + { + SLBuy = ask - StopLoss*Point(); + SLSell = bid + StopLoss*Point(); + } + if(TakeProfit > 0) + { + TPBuy = ask + TakeProfit*Point(); + TPSell = bid - TakeProfit*Point(); + } + if(bool(ObjectGetInteger(chartID, sparam1,OBJPROP_STATE,true))) + { + OnChartEvent(CHARTEVENT_OBJECT_CLICK, lparam, dparam,sparam1); + if(CheckMoneyForTrade(Lots,ORDER_TYPE_BUY) && CheckVolumeValue(Lots)) + if(!trade.Buy(lotAdjust(Lots),Symbol(),SymbolInfoDouble(Symbol(),SYMBOL_ASK),SLBuy,TPBuy,"Order Test")) + Print("Error to Place Buy Orders "+IntegerToString(GetLastError())); + } + + if(bool(ObjectGetInteger(chartID, sparam2,OBJPROP_STATE,true))) + { + OnChartEvent(CHARTEVENT_OBJECT_CLICK, lparam, dparam,sparam2); + if(CheckMoneyForTrade(Lots,ORDER_TYPE_SELL) && CheckVolumeValue(Lots)) + if(!trade.Sell(lotAdjust(Lots),Symbol(),SymbolInfoDouble(Symbol(),SYMBOL_BID),SLSell,TPSell,"Order Test")) + Print("Error to Place Sell Orders "+IntegerToString(GetLastError())); + } + + if(bool(ObjectGetInteger(chartID, sparam3,OBJPROP_STATE,true))) + { + OnChartEvent(CHARTEVENT_OBJECT_CLICK, lparam, dparam,sparam3); + AveragingOrders(); + } + if(bool(ObjectGetInteger(chartID, sparam4,OBJPROP_STATE,true))) + { + OnChartEvent(CHARTEVENT_OBJECT_CLICK, lparam, dparam,sparam4); + TrailingOrders(); + } + } + } + else + { + Alert("Only works on Strategy Tester Mode"); + ExpertRemove(); + } + + ChartRedraw(chartID); + + return; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void TrailingOrders() + { + double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); + double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); + double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT); + int digits = (int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS); + + double TS = TrailingStop*point; + double TST = TrailingStep*point; + + int b = 0,s = 0; + for(int i=0; i 0) + { + if(PositionGetString(POSITION_SYMBOL) == Symbol()) + if(PositionGetInteger(POSITION_MAGIC)==0) + { + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) + { + b++; + } + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) + { + s++; + } + } + } + } + + + + for(int i=0; i 0) + { + if(PositionGetString(POSITION_SYMBOL) == Symbol()) + if(PositionGetInteger(POSITION_MAGIC)==0) + { + if(b == 1) + { + if(PositionGetDouble(POSITION_SL) == 0 || (PositionGetDouble(POSITION_SL) != 0 && PositionGetDouble(POSITION_SL) < PositionGetDouble(POSITION_PRICE_OPEN))) + { + if(bid - PositionGetDouble(POSITION_PRICE_OPEN) > TS+TST-1*point) + { + if(!trade.PositionModify(PositionGetInteger(POSITION_TICKET),bid-TS,PositionGetDouble(POSITION_TP))) + Print("Error to Trail "+IntegerToString(GetLastError())); + } + } + if(PositionGetDouble(POSITION_SL) > PositionGetDouble(POSITION_PRICE_OPEN)) + { + if(bid - PositionGetDouble(POSITION_SL) > TST+(5*point)) + { + if(!trade.PositionModify(PositionGetInteger(POSITION_TICKET),bid-TST,PositionGetDouble(POSITION_TP))) + Print("Error to Trail "+IntegerToString(GetLastError())); + } + } + } + if(s == 1) + { + if(PositionGetDouble(POSITION_SL) == 0 || (PositionGetDouble(POSITION_SL)!= 0 && PositionGetDouble(POSITION_SL) > PositionGetDouble(POSITION_PRICE_OPEN))) + { + if(PositionGetDouble(POSITION_PRICE_OPEN) - ask > TS+TST-1*point) + { + if(!trade.PositionModify(PositionGetInteger(POSITION_TICKET),ask+TS,PositionGetDouble(POSITION_TP))) + Print("Error to Trail "+IntegerToString(GetLastError())); + } + } + if(PositionGetDouble(POSITION_SL) < PositionGetDouble(POSITION_PRICE_OPEN)) + { + if(PositionGetDouble(POSITION_SL)-ask > TST+(5*point)) + { + if(!trade.PositionModify(PositionGetInteger(POSITION_TICKET),ask+TST,PositionGetDouble(POSITION_TP))) + Print("Error to Trail "+IntegerToString(GetLastError())); + } + } + } + } + } + } + + return; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void AveragingOrders() + { + double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); + double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); + double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT); + int digits = (int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS); + int stopLevel = (int)SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL); + double tickSize=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE); + + double swapB = 0, swapS = 0; + + double + BuyPriceMax = 0, BuyPriceMin = 0, + SelPriceMin = 0, SelPriceMax = 0, + BuyPriceMaxLot = 0, BuyPriceMinLot = 0, + SelPriceMaxLot = 0, SelPriceMinLot = 0, + BuyTP = 0, BuySL = 0, BSL = 0, SSL = 0, + SellTP = 0, SellSL = 0; + int + countOpen = 0, buys = 0, sells = 0; + double nn=0,bb=0, factb = 0, facts = 0; + double nnn=0,bbb=0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + if(PositionGetTicket(i) > 0) + { + if(PositionGetString(POSITION_SYMBOL) == Symbol()) + if(PositionGetInteger(POSITION_MAGIC)==0) + { + countOpen++; + + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) + { + buys++; + double opB = NormalizeDouble(PositionGetDouble(POSITION_PRICE_OPEN), digits); + BuyTP = NormalizeDouble(PositionGetDouble(POSITION_TP),digits); + BuySL = NormalizeDouble(PositionGetDouble(POSITION_SL),digits); + double llot=PositionGetDouble(POSITION_VOLUME); + double itog=0; + + swapB = (PositionGetDouble(POSITION_SWAP) + PositionGetDouble(POSITION_COMMISSION))/llot*tickSize; + + itog=(opB - swapB)*llot; + + bb=bb+itog; + nn=nn+llot; + + factb=bb/nn; + + if(opB > BuyPriceMax || BuyPriceMax == 0) + { + BuyPriceMax = opB; + BuyPriceMaxLot = llot; + } + if(opB < BuyPriceMin || BuyPriceMin == 0) + { + BuyPriceMin = opB; + BuyPriceMinLot = llot; + } + } + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) + { + sells++; + double opS = NormalizeDouble(PositionGetDouble(POSITION_PRICE_OPEN), digits); + SellTP = NormalizeDouble(PositionGetDouble(POSITION_TP),digits); + SellSL = NormalizeDouble(PositionGetDouble(POSITION_SL),digits); + double llots=PositionGetDouble(POSITION_VOLUME); + double itogs=0; + + swapS = (PositionGetDouble(POSITION_SWAP) + PositionGetDouble(POSITION_COMMISSION))/llots*tickSize; + + itogs=(opS - swapS)*llots; + + bbb=bbb+itogs; + nnn=nnn+llots; + facts=bbb/nnn; + + if(opS > SelPriceMax || SelPriceMax == 0) + { + SelPriceMax = opS; + SelPriceMaxLot = llots; + } + if(opS < SelPriceMin || SelPriceMin == 0) + { + SelPriceMin = opS; + SelPriceMinLot = llots; + } + } + } + } + int send = -1; + double BuyStep = 0, SellStep = 0; + BuyStep = StepPips * point; + SellStep = StepPips * point; + + double buyLot = 0, selLot = 0; + + buyLot = lotAdjust(BuyPriceMinLot * MathPow(nextLot,buys)); + selLot = lotAdjust(SelPriceMaxLot * MathPow(nextLot,sells)); + + if(buys > 0) + { + if(BuyPriceMin - ask >= BuyStep) + { + if(CheckMoneyForTrade(buyLot,ORDER_TYPE_BUY) && CheckVolumeValue(buyLot)) + if(!trade.Buy(lotAdjust(buyLot),Symbol(),SymbolInfoDouble(Symbol(),SYMBOL_ASK),0,0,"Order Test")) + Print("Buy Average Failed "+IntegerToString(GetLastError())); + } + } + if(sells > 0) + { + if(bid - SelPriceMax >= SellStep) + { + if(CheckMoneyForTrade(selLot,ORDER_TYPE_SELL) && CheckVolumeValue(selLot)) + if(!trade.Sell(lotAdjust(selLot),Symbol(),SymbolInfoDouble(Symbol(),SYMBOL_BID),0,0,"Order Test")) + Print("Sell Average Failed "+IntegerToString(GetLastError())); + } + } + + double TPAverage = TPPlus; + double CORR = 0; + CORR = NormalizeDouble((TPAverage + stopLevel) * point,digits); + + for(int j=PositionsTotal()-1; j>=0; j--) + { + if(PositionGetTicket(j) > 0) + if(PositionGetString(POSITION_SYMBOL) == Symbol()) + if(PositionGetInteger(POSITION_MAGIC)==0) + { + if(buys >=2 && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) + { + if(!compareDoubles(factb+CORR,PositionGetDouble(POSITION_TP))) + modifyAllSLTP(POSITION_TYPE_BUY,BuySL,factb+CORR); + } + if(sells >= 2 && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) + { + if(!compareDoubles(facts-CORR,PositionGetDouble(POSITION_TP))) + modifyAllSLTP(POSITION_TYPE_SELL,SellSL,facts-CORR); + } + } + } + return; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +double lotAdjust(double lots) + { + double value = 0; + double lotStep = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP); + double minLot = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); + double maxLot = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); + value = NormalizeDouble(lots/lotStep,0) * lotStep; + + value = MathMax(MathMin(maxLot, value), minLot); + + return(value); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool modifyAllSLTP(ENUM_POSITION_TYPE type, double SL, double TP) + { + int digits = (int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS); + for(int j=PositionsTotal()-1; j>=0; j--) + { + if(PositionGetTicket(j) > 0) + if(PositionGetString(POSITION_SYMBOL) == Symbol()) + if(PositionGetInteger(POSITION_MAGIC)==0) + if(PositionGetInteger(POSITION_TYPE) == type) + { + if(SL != 0) + if(!compareDoubles(NormalizeDouble(SL,digits),NormalizeDouble(PositionGetDouble(POSITION_SL),digits))) + { + if(!trade.PositionModify(PositionGetInteger(POSITION_TICKET),NormalizeDouble(SL,digits),PositionGetDouble(POSITION_TP))) + Print("Error Modify Stoploss "+IntegerToString(GetLastError())); + else + return(true); + } + if(TP != 0) + if(!compareDoubles(NormalizeDouble(TP,digits),NormalizeDouble(PositionGetDouble(POSITION_TP),digits))) + { + if(!trade.PositionModify(PositionGetInteger(POSITION_TICKET),PositionGetDouble(POSITION_SL),NormalizeDouble(TP,digits))) + Print("Error Modify Takeprofit "+IntegerToString(GetLastError())); + else + return(true); + } + } + } + return(false); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool compareDoubles(double val1, double val2) + { + int digits = (int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS); + if(NormalizeDouble(val1 - val2,digits-1)==0) + return (true); + + return(false); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CheckMoneyForTrade(double lots,ENUM_ORDER_TYPE type) + { +//--- Getting the opening price + MqlTick mqltick; + SymbolInfoTick(Symbol(),mqltick); + double price=mqltick.ask; + if(type==ORDER_TYPE_SELL) + price=mqltick.bid; +//--- values of the required and free margin + double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); +//--- call of the checking function + if(!OrderCalcMargin(type,Symbol(),lots,price,margin)) + { + //--- something went wrong, report and return false + return(false); + } +//--- if there are insufficient funds to perform the operation + if(margin>free_margin) + { + //--- report the error and return false + return(false); + } +//--- checking successful + return(true); + } +//************************************************************************************************/ +bool CheckVolumeValue(double volume) + { + + double min_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); + if(volume < min_volume) + return(false); + + double max_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX); + if(volume > max_volume) + return(false); + + double volume_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP); + + int ratio = (int)MathRound(volume / volume_step); + if(MathAbs(ratio * volume_step - volume) > 0.0000001) + return(false); + + return(true); + } +//+------------------------------------------------------------------+ +//| ChartEvent function | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { +//--- + if(id == CHARTEVENT_OBJECT_CLICK) + { + if(sparam == prefix+"Buy") + if(bool(ObjectGetInteger(chartID, sparam,OBJPROP_STATE,true))) + { + ObjectSetInteger(chartID,prefix+"Buy",OBJPROP_STATE,false); + } + if(sparam == prefix+"Sell") + if(bool(ObjectGetInteger(chartID, sparam,OBJPROP_STATE,true))) + { + ObjectSetInteger(chartID,prefix+"Sell",OBJPROP_STATE,false); + } + if(sparam == prefix+"Martingale") + { + if(bool(ObjectGetInteger(chartID, sparam,OBJPROP_STATE,false))) + { + ObjectSetInteger(chartID,prefix+"Martingale",OBJPROP_STATE,true); + } + } + if(sparam == prefix+"Trail") + { + if(bool(ObjectGetInteger(chartID, sparam,OBJPROP_STATE,false))) + { + ObjectSetInteger(chartID,prefix+"Trail",OBJPROP_STATE,true); + } + } + } + + return; + } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool createBackground(string name) + { + ObjectCreate(chartID, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); + ObjectSetInteger(chartID,name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(chartID, name, OBJPROP_XDISTANCE, 20); + ObjectSetInteger(chartID, name, OBJPROP_YDISTANCE, 40); + ObjectSetInteger(chartID, name, OBJPROP_XSIZE, 140); + ObjectSetInteger(chartID, name, OBJPROP_YSIZE, 30); + ObjectSetInteger(chartID, name, OBJPROP_BGCOLOR, clrGray); + ObjectSetInteger(chartID, name, OBJPROP_BORDER_COLOR, clrBlack); + ObjectSetInteger(chartID, name, OBJPROP_BORDER_TYPE, BORDER_RAISED); + ObjectSetInteger(chartID, name, OBJPROP_WIDTH, 0); + ObjectSetInteger(chartID, name, OBJPROP_BACK, false); + ObjectSetInteger(chartID, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chartID, name, OBJPROP_HIDDEN, true); + return (true); + } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool createBackground2(string name) + { + ObjectCreate(chartID, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); + ObjectSetInteger(chartID,name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(chartID, name, OBJPROP_XDISTANCE, 20); + ObjectSetInteger(chartID, name, OBJPROP_YDISTANCE, 80); + ObjectSetInteger(chartID, name, OBJPROP_XSIZE, 140); + ObjectSetInteger(chartID, name, OBJPROP_YSIZE, 30); + ObjectSetInteger(chartID, name, OBJPROP_BGCOLOR, clrDarkGreen); + ObjectSetInteger(chartID, name, OBJPROP_BORDER_COLOR, clrBlack); + ObjectSetInteger(chartID, name, OBJPROP_BORDER_TYPE, BORDER_RAISED); + ObjectSetInteger(chartID, name, OBJPROP_WIDTH, 0); + ObjectSetInteger(chartID, name, OBJPROP_BACK, false); + ObjectSetInteger(chartID, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chartID, name, OBJPROP_HIDDEN, true); + return (true); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool createBackground3(string name) + { + ObjectCreate(chartID, name, OBJ_RECTANGLE_LABEL, 0, 0, 0); + ObjectSetInteger(chartID,name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(chartID, name, OBJPROP_XDISTANCE, 20); + ObjectSetInteger(chartID, name, OBJPROP_YDISTANCE, 120); + ObjectSetInteger(chartID, name, OBJPROP_XSIZE, 140); + ObjectSetInteger(chartID, name, OBJPROP_YSIZE, 30); + ObjectSetInteger(chartID, name, OBJPROP_BGCOLOR, clrDarkGoldenrod); + ObjectSetInteger(chartID, name, OBJPROP_BORDER_COLOR, clrBlack); + ObjectSetInteger(chartID, name, OBJPROP_BORDER_TYPE, BORDER_RAISED); + ObjectSetInteger(chartID, name, OBJPROP_WIDTH, 0); + ObjectSetInteger(chartID, name, OBJPROP_BACK, false); + ObjectSetInteger(chartID, name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(chartID, name, OBJPROP_HIDDEN, true); + return (true); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool createButton(string name,string text,int xSize,int ySize,int x,int y,int size, int clr, int bgColor, bool state) + { + ObjectCreate(chartID,name, OBJ_BUTTON, 0, 0, 0); + ObjectSetInteger(chartID,name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(chartID, name, OBJPROP_XSIZE, xSize); + ObjectSetInteger(chartID, name, OBJPROP_YSIZE, ySize); + ObjectSetInteger(chartID,name, OBJPROP_XDISTANCE, x); + ObjectSetInteger(chartID,name, OBJPROP_YDISTANCE, 30+y); + ObjectSetString(chartID,name,OBJPROP_TEXT, text); + ObjectSetInteger(chartID, name, OBJPROP_BACK, false); + ObjectSetInteger(chartID,name,OBJPROP_FONTSIZE,size); + ObjectSetInteger(chartID,name,OBJPROP_COLOR,clr); + ObjectSetInteger(chartID,name,OBJPROP_STATE,state); + ObjectSetInteger(chartID,name,OBJPROP_ZORDER,100); + ObjectSetInteger(chartID, name, OBJPROP_BGCOLOR, bgColor); + + return (true); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void testerPanel() + { + createBackground(prefix+"Background"); + createButton(prefix+"Buy","Buy",50,20,30,15,8,clrWhite,clrBlue,false); + createButton(prefix+"Sell","Sell",50,20,100,15,8,clrWhite,clrRed,false); + createBackground2(prefix+"Background2"); + createButton(prefix+"Martingale","Enable Martingale",120,20,30,55,8,clrBlack,clrLightGreen,true); + createBackground3(prefix+"Background3"); + createButton(prefix+"Trail","Enable Trail",120,20,30,95,8,clrBlack,clrGold,true); + return; + } +//+------------------------------------------------------------------+ diff --git a/MovingAverage.mq5 b/MovingAverage.mq5 new file mode 100644 index 0000000..270c202 --- /dev/null +++ b/MovingAverage.mq5 @@ -0,0 +1,73 @@ + +#property copyright "Mueller Peter" +#property link "https://www.mql5.com/en/users/mullerp04/seller" +#property version "1.00" + +// Include external functions +#include + +// Inputs for the Expert Advisor +input int MAPeriod = 50; // Moving Average period +input double LotSize = 0.01; // Lot size for trades +input int TPPoints = 150; // Take profit points +input int SLPoints = 150; // Stop loss points + +CTrade Trade; // Object for managing trades + +// Initialization function +int OnInit() +{ + return(INIT_SUCCEEDED); // Initialization succeeded +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + // Nothing to clean up for now +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + // Handle for the moving average indicator and arrays to store its values + static int MAhandle = iMA(_Symbol, PERIOD_CURRENT, MAPeriod, 0, MODE_SMA, PRICE_CLOSE); + static double MAArray[2]; + + // Check if the market is open + if(!MarketOpen()) + return; + + // If there are open positions, exit the function + if(PositionsTotal()) + return; + + // If a new candle has formed + if(IsNewCandle()) + { + // Copy the moving average values into the array + CopyBuffer(MAhandle, 0, 1, 2, MAArray); + double PrevClose = iClose(_Symbol, PERIOD_CURRENT, 2); // Close price of the previous candle + double CurrentClose = iClose(_Symbol, PERIOD_CURRENT, 1); // Close price of the current candle + + // If the moving average crosses above the price, sell + if(MAArray[0] < PrevClose && MAArray[1] > CurrentClose) + { + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + if(CheckVolumeValue(LotSize)) // Checking wether the volume is valid + Trade.Sell(RoundtoLots(LotSize), _Symbol, 0, Round(bid + SLPoints * _Point, _Digits), Round(bid - TPPoints * _Point, _Digits)); + + } + + // If the moving average crosses below the price, buy + if(MAArray[0] > PrevClose && MAArray[1] < CurrentClose) + { + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + if(CheckVolumeValue(LotSize)) // Checking wether the volume is valid + Trade.Buy(RoundtoLots(LotSize), _Symbol, 0, Round(ask - SLPoints * _Point, _Digits), Round(ask + TPPoints * _Point, _Digits)); + } + } +} diff --git a/MovingAverageMartinGale.mq5 b/MovingAverageMartinGale.mq5 new file mode 100644 index 0000000..149f5b0 --- /dev/null +++ b/MovingAverageMartinGale.mq5 @@ -0,0 +1,92 @@ + +#property copyright "Mueller Peter" +#property link "https://www.mql5.com/en/users/mullerp04/seller" +#property version "1.00" +#include + +// Inputs for the Expert Advisor +input int MAPeriod = 50; // Moving Average period +input double StartingLot = 0.01; // Starting lot size for trades +input double MaxLot = 0.5; // Maximum lot size allowed +input int TPPoints = 100; // Take profit points +input int SLPoints = 300; // Stop loss points +input double LotMultiplier = 2; // Multiplier for increasing lot size after a loss +input double TPMultiplier = 2; // Multiplier for adjusting TP and SL after a loss + +CTrade Trade; // Object for managing trades + +// Initialization function +int OnInit() +{ + return(INIT_SUCCEEDED); // Initialization succeeded +} + +// Deinitialization function +void OnDeinit(const int reason) +{ + // Nothing to clean up for now +} + +// Main function called on every tick +void OnTick() +{ + // Handle for the moving average indicator and arrays to store its values + static int MAhandle = iMA(_Symbol, PERIOD_CURRENT, MAPeriod, 0, MODE_SMA, PRICE_CLOSE); + static double MAArray[2]; + static double Vol = StartingLot; // Current lot size + static double TP = TPPoints*_Point; // Current take profit in points + static double SL = SLPoints*_Point; // Current stop loss in points + + // Check if the market is open + if(!MarketOpen()) + return; + + // If there are open positions, exit the function + if(PositionsTotal()) + return; + + // If a new candle has formed + if(IsNewCandle()) + { + // Copy the moving average values into the array + CopyBuffer(MAhandle, 0, 1, 2, MAArray); + double PrevClose = iClose(_Symbol, PERIOD_CURRENT, 2); // Close price of the previous candle + double CurrentClose = iClose(_Symbol, PERIOD_CURRENT, 1); // Close price of the current candle + + // Check for crossover conditions + if((MAArray[0] > PrevClose && MAArray[1] < CurrentClose) || (MAArray[0] < PrevClose && MAArray[1] > CurrentClose)) + { + // If the last trade was a loss and we can increase the lot size + if(HistoryLastProfit() < 0 && Vol * LotMultiplier < MaxLot) + { + Vol *= LotMultiplier; // Increase lot size + TP *= TPMultiplier; // Adjust take profit + SL *= TPMultiplier; // Adjust stop loss + } + + // If the last trade was a profit + if(HistoryLastProfit() > 0) + { + Vol = StartingLot; // Reset lot size + TP = TPPoints * _Point; // Reset take profit + SL = SLPoints * _Point; // Reset stop loss + } + } + + // If the moving average crosses above the price, sell + if(MAArray[0] < PrevClose && MAArray[1] > CurrentClose) + { + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + if(CheckVolumeValue(RoundtoLots(Vol))) //Checking wether the volume is valid + Trade.Sell(RoundtoLots(Vol), _Symbol, 0, Round(bid + SL, _Digits), Round(bid - TP, _Digits)); + } + + // If the moving average crosses below the price, buy + if(MAArray[0] > PrevClose && MAArray[1] < CurrentClose) + { + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + if(CheckVolumeValue(RoundtoLots(Vol))) //Checking wether the volume is valid + Trade.Buy(RoundtoLots(Vol), _Symbol, 0, Round(ask - SL, _Digits), Round(ask + TP, _Digits)); + } + } +} \ No newline at end of file diff --git a/NetFtp.mq5 b/NetFtp.mq5 new file mode 100644 index 0000000..6881aa8 --- /dev/null +++ b/NetFtp.mq5 @@ -0,0 +1,21 @@ +//+------------------------------------------------------------------+ +//| NetFtp.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ + +#include +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const string filename = _Symbol + "-" + PeriodToString() + "-" + + (string)(ulong)TimeTradeServer() + ".png"; + PRTF(ChartScreenShot(0, filename, 300, 200)); + Print("Sending file: " + filename); + PRTF(SendFTP(filename, "/upload")); // 0 or FTP_CONNECT_FAILED(4522), FTP_CHANGEDIR(4523), etc +} +//+------------------------------------------------------------------+ diff --git a/NetMail.mq5 b/NetMail.mq5 new file mode 100644 index 0000000..28f8853 --- /dev/null +++ b/NetMail.mq5 @@ -0,0 +1,20 @@ +//+------------------------------------------------------------------+ +//| NetMail.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ + +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const string message = "Hello from " + + AccountInfoString(ACCOUNT_SERVER) + + " " + (string)AccountInfoInteger(ACCOUNT_LOGIN); + Print("Sending email: " + message); + PRTF(SendMail(MQLInfoString(MQL_PROGRAM_NAME), message)); // MAIL_SEND_FAILED(4510) or 0 +} +//+------------------------------------------------------------------+ diff --git a/NetNotification.mq5 b/NetNotification.mq5 new file mode 100644 index 0000000..ab6111d --- /dev/null +++ b/NetNotification.mq5 @@ -0,0 +1,21 @@ +//+------------------------------------------------------------------+ +//| NetNotification.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ + +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const string message = MQLInfoString(MQL_PROGRAM_NAME) + + " runs on " + AccountInfoString(ACCOUNT_SERVER) + + " " + (string)AccountInfoInteger(ACCOUNT_LOGIN); + Print("Sending notification: " + message); + PRTF(SendNotification(NULL)); // INVALID_PARAMETER(4003) + PRTF(SendNotification(message)); // NOTIFICATION_WRONG_SETTINGS(4517) or 0 +} +//+------------------------------------------------------------------+ diff --git a/New Candel - Bar Detector.mq5 b/New Candel - Bar Detector.mq5 new file mode 100644 index 0000000..efadc95 --- /dev/null +++ b/New Candel - Bar Detector.mq5 @@ -0,0 +1,98 @@ +//+------------------------------------------------------------------+ +//| Code Checker.mq5 | +//| by H A T Lakmal | +//| https://t.me/Lakmal846 | +//+------------------------------------------------------------------+ +#property copyright "by H A T Lakmal" +#property link "https://t.me/Lakmal846" +#property version "1.00" + +bool NewBarRecived = false; // Falg for controll. + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { +//--- create timer + EventSetTimer(60); + +//--- + return(INIT_SUCCEEDED); + } +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { +//--- destroy timer + EventKillTimer(); + + } + + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() + { + datetime TimePreviousBar = iTime(_Symbol,PERIOD_M1,1); + datetime TimeCurrentClose = TimePreviousBar + 60; // Closing Time of the current bar. + datetime Time_Current = TimeCurrent(); + + if(Time_Current == TimeCurrentClose && NewBarRecived == false) + { + PlaySound("ok.wav"); // For the statement work of not. + NewBarRecived = true; // Update the flag to avoide multiple calls. + + + // Your Code goes here ----- (Do Something) + + } + else + if(Time_Current > TimeCurrentClose) + { + NewBarRecived = false; // Rest the flag for next bar open. + + + + // Your Code goes here ----- (Do Something) + + + } + + + Comment("\n" + "\n" + "Time Current Bar -: " + TimeToString(TimePreviousBar,TIME_DATE|TIME_MINUTES|TIME_SECONDS) + + "\n" + "Time Current Close -: " +TimeToString(TimeCurrentClose,TIME_DATE|TIME_MINUTES|TIME_SECONDS) + + "\n" + "Time Current -: " + TimeToString(Time_Current,TIME_DATE|TIME_MINUTES|TIME_SECONDS) + "\n" +"\n" + "A New Bar Recived -: " + NewBarRecived); // For check calculations + + + } +//+------------------------------------------------------------------+ +//| Timer function | +//+------------------------------------------------------------------+ +void OnTimer() + { +//--- + + } +//+------------------------------------------------------------------+ +//| Trade function | +//+------------------------------------------------------------------+ +void OnTrade() + { +//--- + + } +//+------------------------------------------------------------------+ +//| ChartEvent function | +//+------------------------------------------------------------------+ +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { +//--- + + } +//+------------------------------------------------------------------+ diff --git a/NonEmbeddedIndicator.mq5 b/NonEmbeddedIndicator.mq5 new file mode 100644 index 0000000..86bd468 --- /dev/null +++ b/NonEmbeddedIndicator.mq5 @@ -0,0 +1,55 @@ +//+------------------------------------------------------------------+ +//| NonEmbeddedIndicator.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property indicator_chart_window +#property indicator_buffers 0 +#property indicator_plots 0 + +input int Reference = 0; + +int handle = 0; + +//+------------------------------------------------------------------+ +//| Indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + const string name = MQLInfoString(MQL_PROGRAM_NAME); + const string path = MQLInfoString(MQL_PROGRAM_PATH); + Print(Reference); + Print("Name: " + name); + Print("Full path: " + path); + + if(Reference == 0) + { + handle = iCustom(_Symbol, _Period, name, 1); + if(handle == INVALID_HANDLE) + { + return INIT_FAILED; + } + } + Print("Success"); + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Indicator calculation function (dummy here) | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const int begin, + const double &price[]) +{ + return rates_total; +} + +//+------------------------------------------------------------------+ +//| Indicator finalization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + IndicatorRelease(handle); +} +//+------------------------------------------------------------------+ diff --git a/PropFirmHelper.mq5 b/PropFirmHelper.mq5 new file mode 100644 index 0000000..e1e6359 --- /dev/null +++ b/PropFirmHelper.mq5 @@ -0,0 +1,374 @@ +//+------------------------------------------------------------------+ +//| BreakoutStrategy.mq5 | +//| Copyright 2024, QuanAlpha | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2024, QuanAlpha" +#property version "1.00" + +#include + +//+------------------------------------------------------------------+ +//| INPUT PARAMETERS | +//+------------------------------------------------------------------+ +input string aa = "------------------SETTINGS----------------------"; +input string BOT_NAME = "Breakout Strategy with Prop Firm Challenge Helper"; +input int EXPERT_MAGIC = 1; +input string bb = "-------------------ENTRY------------------------"; +input int ENTRY_PERIOD = 20; +input int ENTRY_SHIFT = 1; +input string cc = "--------------------EXIT------------------------"; +input int EXIT_PERIOD = 20; +input int EXIT_SHIFT = 1; +input string dd = "-------------PROP FIRM CHALLENGE-----------------"; +input bool isChallenge = false; +input double PASS_CRITERIA = 110100.; +input double DAILY_LOSS_LIMIT = 4500.; +input string gg = "----------------RISK PROFILE--------------------"; +input double RISK_PER_TRADE = 1.0; // Percent of equity per trade +input ENUM_TIMEFRAMES TRADING_TIMEFRAME = PERIOD_H1; + +//+------------------------------------------------------------------+ +//| GLOBAL VARIABLES | +//+------------------------------------------------------------------+ + +// Trade Object +CTrade trade; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + trade.SetExpertMagicNumber(EXPERT_MAGIC); + trade.SetDeviationInPoints(10); + + printf(BOT_NAME + " initialized!"); + return(INIT_SUCCEEDED); +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + printf(BOT_NAME + " exited, exit code: %d", reason); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ + +void OnTick() +{ + if (isChallenge) + { + // Check if we passed the challenge + if (isPassed()) + { + ClearAll("Prop Firm Challenge Passed!"); + } + else if (isDailyLimit()) + { + ClearAll("Daily loss limit exceeded!"); + } + + } + + ExecuteTrade(); +} + +//+------------------------------------------------------------------+ +//| TRADE EXECUTION | +//+------------------------------------------------------------------+ +void ExecuteTrade() +{ + if (BarOpen()) + { + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID), ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + + double trading_hh = HighestHigh(ENTRY_PERIOD, ENTRY_SHIFT, TRADING_TIMEFRAME); + double trading_ll = LowestLow(ENTRY_PERIOD, ENTRY_SHIFT, TRADING_TIMEFRAME); + + double trigger_long = trading_hh + SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); + double trigger_short = trading_ll - SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); + + double exit_long = LowestLow(EXIT_PERIOD, EXIT_SHIFT, TRADING_TIMEFRAME); + double exit_short = HighestHigh(EXIT_PERIOD, EXIT_SHIFT, TRADING_TIMEFRAME); + + // Delete old orders + int orders = OrderManaging(); + + // Manage existing positions + int long_positions = PositionManaging(POSITION_TYPE_BUY); + int short_positions = PositionManaging(POSITION_TYPE_SELL); + + // Long order + if (long_positions == 0 && trigger_long - ask >= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + double sl = trigger_long - exit_long; + double lot_size = CalculateLotSize(sl, trigger_long); + double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + while (lot_size > max_volume) + { + trade.BuyStop(max_volume, trigger_long, _Symbol, exit_long, 0.0, ORDER_TIME_DAY); + lot_size -= max_volume; + } + trade.BuyStop(lot_size, trigger_long, _Symbol, exit_long, 0.0, ORDER_TIME_DAY); + } + + // Short order + if (short_positions == 0 && bid - trigger_short >= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + double sl = exit_short - trigger_short; + double lot_size = CalculateLotSize(sl, trigger_short); + double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + while (lot_size > max_volume) + { + trade.SellStop(max_volume, trigger_short, _Symbol, exit_short, 0.0, ORDER_TIME_DAY); + lot_size -= max_volume; + } + trade.SellStop(lot_size, trigger_short, _Symbol, exit_short, 0.0, ORDER_TIME_DAY); + } + } +} + +//+------------------------------------------------------------------+ +//| Indicators | +//+------------------------------------------------------------------+ + +double HighestHigh(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + return iHigh(_Symbol, timeframe, iHighest(_Symbol, timeframe, MODE_HIGH, period, shift)); +} + +double LowestLow(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + return iLow(_Symbol, timeframe, iLowest(_Symbol, timeframe, MODE_LOW, period, shift)); +} + +double Middle(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + return (HighestHigh(period, shift, timeframe) + LowestLow(period, shift, timeframe)) / 2.; +} + +double ATR(int period, int shift, ENUM_TIMEFRAMES timeframe) +{ + int handle = iATR(_Symbol, timeframe, period); + double value[]; + CopyBuffer(handle, 0, shift, 1, value); + return value[0]; +} + +//+------------------------------------------------------------------+ +//| Manage existing positions | +//+------------------------------------------------------------------+ +int PositionManaging(ENUM_POSITION_TYPE position_type) +{ + int positions = 0; + double trail_long, trail_short; + double atr = ATR(20, 1, TRADING_TIMEFRAME); + + trail_long = LowestLow(EXIT_PERIOD, EXIT_SHIFT, TRADING_TIMEFRAME); + trail_short = HighestHigh(EXIT_PERIOD, EXIT_SHIFT, TRADING_TIMEFRAME); + + + for (int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong posTicket = PositionGetTicket(i); + if (PositionSelectByTicket(posTicket)) + { + if (PositionGetString(POSITION_SYMBOL) == _Symbol && + PositionGetInteger(POSITION_MAGIC) == EXPERT_MAGIC && + PositionGetInteger(POSITION_TYPE) == position_type) + { + positions = positions + 1; + double sl = PositionGetDouble(POSITION_SL), tp = PositionGetDouble(POSITION_TP), cp = PositionGetDouble(POSITION_PRICE_CURRENT), op = PositionGetDouble(POSITION_PRICE_OPEN); + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) + { + // Trailing stop + if (cp < trail_long) + { + trade.PositionClose(posTicket); + positions--; + } + else if (trail_long > sl + 0.1 * atr && cp - trail_long >= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + trade.PositionModify(posTicket, trail_long, tp); + } + } + else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) + { + // Trailing stop + if (cp > trail_short) + { + trade.PositionClose(posTicket); + positions--; + } + else if (trail_short < sl - 0.1 * atr && trail_short - cp >= SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL)) + { + trade.PositionModify(posTicket, trail_short, tp); + } + } + } + } + } + return MathMax(positions, 0); +} + +//+------------------------------------------------------------------+ +//| Delete old order | +//+------------------------------------------------------------------+ +int OrderManaging() +{ + int orders = 0; + for (int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong orderTicket = OrderGetTicket(i); + if (OrderSelect(orderTicket)) + { + if (OrderGetString(ORDER_SYMBOL) == _Symbol && + OrderGetInteger(ORDER_MAGIC) == EXPERT_MAGIC) + { + trade.OrderDelete(orderTicket); + } + } + } + return orders; +} + +//+------------------------------------------------------------------+ +//| Calculate lot_sizes based on risk % of equity per trade | +//+------------------------------------------------------------------+ + +double CalculateLotSize(double sl_value, double price) +{ + double lots = 0.0; + double loss = 0.0; + + double balance=AccountInfoDouble(ACCOUNT_EQUITY); + double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); + double sl_price=price-sl_value; + + if (OrderCalcProfit(ORDER_TYPE_BUY,_Symbol,1.0,price,sl_price,loss)) + { + double lotstep=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); + double risk_money=RISK_PER_TRADE * balance / 100.; + double margin = 0; + lots=risk_money/MathAbs(loss); + lots=MathFloor(lots/lotstep)*lotstep; + + // Adjust lots to broker limits. + double minlot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); + double maxlot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); + + if(lots < minlot) + { + lots=minlot; + } + + // Check if available margin is enough to enter a position + if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,lots,price,margin)) + { + double free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); + if(free_margin<0) + { + lots=0; + } + else if(free_margin= 0; i--) + { + ulong orderTicket = OrderGetTicket(i); + if (OrderSelect(orderTicket)) + { + trade.OrderDelete(orderTicket); + } + } + + for (int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong posTicket = PositionGetTicket(i); + trade.PositionClose(posTicket); + } +} + +// Check if we have achieved profit target +bool isPassed() +{ + return AccountInfoDouble(ACCOUNT_EQUITY) > PASS_CRITERIA; +} + +// Check if we are about to violate maximum daily loss +bool isDailyLimit() +{ + MqlDateTime date_time; + TimeToStruct(TimeCurrent(), date_time); + int current_day = date_time.day, current_month = date_time.mon, current_year = date_time.year; + + // Current balance + double current_balance = AccountInfoDouble(ACCOUNT_BALANCE); + + // Get today's closed trades PL + HistorySelect(0, TimeCurrent()); + int orders = HistoryDealsTotal(); + + double PL = 0.0; + for (int i = orders - 1; i >= 0; i--) + { + ulong ticket=HistoryDealGetTicket(i); + if(ticket==0) + { + Print("HistoryDealGetTicket failed, no trade history"); + break; + } + double profit = HistoryDealGetDouble(ticket,DEAL_PROFIT); + if (profit != 0) + { + // Get deal datetime + MqlDateTime deal_time; + TimeToStruct(HistoryDealGetInteger(ticket, DEAL_TIME), deal_time); + // Check deal time + if (deal_time.day == current_day && deal_time.mon == current_month && deal_time.year == current_year) + { + PL += profit; + } + else + break; + } + } + double starting_balance = current_balance - PL; + double current_equity = AccountInfoDouble(ACCOUNT_EQUITY); + return current_equity < starting_balance - DAILY_LOSS_LIMIT; +} \ No newline at end of file diff --git a/PseudoMarketBook.mq5 b/PseudoMarketBook.mq5 new file mode 100644 index 0000000..1f7b068 --- /dev/null +++ b/PseudoMarketBook.mq5 @@ -0,0 +1,210 @@ +//+------------------------------------------------------------------+ +//| PseudoMarketBook.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Generate pseudo market book from ticks for a custom symbol based on a symbol without market books." + +#include +#include +#include +#include + +input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder +input uint CustomBookDepth = 20; + +string CustomSymbol; +int depth; +double contract; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + CustomSymbol = _Symbol + ".Pseudo"; + + bool custom = false; + if(!PRTF(SymbolExist(CustomSymbol, custom))) + { + if(PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol))) + { + CustomSymbolSetString(CustomSymbol, SYMBOL_DESCRIPTION, "Pseudo book generator"); + CustomSymbolSetString(CustomSymbol, SYMBOL_FORMULA, "\"" + _Symbol + "\""); + } + } + else if(!custom) + { + Alert("Standard symbol already exists"); + return INIT_FAILED; + } + else + { + if(IDYES == MessageBox(StringFormat("Delete existing custom symbol '%s'?", CustomSymbol), + "Please, confirm", MB_YESNO)) + { + PRTF(MarketBookRelease(CustomSymbol)); + PRTF(SymbolSelect(CustomSymbol, false)); + PRTF(CustomRatesDelete(CustomSymbol, 0, LONG_MAX)); + PRTF(CustomTicksDelete(CustomSymbol, 0, LONG_MAX)); + if(!PRTF(CustomSymbolDelete(CustomSymbol))) + { + Alert("Can't delete ", CustomSymbol, ", please, check up and delete manually"); + } + return INIT_PARAMETERS_INCORRECT; + } + } + + if(PRTF(SymbolInfoInteger(_Symbol, SYMBOL_TICKS_BOOKDEPTH)) != CustomBookDepth + && PRTF(SymbolInfoInteger(CustomSymbol, SYMBOL_TICKS_BOOKDEPTH)) != CustomBookDepth) + { + Print("Adjusting custom market book depth"); + PRTF(CustomSymbolSetInteger(CustomSymbol, SYMBOL_TICKS_BOOKDEPTH, CustomBookDepth)); + } + + PRTF(SymbolSelect(CustomSymbol, true)); + + SymbolMonitor sm; + CustomSymbolMonitor csm(CustomSymbol, &sm); + int props[] = {SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE}; + const int d1 = csm.verify(props); + if(d1) + { + Print("Number of found descrepancies: ", d1); + if(csm.verify(props)) // check again + { + Alert("Custom symbol can not be created, internal error!"); + return INIT_FAILED; + } + Print("Fixed"); + } + + + depth = (int)PRTF(SymbolInfoInteger(CustomSymbol, SYMBOL_TICKS_BOOKDEPTH)); + contract = PRTF(SymbolInfoDouble(CustomSymbol, SYMBOL_TRADE_CONTRACT_SIZE)); + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Helper function to accumulate volumes by price levels | +//+------------------------------------------------------------------+ +void Place(double &array[], const int index, const double value = 1) +{ + const int size = ArraySize(array); + if(index >= size) + { + ArrayResize(array, index + 1); + for(int i = size; i <= index; ++i) + { + array[i] = 0; + } + } + array[index] += value; +} + +//+------------------------------------------------------------------+ +//| Simulate market book based on previous ticks | +//| - what has been bought, will be sold | +//| - what has been sold, will be bought | +//+------------------------------------------------------------------+ +bool GenerateMarketBook(const int count, MqlBookInfo &book[]) +{ + MqlTick tick; // center of the book + if(!SymbolInfoTick(_Symbol, tick)) return false; + + double buys[]; // buy volumes be price levels + double sells[]; // sell volumes be price levels + + MqlTick ticks[]; + CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, 0, count); // get tick history + for(int i = 1; i < ArraySize(ticks); ++i) + { + // consider ask was moved up by buy + int k = (int)MathRound((tick.ask - ticks[i].ask) / _Point); + if(ticks[i].ask > ticks[i - 1].ask) + { + // already bought, will not buy again, probably will take profit by selling + if(k <= 0) + { + Place(sells, -k, contract / sqrt(sqrt(ArraySize(ticks) - i))); + } + } + + // bid was moved down by sell + k = (int)MathRound((tick.bid - ticks[i].bid) / _Point); + if(ticks[i].bid < ticks[i - 1].bid) + { + // already sold, will not sell again, probably will take profit by buying + if(k >= 0) + { + Place(buys, k, contract / sqrt(sqrt(ArraySize(ticks) - i))); + } + } + } + + for(int i = 0, k = 0; i < ArraySize(sells) && k < depth; ++i) // upper half of the book + { + if(sells[i] > 0) + { + MqlBookInfo info = {}; + info.type = BOOK_TYPE_SELL; + info.price = tick.ask + i * _Point; + info.volume = (long)sells[i]; + info.volume_real = (double)(long)sells[i]; + PUSH(book, info); + ++k; + } + } + + for(int i = 0, k = 0; i < ArraySize(buys) && k < depth; ++i) // bottom half of the book + { + if(buys[i] > 0) + { + MqlBookInfo info = {}; + info.type = BOOK_TYPE_BUY; + info.price = tick.bid - i * _Point; + info.volume = (long)buys[i]; + info.volume_real = (double)(long)buys[i]; + PUSH(book, info); + ++k; + } + } + + return ArraySize(book) > 0; +} + +//+------------------------------------------------------------------+ +//| Tick event handler | +//+------------------------------------------------------------------+ +void OnTick() +{ + MqlBookInfo book[]; + if(GenerateMarketBook(2000, book)) + { + ResetLastError(); + if(!CustomBookAdd(CustomSymbol, book)) + { + Print("Can't add market books, ", E2S(_LastError)); + ExpertRemove(); + } + } +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int) +{ + // this call is required because CustomBookAdd implicitly locks + // the symbol for which the books are generated, + // without this call user will not be able to remove the symbol + // from the Market Watch (where it is internally selected from + // inside CustomBookAdd, even if it's not visible) + // and delete it + PRTF(MarketBookRelease(CustomSymbol)); +} + +//+------------------------------------------------------------------+ diff --git a/Raymond Cloudy Day for EA.mq5 b/Raymond Cloudy Day for EA.mq5 new file mode 100644 index 0000000..44da32a Binary files /dev/null and b/Raymond Cloudy Day for EA.mq5 differ diff --git a/Reservoir.mq5 b/Reservoir.mq5 new file mode 100644 index 0000000..63ff29b --- /dev/null +++ b/Reservoir.mq5 @@ -0,0 +1,79 @@ +//+------------------------------------------------------------------+ +//| Reservoir.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//| Test script to store and restore user data to/from resource | +//+------------------------------------------------------------------+ +#include +#include + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const string resource = "::reservoir"; + + Reservoir res1; + string message = "message1"; // custom string to write into resource + PRTF(res1.packString(message)); + + MqlTick tick1[1]; // simple struct to add + SymbolInfoTick(_Symbol, tick1[0]); + PRTF(res1.packArray(tick1)); + PRTF(res1.packNumber(DBL_MAX)); // double value + + /* + // complex structures with strings and objects not supported, + // compiler error: 'MqlParam' has objects and cannot be used as union member + MqlParam param[1] = {{}}; + res1.packArray(param); + */ + + res1.submit(resource); // commit our custom data into the resource + res1.clear(); // make the reservoir object empty + + string reply; // new variable to receive the message back + MqlTick tick2[1]; // new struct to receive the tick back + double result; // new variable to receive the number + + PRTF(res1.acquire(resource)); // attach the object to specified resource + PRTF(res1.unpackString(reply)); // restore custom string + PRTF(res1.unpackArray(tick2)); // restore simple struct + PRTF(res1.unpackNumber(result)); // restore double value + + // output and compare restored data (string, struct, and number) + PRTF(reply); + PRTF(ArrayCompare(tick1, tick2)); + ArrayPrint(tick2); + PRTF(result == DBL_MAX); + + // make sure the reservoir was read till the end + PRTF(res1.size()); + PRTF(res1.cursor()); + + PrintFormat("Cleaning up local storage '%s'", resource); + ResourceFree(resource); +} +//+------------------------------------------------------------------+ +/* + example output + + res1.packString(message)=4 / ok + res1.packArray(tick1)=20 / ok + res1.packNumber(DBL_MAX)=23 / ok + res1.acquire(resource)=true / ok + res1.unpackString(reply)=4 / ok + res1.unpackArray(tick2)=20 / ok + res1.unpackNumber(result)=23 / ok + reply=message1 / ok + ArrayCompare(tick1,tick2)=0 / ok + [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] + [0] 2022.05.19 23:09:32 1.05867 1.05873 0.0000 0 1653001772050 6 0.00000 + result==DBL_MAX=true / ok + res1.size()=23 / ok + res1.cursor()=23 / ok + Cleaning up local storage '::reservoir' + +*/ +//+------------------------------------------------------------------+ diff --git a/ResourceFont.mq5 b/ResourceFont.mq5 new file mode 100644 index 0000000..fa1732e --- /dev/null +++ b/ResourceFont.mq5 @@ -0,0 +1,87 @@ +//+------------------------------------------------------------------+ +//| ResourceFont.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Display text with custom font in dynamically changing orientation." +#property script_show_inputs + +#resource "a_LCDNova3DCmObl.ttf" + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +input string Message = "Hello world!"; // Message +input uint Seconds = 10; // Demo Time (seconds) + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const string name = "FONT"; + const int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); + const int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); + + // on-chart object to hold bitmap resource + ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0); + ObjectSetInteger(0, name, OBJPROP_XDISTANCE, w / 2); + ObjectSetInteger(0, name, OBJPROP_YDISTANCE, h / 2); + ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER); + + uint data[], width, height; + + // empty resource for binding with the object + ArrayResize(data, 1); + ResourceCreate(name, data, 1, 1, 0, 0, 1, COLOR_FORMAT_ARGB_RAW); + ObjectSetString(0, name, OBJPROP_BMPFILE, "::" + name); + + const uint timeout = GetTickCount() + Seconds * 1000; + int angle = 0; + int remain = 10; + + while(!IsStopped() && GetTickCount() < timeout) + { + // apply new angle + TextSetFont("::a_LCDNova3DCmObl.ttf", -240, 0, angle * 10); + + // generate altered text + const string text = Message + " (" + (string)remain-- + ")"; + + // obtain dimensions of the text, allocate array + TextGetSize(text, width, height); + ArrayResize(data, width * height); + ArrayInitialize(data, 0); // transparent background + + // on vertical orientation exchange the sizes + if((bool)(angle / 90 & 1)) + { + const uint t = width; + width = height; + height = t; + } + + // adjust the object dimensions + ObjectSetInteger(0, name, OBJPROP_XSIZE, width); + ObjectSetInteger(0, name, OBJPROP_YSIZE, height); + + // draw the text + TextOut(text, width / 2, height / 2, TA_CENTER | TA_VCENTER, data, width, height, + ColorToARGB(clrBlue), COLOR_FORMAT_ARGB_RAW); + + // update the resource and the chart + ResourceCreate(name, data, width, height, 0, 0, width, COLOR_FORMAT_ARGB_RAW); + ChartRedraw(); + + // update the counter + angle += 90; + + Sleep(1000); + } + + ObjectDelete(0, name); + ResourceFree("::" + name); +} +//+------------------------------------------------------------------+ diff --git a/ResourceReadImage.mq5 b/ResourceReadImage.mq5 new file mode 100644 index 0000000..c4e1af2 --- /dev/null +++ b/ResourceReadImage.mq5 @@ -0,0 +1,111 @@ +//+------------------------------------------------------------------+ +//| ResourceReadImage.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Runs the script 3 times to consequentially go through 3 steps/states:" +"1. prepare original and modified bitmaps for On/Off states;" +"2. create an object on a chart to draw the bitmaps (clickable to switch between On/Off);" +"3. delete the objects and resources;" +// NB: descriptions are not visible in UI for scripts unless +// the following directive is specified: #property script_show_inputs + +#include + +//+------------------------------------------------------------------+ +//| Helper function to setup an object for bitmaps drawing | +//+------------------------------------------------------------------+ +void ShowBitmap(const string name, const string resourceOn, const string resourceOff = NULL) +{ + ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0); + + ObjectSetString(0, name, OBJPROP_BMPFILE, 0, resourceOn); + if(resourceOff != NULL) ObjectSetString(0, name, OBJPROP_BMPFILE, 1, resourceOff); + ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 50); + ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 50); + ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER); + ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER); +} + +//+------------------------------------------------------------------+ +//| Helper function to create an image with inverted colors | +//+------------------------------------------------------------------+ +bool ResourceCreateInverted(const string resource, const string inverted) +{ + uint data[], width, height; + PRTF(ResourceReadImage(resource, data, width, height)); + for(int i = 0; i < ArraySize(data); ++i) + { + data[i] = data[i] ^ 0x00FFFFFF; + } + return PRTF(ResourceCreate(inverted, data, width, height, 0, 0, 0, + COLOR_FORMAT_ARGB_NORMALIZE)); +} + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const static string resource = "::Images\\pseudo.bmp"; + const static string inverted = resource + "_inv"; + const static string object = "object"; + + Print(""); // use empty line just as logging delimiter + + uint data[], width, height; + // check for resource existence + if(!PRTF(ResourceReadImage(resource, data, width, height))) + { + Print("Initial state: Creating 2 bitmaps"); + PRTF(ResourceCreate(resource, "\\Images\\dollar.bmp")); // try local "argb.bmp" as well + ResourceCreateInverted(resource, inverted); + } + else + { + Print("Resources (bitmaps) are detected"); + if(PRTF(ObjectFind(0, object) < 0)) + { + Print("Active state: Creating object to draw 2 bitmaps"); + ShowBitmap(object, resource, inverted); + } + else + { + Print("Cleanup state: Removing object and resources"); + PRTF(ObjectDelete(0, object)); + PRTF(ResourceFree(resource)); + PRTF(ResourceFree(inverted)); + } + } +} +//+------------------------------------------------------------------+ +/* + 1-st run output + + ResourceReadImage(resource,data,width,height)=false / RESOURCE_NOT_FOUND(4016) + Initial state: Creating 2 bitmaps + ResourceCreate(resource,\Images\dollar.bmp)=true / ok + ResourceReadImage(resource,data,width,height)=true / ok + ResourceCreate(inverted,data,width,height,0,0,0,COLOR_FORMAT_XRGB_NOALPHA)=true / ok + + 2-nd run output + + ResourceReadImage(resource,data,width,height)=true / ok + Resources (bitmaps) are detected + ObjectFind(0,object)<0=true / OBJECT_NOT_FOUND(4202) + Active state: Creating object to draw 2 bitmaps + + 3-rd run output + + ResourceReadImage(resource,data,width,height)=true / ok + Resources (bitmaps) are detected + ObjectFind(0,object)<0=false / ok + Cleanup state: Removing object and resources + ObjectDelete(0,object)=true / ok + ResourceFree(resource)=true / ok + ResourceFree(inverted)=true / ok + +*/ +//+------------------------------------------------------------------+ diff --git a/ResourceShapesDraw.mq5 b/ResourceShapesDraw.mq5 new file mode 100644 index 0000000..fd9f559 --- /dev/null +++ b/ResourceShapesDraw.mq5 @@ -0,0 +1,638 @@ +//+------------------------------------------------------------------+ +//| ResourceShapesDraw.mq5 | +//| Copyright (c) 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property script_show_inputs + +#include +#include +#include +#include + +#define FIGURES 21 + +//+------------------------------------------------------------------+ +//| Some of possible color mixing rules for overlapping Shapes | +//+------------------------------------------------------------------+ +enum COLOR_EFFECT +{ + PLAIN, + COMPLEMENT, + BLENDING_XOR, + DIMMING_SUM, + LIGHTEN_OR, +}; + +input color BackgroundColor = clrNONE; +input COLOR_EFFECT ColorEffect = PLAIN; +input bool SaveImage = false; + +//+------------------------------------------------------------------+ +//| Basic interface for drawing primitives | +//+------------------------------------------------------------------+ +interface Drawing +{ + void point(const float x1, const float y1, const uint pixel); + void line(const int x1, const int y1, const int x2, const int y2, const color clr); + void rect(const int x1, const int y1, const int x2, const int y2, const color clr); +}; + +//+------------------------------------------------------------------+ +//| Base shape class for drawing | +//+------------------------------------------------------------------+ +class Shape +{ +public: + class Registrator + { + static Registrator *regs[]; + int shapeCount; + public: + const string type; + Registrator(const string t) : type(t), shapeCount(0) + { + const int n = ArraySize(regs); + ArrayResize(regs, n + 1); + regs[n] = &this; + } + + static int getTypeCount() + { + return ArraySize(regs); + } + + static Registrator *get(const int i) + { + return regs[i]; + } + + int increment() + { + return ++shapeCount; + } + + int getShapeCount() const + { + return shapeCount; + } + + virtual Shape *create(const int px, const int py, const color back, + const int ¶meters[]) = 0; + }; + +protected: + int x, y; + color backgroundColor; + const string type; + string name; + + Shape(int px, int py, color back, string t) : + x(px), y(py), + backgroundColor(back), + type(t) + { + } + +public: + ~Shape() + { + ObjectDelete(0, name); + } + + virtual void draw(Drawing *drawing) = 0; + virtual void setup(const int ¶meters[]) = 0; + // TODO: read from and write to a file + //virtual void load(const int handle) = 0; + //virtual void save(const int handle) = 0; + + Shape *setColor(const color c) + { + backgroundColor = c; + return &this; + } + + Shape *moveX(const int _x) + { + x += _x; + return &this; + } + + Shape *moveY(const int _y) + { + y += _y; + return &this; + } + + Shape *move(const int _x, const int _y) + { + x += _x; + y += _y; + return &this; + } + + string toString() const + { + return type + " " + (string)x + " " + (string)y; + } +}; + +//+------------------------------------------------------------------+ +//| Templatized helper class for Shape-derived classes registration | +//+------------------------------------------------------------------+ +template +class MyRegistrator : public Shape::Registrator +{ +public: + MyRegistrator() : Registrator(TYPENAME(T)) + { + } + + virtual Shape *create(const int px, const int py, const color back, + const int ¶meters[]) override + { + T *temp = new T(px, py, back); + temp.setup(parameters); + return temp; + } +}; + +//+------------------------------------------------------------------+ +//| Rectangle shape | +//+------------------------------------------------------------------+ +class Rectangle : public Shape +{ + static MyRegistrator r; + +protected: + int dx, dy; // size (width, height) + + Rectangle(int px, int py, color back, string t) : + Shape(px, py, back, t), dx(1), dy(1) + { + } + +public: + Rectangle(int px, int py, color back) : + Shape(px, py, back, TYPENAME(this)), dx(1), dy(1) + { + name = TYPENAME(this) + (string)r.increment(); + } + + virtual void setup(const int ¶meters[]) override + { + if(ArraySize(parameters) < 2) + { + Print("Insufficient parameters for Rectangle"); + return; + } + dx = parameters[0]; + dy = parameters[1]; + } + + void draw(Drawing *drawing) override + { + drawing.rect(x - dx / 2, y - dy / 2, x + dx / 2, y + dy / 2, backgroundColor); + } +}; + +//+------------------------------------------------------------------+ +//| Square shape | +//+------------------------------------------------------------------+ +class Square : public Rectangle +{ + static MyRegistrator r; +public: + Square(int px, int py, color back) : + Rectangle(px, py, back, TYPENAME(this)) + { + name = TYPENAME(this) + (string)r.increment(); + } + + virtual void setup(const int ¶meters[]) override + { + if(ArraySize(parameters) < 1) + { + Print("Insufficient parameters for Square"); + return; + } + dx = dy = parameters[0]; + } + + void draw(Drawing *drawing) override + { + Rectangle::draw(drawing); + } +}; + +//+------------------------------------------------------------------+ +//| Ellipse shape | +//+------------------------------------------------------------------+ +class Ellipse : public Shape +{ + static MyRegistrator r; +protected: + int dx, dy; // large and small radiuses + + Ellipse(int px, int py, color back, string t) : + Shape(px, py, back, t), dx(1), dy(1) + { + } + +public: + Ellipse(int px, int py, color back) : + Shape(px, py, back, TYPENAME(this)), dx(1), dy(1) + { + name = TYPENAME(this) + (string)r.increment(); + } + + virtual void setup(const int ¶meters[]) override + { + if(ArraySize(parameters) < 2) + { + Print("Insufficient parameters for Ellipse"); + return; + } + dx = parameters[0]; // first radius + dy = parameters[1]; // second radius + } + + void draw(Drawing *drawing) override + { + // (x, y) is a center + // p0: x + dx, y + // p1: x - dx, y + // p2: x, y + dy + + const int hh = dy * dy; + const int ww = dx * dx; + const int hhww = hh * ww; + int x0 = dx; + int step = 0; + + // main horizontal diameter + drawing.line(x - dx, y, x + dx, y, backgroundColor); + + // smaller horizontal lines in upper and lower halves keep symmetry + for(int j = 1; j <= dy; j++) + { + for(int x1 = x0 - (step - 1); x1 > 0; --x1) + { + if(x1 * x1 * hh + j * j * ww <= hhww) + { + step = x0 - x1; + break; + } + } + x0 -= step; + + drawing.line(x - x0, y - j, x + x0, y - j, backgroundColor); + drawing.line(x - x0, y + j, x + x0, y + j, backgroundColor); + // TODO: edge smoothing by xx + // float xx = (float)sqrt((hhww - j * j * ww) / hh); + } + } +}; + +//+------------------------------------------------------------------+ +//| Circle shape | +//+------------------------------------------------------------------+ +class Circle : public Ellipse +{ + static MyRegistrator r; +public: + Circle(int px, int py, color back) : + Ellipse(px, py, back, TYPENAME(this)) + { + name = TYPENAME(this) + (string)r.increment(); + } + + virtual void setup(const int ¶meters[]) override + { + if(ArraySize(parameters) < 1) + { + Print("Insufficient parameters for Circle"); + return; + } + dx = dy = parameters[0]; + } + + void draw(Drawing *drawing) override + { + Ellipse::draw(drawing); + } +}; + +//+------------------------------------------------------------------+ +//| Triangle shape | +//+------------------------------------------------------------------+ +class Triangle: public Shape +{ + static MyRegistrator r; +protected: + int dx; // single side (equilateral) +public: + Triangle(int px, int py, color back) : + Shape(px, py, back, TYPENAME(this)), dx(1) + { + name = TYPENAME(this) + (string)r.increment(); + } + + virtual void setup(const int ¶meters[]) override + { + if(ArraySize(parameters) < 1) + { + Print("Insufficient parameters for Triangle"); + return; + } + dx = parameters[0]; + } + + virtual void draw(Drawing *drawing) override + { + if(dx <= 3) + { + drawing.point(x, y, ColorToARGB(backgroundColor)); + return; + } + + // (x, y) is a center + // R = a * sqrt(3) / 3 + // p0: x, y + R + // p1: x - R * cos(30), y - R * sin(30) + // p2: x + R * cos(30), y - R * sin(30) + // height by Pythagorean theorem: dx * dx = dx * dx / 4 + h * h + // sqrt(dx * dx * 3/4) = h + const double R = dx * sqrt(3) / 3; + const double H = sqrt(dx * dx * 3 / 4); + const double angle = H / (dx / 2); + + // main vertical line (triangle height) + const int base = y + (int)(R - H); + drawing.line(x, y + (int)R, x, base, backgroundColor); + + // go left and right sideways with shorter vertical lines, with symmetry + for(int j = 1; j <= dx / 2; ++j) + { + drawing.line(x - j, y + (int)(R - angle * j), x - j, base, backgroundColor); + drawing.line(x + j, y + (int)(R - angle * j), x + j, base, backgroundColor); + } + } +}; + +//+------------------------------------------------------------------+ +//| Our implementation of Drawing interface based on bitmap resource | +//+------------------------------------------------------------------+ +class MyDrawing: public Drawing +{ + const string object; // bitmap object + const string sheet; // resource + uint data[]; + int width, height; + AutoPtr shapes[]; + const uint bg; + COLOR_EFFECT xormode; + bool smoothing; + +public: + MyDrawing(const uint background = 0, const string s = NULL) : + object((s == NULL ? "Drawing" : s)), + sheet("::" + (s == NULL ? "D" + (string)ChartID() : s)), + bg(background), xormode(PLAIN), smoothing(false) + { + width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); + height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); + ArrayResize(data, width * height); + ArrayInitialize(data, background); + + ResourceCreate(sheet, data, width, height, 0, 0, width, COLOR_FORMAT_ARGB_NORMALIZE); + + ObjectCreate(0, object, OBJ_BITMAP_LABEL, 0, 0, 0); + ObjectSetInteger(0, object, OBJPROP_XDISTANCE, 0); + ObjectSetInteger(0, object, OBJPROP_YDISTANCE, 0); + ObjectSetInteger(0, object, OBJPROP_XSIZE, width); + ObjectSetInteger(0, object, OBJPROP_YSIZE, height); + ObjectSetString(0, object, OBJPROP_BMPFILE, sheet); + } + + ~MyDrawing() + { + ResourceFree(sheet); + ObjectDelete(0, object); + } + + string resource() const + { + return sheet; + } + + Shape *push(Shape *shape) + { + shapes[EXPAND(shapes)] = shape; + return shape; + } + + void setColorEffect(const COLOR_EFFECT x) + { + xormode = x; + } + + void draw() + { + for(int i = 0; i < ArraySize(shapes); ++i) + { + shapes[i][].draw(&this); + } + ResourceCreate(sheet, data, width, height, 0, 0, width, COLOR_FORMAT_ARGB_NORMALIZE); + ChartRedraw(); + } + + void shake() + { + ArrayInitialize(data, bg); + for(int i = 0; i < ArraySize(shapes); ++i) + { + shapes[i][].move(random(20) - 10, random(20) - 10); + } + } + + void _point(const int x1, const int y1, const uint pixel) + { + const int index = y1 * width + x1; + if(index >= 0 && index < ArraySize(data)) + { + switch(xormode) + { + case COMPLEMENT: + data[index] = (pixel ^ (1 - data[index])); // complementary + break; + case BLENDING_XOR: + data[index] = (pixel & 0xFF000000) | (pixel ^ data[index]); // blending (XOR) + break; + case DIMMING_SUM: + data[index] = (pixel + data[index]); // dimming (SUM) + break; + case LIGHTEN_OR: + data[index] = (pixel & 0xFF000000) | (pixel | data[index]); // lighten (OR) + break; + case PLAIN: + default: + data[index] = pixel; + } + } + } + + virtual void point(const float x1, const float y1, const uint pixel) override + { + const int x_main = (int)MathRound(x1); + const int y_main = (int)MathRound(y1); + _point(x_main, y_main, pixel); + + if(smoothing) // test implementation of antialiasing (used in diagonal lines) + { + const int factorx = (int)((x1 - x_main) * 255); + if(fabs(factorx) >= 10) + { + _point(x_main + factorx / fabs(factorx), y_main, ((int)fabs(factorx) << 24) | (pixel & 0xFFFFFF)); + } + const int factory = (int)((y1 - y_main) * 255); + if(fabs(factory) >= 10) + { + _point(x_main, y_main + factory / fabs(factory), ((int)fabs(factory) << 24) | (pixel & 0xFFFFFF)); + } + } + } + + virtual void line(const int x1, const int y1, const int x2, const int y2, const color clr) override + { + if(x1 == x2) rect(x1, y1, x1, y2, clr); + else if(y1 == y2) rect(x1, y1, x2, y1, clr); + else + { + smoothing = true; + const uint pixel = ColorToARGB(clr); + double angle = 1.0 * (y2 - y1) / (x2 - x1); + if(fabs(angle) < 1) // step by larger axis, that is x + { + const int sign = x2 > x1 ? +1 : -1; + for(int i = 0; i <= fabs(x2 - x1); ++i) + { + const float p = (float)(y1 + sign * i * angle); + point(x1 + sign * i, p, pixel); + } + } + else // step by y + { + const int sign = y2 > y1 ? +1 : -1; + for(int i = 0; i <= fabs(y2 - y1); ++i) + { + const float p = (float)(x1 + sign * i / angle); + point(p, y1 + sign * i, pixel); + } + } + smoothing = false; + } + } + + virtual void rect(const int x1, const int y1, const int x2, const int y2, const color clr) override + { + // line(x1, y1, x2, y2, clr); // debug + const uint pixel = ColorToARGB(clr); + for(int i = fmin(x1, x2); i <= fmax(x1, x2); ++i) + { + for(int j = fmin(y1, y2); j <= fmax(y1, y2); ++j) + { + point(i, j, pixel); + } + } + } +}; + +//+------------------------------------------------------------------+ +//| Static in-class variables | +//+------------------------------------------------------------------+ +static Shape::Registrator *Shape::Registrator::regs[] = {}; + +static MyRegistrator Rectangle::r; +static MyRegistrator Square::r; +static MyRegistrator Ellipse::r; +static MyRegistrator Circle::r; +static MyRegistrator Triangle::r; + +//+------------------------------------------------------------------+ +//| Return random number in a range | +//+------------------------------------------------------------------+ +int random(int range) +{ + return (int)(rand() / 32767.0 * range); +} + +//+------------------------------------------------------------------+ +//| Create a random shape | +//+------------------------------------------------------------------+ +Shape *addRandomShape() +{ + const int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); + const int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); + + const int n = random(Shape::Registrator::getTypeCount()); + + int cx = 1 + w / 4 + random(w / 2), cy = 1 + h / 4 + random(h / 2); + int clr = ((random(256) << 16) | (random(256) << 8) | random(256)); + + int custom[] = {1 + random(w / 4), 1 + random(h / 4)}; + return Shape::Registrator::get(n).create(cx, cy, clr, custom); +} + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + // adjust chart settings + ChartSetInteger(0, CHART_SHOW, false); + + MyDrawing raster(BackgroundColor != clrNONE ? ColorToARGB(BackgroundColor) : 0); + raster.setColorEffect(ColorEffect); + + // prepare a random set of shapes for drawing + for(int i = 0; i < FIGURES; ++i) + { + raster.push(addRandomShape()); + } + + raster.draw(); // show initial state + + const int n = Shape::Registrator::getTypeCount(); + for(int i = 0; i < n; ++i) + { + Print(Shape::Registrator::get(i).type, " ", + Shape::Registrator::get(i).getShapeCount()); + } + + // keep small random movement of shapes until user stops it + const ulong start = TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE); + while(!IsStopped() + && TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE) == start) + { + Sleep(250); + raster.shake(); + raster.draw(); + } + + if(SaveImage) + { + const string filename = EnumToString(ColorEffect) + ".bmp"; + if(ResourceSave(raster.resource(), filename)) + { + Print("Bitmap image saved: ", filename); + } + else + { + Print("Can't save image ", filename, ", ", E2S(_LastError)); + } + } + + ChartSetInteger(0, CHART_SHOW, true); +} +//+------------------------------------------------------------------+ diff --git a/ResourceText.mq5 b/ResourceText.mq5 new file mode 100644 index 0000000..446be5e --- /dev/null +++ b/ResourceText.mq5 @@ -0,0 +1,124 @@ +//+------------------------------------------------------------------+ +//| ResourceText.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Display text in different styles." +#property script_show_inputs + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +input string Font = "Arial"; // Font Name +input int Size = -240; // Size +input color Color = clrBlue; // Font Color +input color Background = clrNONE; // Background Color +input uint Seconds = 10; // Demo Time (seconds) + +enum ENUM_FONT_WEIGHTS +{ + _DONTCARE = FW_DONTCARE, + _THIN = FW_THIN, + _EXTRALIGHT = FW_EXTRALIGHT, + _LIGHT = FW_LIGHT, + _NORMAL = FW_NORMAL, + _MEDIUM = FW_MEDIUM, + _SEMIBOLD = FW_SEMIBOLD, + _BOLD = FW_BOLD, + _EXTRABOLD = FW_EXTRABOLD, + _HEAVY = FW_HEAVY, +}; + +int Random(const int limit) +{ + return rand() % limit; +} + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const uint weights[] = + { + FW_DONTCARE, + FW_THIN, + FW_EXTRALIGHT, // FW_ULTRALIGHT, + FW_LIGHT, + FW_NORMAL, // FW_REGULAR, + FW_MEDIUM, + FW_SEMIBOLD, // FW_DEMIBOLD, + FW_BOLD, + FW_EXTRABOLD, // FW_ULTRABOLD, + FW_HEAVY, // FW_BLACK + }; + + const int nw = sizeof(weights) / sizeof(uint); + + const uint rendering[] = + { + FONT_ITALIC, + FONT_UNDERLINE, + FONT_STRIKEOUT + }; + + const int nr = sizeof(rendering) / sizeof(uint); + + const string name = "FONT"; + const int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); + const int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); + + // on-chart object to hold bitmap resource + ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0); + // adjust the object dimensions + ObjectSetInteger(0, name, OBJPROP_XSIZE, w); + ObjectSetInteger(0, name, OBJPROP_YSIZE, h); + + uint data[]; + + // empty resource for binding with the object + ArrayResize(data, w * h); + // transparent background by default + ArrayInitialize(data, Background == clrNONE ? 0 : ColorToARGB(Background)); + ResourceCreate(name, data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_RAW); + ObjectSetString(0, name, OBJPROP_BMPFILE, "::" + name); + + const int step = h / (ArraySize(weights) + 2); + int cursor = 0; + + for(int weight = 0; weight < ArraySize(weights); ++weight) + { + // apply new style + const int r = Random(8); + uint render = 0; + for(int j = 0; j < 3; ++j) + { + if((bool)(r & (1 << j))) render |= rendering[j]; + } + TextSetFont(Font, Size, weights[weight] | render); + + // generate font description + const string text = Font + EnumToString((ENUM_FONT_WEIGHTS)weights[weight]); + + // draw the text on a separate line + cursor += step; + TextOut(text, w / 2, cursor, TA_CENTER | TA_TOP, data, w, h, + ColorToARGB(Color), COLOR_FORMAT_ARGB_RAW); + } + + // update the resource and the chart + ResourceCreate(name, data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_RAW); + ChartRedraw(); + + const uint timeout = GetTickCount() + Seconds * 1000; + while(!IsStopped() && GetTickCount() < timeout) + { + Sleep(1000); + } + + ObjectDelete(0, name); + ResourceFree("::" + name); +} +//+------------------------------------------------------------------+ diff --git a/ResourceTextAnchOrientation.mq5 b/ResourceTextAnchOrientation.mq5 new file mode 100644 index 0000000..5de59a7 --- /dev/null +++ b/ResourceTextAnchOrientation.mq5 @@ -0,0 +1,114 @@ +//+------------------------------------------------------------------+ +//| ResourceTextAnchOrientation.mq5 | +//| Copyright 2022, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property description "Draw many text examples with different anchor point and orientation." +#property script_show_inputs + +#include + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +input string Font = "Arial"; // Font Name +input int Size = -150; // Size +input int ExampleCount = 11; // Number of examples +input color Background = clrNONE; // Background Color +input uint Seconds = 10; // Demo Time (seconds) + +int Random(const int limit) +{ + return rand() % limit; +} + +enum ENUM_TEXT_ANCHOR +{ + LEFT_TOP = TA_LEFT | TA_TOP, + LEFT_VCENTER = TA_LEFT | TA_VCENTER, + LEFT_BOTTOM = TA_LEFT | TA_BOTTOM, + CENTER_TOP = TA_CENTER | TA_TOP, + CENTER_VCENTER = TA_CENTER | TA_VCENTER, + CENTER_BOTTOM = TA_CENTER | TA_BOTTOM, + RIGHT_TOP = TA_RIGHT | TA_TOP, + RIGHT_VCENTER = TA_RIGHT | TA_VCENTER, + RIGHT_BOTTOM = TA_RIGHT | TA_BOTTOM, +}; + +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() +{ + const ENUM_TEXT_ANCHOR anchors[] = + { + LEFT_TOP, + LEFT_VCENTER, + LEFT_BOTTOM, + CENTER_TOP, + CENTER_VCENTER, + CENTER_BOTTOM, + RIGHT_TOP, + RIGHT_VCENTER, + RIGHT_BOTTOM, + }; + + const int na = sizeof(anchors) / sizeof(uint); + + const string name = "FONT"; + const int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); + const int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); + + // on-chart object to hold bitmap resource + ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0); + // adjust the object dimensions + ObjectSetInteger(0, name, OBJPROP_XSIZE, w); + ObjectSetInteger(0, name, OBJPROP_YSIZE, h); + + // prepare empty buffer for resource for setting to the object + uint data[]; + ArrayResize(data, w * h); + // transparent background by default + ArrayInitialize(data, Background == clrNONE ? 0 : ColorToARGB(Background)); + ResourceCreate(name, data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE); + ObjectSetString(0, name, OBJPROP_BMPFILE, "::" + name); + + for(int i = 0; i < ExampleCount; ++i) + { + // apply random angle + const int angle = Random(360); + TextSetFont(Font, Size, 0, angle * 10); + + // get random coordinates and anchor point + const ENUM_TEXT_ANCHOR anchor = anchors[Random(na)]; + const int x = Random(w / 2) + w / 4; + const int y = Random(h / 2) + h / 4; + const color clr = ColorMix::HSVtoRGB(angle); + + // draw the bullet just at the binding point of the text + TextOut(ShortToString(0x2022), x, y, TA_CENTER | TA_VCENTER, data, w, h, + ColorToARGB(clr), COLOR_FORMAT_ARGB_NORMALIZE); + + // generate altered text + const string text = EnumToString(anchor) + "(" + (string)angle + CharToString(0xB0) + ")"; + + // draw the text + TextOut(text, x, y, anchor, data, w, h, + ColorToARGB(clr), COLOR_FORMAT_ARGB_NORMALIZE); + } + // update the resource and the chart + ResourceCreate(name, data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE); + ChartRedraw(); + + const uint timeout = GetTickCount() + Seconds * 1000; + while(!IsStopped() && GetTickCount() < timeout) + { + Sleep(1000); + } + + ObjectDelete(0, name); + ResourceFree("::" + name); +} +//+------------------------------------------------------------------+ diff --git a/TRIX ARROWS.mq5 b/TRIX ARROWS.mq5 new file mode 100644 index 0000000..27a8c2f Binary files /dev/null and b/TRIX ARROWS.mq5 differ diff --git a/ea_template.mq5 b/ea_template.mq5 new file mode 100644 index 0000000..d5c938f Binary files /dev/null and b/ea_template.mq5 differ diff --git a/history_deal.mq5 b/history_deal.mq5 new file mode 100644 index 0000000..3fe212b --- /dev/null +++ b/history_deal.mq5 @@ -0,0 +1,67 @@ +//+------------------------------------------------------------------+ +//| history_deal.mq5 | +//| Copyright 2010, MetaQuotes Software Corp. | +//| http://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2010, MetaQuotes Software Corp." +#property link "http://www.mql5.com" +#property version "1.00" +//+------------------------------------------------------------------+ +//| Включаем все классы, которые будут использоваться | +//+------------------------------------------------------------------+ +//--- Класс CDealInfo +#include +//+------------------------------------------------------------------+ +//| Создаем объект класса | +//+------------------------------------------------------------------+ +//--- Объект класса CDealInfo +CDealInfo mydeal; +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() + { +//--- Получаем все исторические сделки и их свойства + int buy=0; + int sell=0; + int deal_in=0; + int deal_out=0; + ulong d_ticket; +//--- Выбираем все исторические записи + if (HistorySelect(0,TimeCurrent())) + { + //--- Получаем общее количество сделок в истории + for (int j=HistoryDealsTotal()-1; j>=0; j--) + { + //--- Выбираем сделки по индексу + d_ticket = HistoryDealGetTicket(j); + if (d_ticket>0) + { + //--- Устанавливаем тикет сделки для работы с ней + mydeal.Ticket(d_ticket); + Print("Индекс сделки ",j," Тикет: ",mydeal.Ticket()," !"); + Print("Индекс сделки ",j," Время исполнения: ",TimeToString(mydeal.Time())," !"); + Print("Индекс сделки ",j," Цена: ",mydeal.Price()," !"); + Print("Индекс сделки ",j," Символ: ",mydeal.Symbol()," !"); + Print("Индекс сделки ",j," Описание типа сделки: ",mydeal.TypeDescription()," !"); + Print("Индекс сделки ", j ," Magic сделки: ", mydeal.Magic() ," !"); + Print("Индекс сделки ", j ," Время сделки: ", mydeal.Time() ," !"); + Print("Индекс сделки ",j," Начальный объем сделки: ",mydeal.Volume()," !"); + Print("Индекс сделки ",j," Направление сделки: ",mydeal.EntryDescription()," !"); + Print("Индекс сделки ",j," Прибыль сделки: ",mydeal.Profit()," !"); + // + if (mydeal.Entry() == DEAL_ENTRY_IN) deal_in++; + if (mydeal.Entry() == DEAL_ENTRY_OUT) deal_out++; + if (mydeal.DealType() == DEAL_TYPE_BUY) buy++; + if (mydeal.DealType() == DEAL_TYPE_SELL) sell++; + } + } + } +//--- Выводим статистику + Print("Общее количество сделок в истории:",HistoryDealsTotal()," !"); + Print("Количество сделок в направлении IN: ",deal_in); + Print("Количество сделок в направлении OUT: ",deal_out); + Print("Количество сделок на покупку: ",buy); + Print("Количество сделок на продажу: ",sell); + } +//+------------------------------------------------------------------+ diff --git a/history_order.mq5 b/history_order.mq5 new file mode 100644 index 0000000..3937871 --- /dev/null +++ b/history_order.mq5 @@ -0,0 +1,105 @@ +//+------------------------------------------------------------------+ +//| history_order.mq5 | +//| Copyright 2010, MetaQuotes Software Corp. | +//| http://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2010, MetaQuotes Software Corp." +#property link "http://www.mql5.com" +#property version "1.00" +//+------------------------------------------------------------------+ +//| Включаем все классы, которые будут использоваться | +//+------------------------------------------------------------------+ +//--- Класс CHistoryOrderInfo +#include +//+------------------------------------------------------------------+ +//| Создаем объект класса | +//+------------------------------------------------------------------+ +//--- Объект класса CHistoryOrderInfo +CHistoryOrderInfo myhistory; +//+------------------------------------------------------------------+ +//| Script program start function | +//+------------------------------------------------------------------+ +void OnStart() + { +//--- Получаем все исторические ордера и их свойства + int buystop=0; + int sellstop=0; + int buylimit=0; + int selllimit=0; + int buystoplimit=0; + int sellstoplimit=0; + int buy=0; + int sell=0; + + int s_started=0; + int s_placed=0; + int s_cancelled=0; + int s_partial=0; + int s_filled=0; + int s_rejected=0; + int s_expired=0; + + ulong o_ticket; +//--- Выбираем все исторические записи + if(HistorySelect(0,TimeCurrent())) //-- Все исторические ордера + { + //--- Получаем общее количество ордеров в истории + for(int j=HistoryOrdersTotal()-1; j>=0; j--) + { + //--- Выбираем ордер по индексу + o_ticket=HistoryOrderGetTicket(j); + if(o_ticket>0) + { + //--- Устанавливаем тикет ордера для дальнейшей работы с ним + myhistory.Ticket(o_ticket); + Print("Индекс ордера ",j," Тикет: ",myhistory.Ticket()," !"); + Print("Индекс ордера ",j," Время установки: ",TimeToString(myhistory.TimeSetup())," !"); + Print("Индекс ордера ",j," Цена открытия: ",myhistory.PriceOpen()," !"); + Print("Индекс ордера ",j," Символ: ",myhistory.Symbol()," !"); + Print("Индекс ордера ",j," Тип ордера: ",myhistory.OrderType()," !"); + Print("Индекс ордера ",j," Описание типа: ",myhistory.TypeDescription()," !"); + Print("Индекс ордера ",j," Magic: ",myhistory.Magic()," !"); + Print("Индекс ордера ",j," Время исполнения: ", myhistory.TimeDone() ," !"); + Print("Индекс ордера ",j," Начальный объем: ", myhistory.VolumeInitial() ," !"); + // + if(myhistory.OrderType() == ORDER_TYPE_BUY_STOP) buystop++; + if(myhistory.OrderType() == ORDER_TYPE_SELL_STOP) sellstop++; + if(myhistory.OrderType() == ORDER_TYPE_BUY) buy++; + if(myhistory.OrderType() == ORDER_TYPE_SELL) sell++; + if(myhistory.OrderType() == ORDER_TYPE_BUY_LIMIT) buylimit++; + if(myhistory.OrderType() == ORDER_TYPE_SELL_LIMIT) selllimit++; + if(myhistory.OrderType() == ORDER_TYPE_BUY_STOP_LIMIT) buystoplimit++; + if(myhistory.OrderType() == ORDER_TYPE_SELL_STOP_LIMIT) sellstoplimit++; + + if(myhistory.State() == ORDER_STATE_STARTED) s_started++; + if(myhistory.State() == ORDER_STATE_PLACED) s_placed++; + if(myhistory.State() == ORDER_STATE_CANCELED) s_cancelled++; + if(myhistory.State() == ORDER_STATE_PARTIAL) s_partial++; + if(myhistory.State() == ORDER_STATE_FILLED) s_filled++; + if(myhistory.State() == ORDER_STATE_REJECTED) s_rejected++; + if(myhistory.State() == ORDER_STATE_EXPIRED) s_expired++; + } + } + } +// Вывод статистики + Print("По типу ордеров"); + Print("Рыночных ордеров Buy: ",buy); + Print("Рыночных ордеров Sell: ",sell); + Print("Отложенных ордеров Buy Stop: ",buystop); + Print("Отложенных ордеров Sell Stop: ",sellstop); + Print("Отложенных ордеров Buy Limit: ",buylimit); + Print("Отложенных ордеров Sell Limit: ",selllimit); + Print("Отложенных ордеров Buy Stop Limit: ",buystoplimit); + Print("Отложенных ордеров Sell Stop Limit: ",sellstoplimit); + Print("Общее количество ордеров: ",HistoryOrdersTotal()," !"); + + Print("По статусу ордеров"); + Print("Проверены на корректность, но еще не приняты брокером: ",s_started); + Print("Принято: ",s_placed); + Print("Снято клиентом: ",s_cancelled); + Print("Выполнены частично: ",s_partial); + Print("Выполнены полностью: ",s_filled); + Print("Отклонены: ",s_rejected); + Print("Сняты по истечении срока действия ордера: ",s_expired); + } +//+------------------------------------------------------------------+ diff --git a/initial_data (1).mq5 b/initial_data (1).mq5 new file mode 100644 index 0000000..231a0e7 Binary files /dev/null and b/initial_data (1).mq5 differ diff --git a/mql5_standardclass_ea.mq5 b/mql5_standardclass_ea.mq5 new file mode 100644 index 0000000..732688e --- /dev/null +++ b/mql5_standardclass_ea.mq5 @@ -0,0 +1,488 @@ +//+------------------------------------------------------------------+ +//| mql5_standardclass_ea.mq5 | +//| Copyright 2010, MetaQuotes Software Corp. | +//| http://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2010, MetaQuotes Software Corp." +#property link "http://www.mql5.com" +#property version "1.00" +//+------------------------------------------------------------------+ +//| Включаемые файлы с классами, которые будут использованы | +//+------------------------------------------------------------------+ +//--- Класс CTrade +#include +//--- Класс CPositionInfo +#include +//--- Класс CAccountInfo +#include +//--- Класс CSymbolInfo +#include + +//+------------------------------------------------------------------+ +//| Входные параметры | +//+------------------------------------------------------------------+ +input int StopLoss=100; // Stop Loss +input int TakeProfit=240; // Take Profit +input int ADX_Period=15; // Период ADX +input int MA_Period=15; // Период Moving Average +input ulong EA_Magic=99977; // Magic Number советника +input double Adx_Min=24.0; // Минимальное значение ADX +input double Lot=0.1; // Количество лотов для торговли +input ulong dev=100; // Проскальзывание в пунктах +input long Trail_point=32; // Количество пунктов для увеличения TP/SL +input int Min_Bars = 20; // Минимальное количество баров, требуемое эксперту для торговли +input double TradePct = 25; // Процент свободной торговой маржи счета +//+------------------------------------------------------------------+ +//| Другие полезные параметры | +//+------------------------------------------------------------------+ +int adxHandle; //--- хэндл нашего индикатора ADX +int maHandle; //--- хэндл нашего индикатора Moving Average +double plsDI[],minDI[],adxVal[]; //--- динамические массивы для хранения значений +DI, -DI и ADX каждого бара +double maVal[]; //--- динамический массив для хранения значений индикатора Moving Average для каждого бара +double p_close; //--- переменная для хранения текущих значений цены закрытия бара +int STP,TKP; //--- будут использоваться для Stop Loss, Take Profit +double TPC; //--- будет использован для контроля маржи +//+------------------------------------------------------------------+ +//| Создание объектов классов | +//+------------------------------------------------------------------+ +//--- Объект класса СTrade +CTrade mytrade; +//--- Объект класса СPositionInfo +CPositionInfo myposition; +//--- Объект класса СAccountInfo +CAccountInfo myaccount; +//--- Объект класса СSymbolInfo +CSymbolInfo mysymbol; +//+------------------------------------------------------------------+ +//| Пользовательские функции | +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| Проверяет, может ли советник совершать торговые операции | +//+------------------------------------------------------------------+ +bool checkTrading() + { + bool can_trade=false; +//--- проверка того, синхронизирован ли терминал с сервером и т.д. + if(myaccount.TradeAllowed() && myaccount.TradeExpert() && mysymbol.IsSynchronized()) + { + //--- есть ли у нас достаточное количество баров? + int mbars=Bars(_Symbol,_Period); + if(mbars>Min_Bars) + { + can_trade=true; + } + } + return(can_trade); + } +//+------------------------------------------------------------------+ +//| Подтверждает, что маржи достаточно для открытия ордера | +//|------------------------------------------------------------------+ +bool ConfirmMargin(ENUM_ORDER_TYPE otype,double price) + { + bool confirm=false; + double lot_price=myaccount.MarginCheck(_Symbol,otype,Lot,price); //--- цена лота/кол-во требуемой маржи + double act_f_mag=myaccount.FreeMargin(); //--- кол-во свободной маржи счета +//--- проверяет, достаточно ли средств маржи + if(MathFloor(act_f_mag*TPC)>MathFloor(lot_price)) + { + confirm=true; + } + return(confirm); + } +//+------------------------------------------------------------------+ +//| Проверяет условия на покупку | +//+------------------------------------------------------------------+ +bool checkBuy() + { + bool dobuy=false; + if((maVal[0]>maVal[1]) && (maVal[1]>maVal[2]) && (p_close>maVal[1])) + { + //--- MA растет и цена закрытия предыдущего бара выше MA + if((adxVal[1]>Adx_Min) && (plsDI[1]>minDI[1])) + { + //--- значение ADX больше, чем минимально требуемое, и +DI больше, чем -DI + dobuy=true; + } + } + return(dobuy); + } +//+------------------------------------------------------------------+ +//| Проверяет условия на продажу | +//+------------------------------------------------------------------+ +bool checkSell() + { + bool dosell=false; + if((maVal[0]Adx_Min) && (minDI[1]>plsDI[1])) + { + //--- значение ADX больше, чем минимально требуемое, и -DI больше, чем +DI + dosell=true; + } + } + return(dosell); + } +//+------------------------------------------------------------------+ +//| Проверяет, нужно ли закрывать открытую позицию | +//+------------------------------------------------------------------+ +bool checkClosePos(string ptype,double Closeprice) + { + bool mark=false; + if(ptype=="BUY") + { + //--- нужно ли закрывать эту позицию? + if(ClosepricemaVal[1]) //--- цена закрытия предыдущего бара выше MA + { + mark=true; + } + } + return(mark); + } +//+------------------------------------------------------------------+ +//| Проверяет и если нужно, закрывает открытую позицию | +//+------------------------------------------------------------------+ +bool ClosePosition(string ptype,double clp) + { + bool marker=false; + + if(myposition.Select(_Symbol)==true) + { + if(myposition.Magic()==EA_Magic && myposition.Symbol()==_Symbol) + { + //--- Проверяем, нужно ли закрывать эту позицию + if(checkClosePos(ptype,clp)==true) + { + //--- закрываем эту позицию и проверяем, успешно ли она закрылась + if(mytrade.PositionClose(_Symbol)) + {//--- запрос успешно выполнен + Alert("Открытая позиция была успешно закрыта!!"); + marker=true; + } + else + { + Alert("Запрос на закрытие позиции не выполнен - ошибка: ",mytrade.ResultRetcodeDescription()); + } + } + } + } + return(marker); + } +//+------------------------------------------------------------------+ +//| Проверяет условия модификации открытой позиции | +//+------------------------------------------------------------------+ + bool CheckModify(string otype,double cprc) + { + bool check=false; + if(otype=="BUY") + { + if((maVal[2]maVal[1]) && (adxVal[1]>Adx_Min)) + { + check=true; + } + } + else if(otype=="SELL") + { + if((maVal[2]>maVal[1]) && (maVal[1]>maVal[0]) && (cprcAdx_Min)) + { + check=true; + } + } + return(check); + } +//+------------------------------------------------------------------+ +//| Изменяет параметры открытой позиции | +//+------------------------------------------------------------------+ + void Modify(string ptype,double stpl,double tkpf) + { + double ntp,nsl,pbid,pask; //--- новые Stop Loss, Take profit ,и цены Bid и Ask + long tsp=Trail_point; + if(_Digits==5 || _Digits==3) tsp=tsp*10; //--- учет 5/3 знаковых цен + long stplevel= mysymbol.StopsLevel(); //--- Stops Level + if(tsp adxmin, +DI > -DI +*/ + if(checkBuy()==true) + { + //--- есть ли открытая позиция на покупку? + if(Buy_opened) + { + Alert("У нас уже есть открытая позиция на покупку!!!"); + return; //--- Не открываем новую позицию на покупку + } + + double mprice=NormalizeDouble(mysymbol.Ask(),_Digits); //--- последняя цена ask + double stloss = NormalizeDouble(mysymbol.Ask() - STP*_Point,_Digits); //--- Stop Loss + double tprofit = NormalizeDouble(mysymbol.Ask()+ TKP*_Point,_Digits); //--- Take Profit + //--- проверяем маржу + if(ConfirmMargin(ORDER_TYPE_BUY,mprice)==false) + { + Alert("Недостаточно средств для совершения торговой операции с параметрами, которые установлены."); + return; + } + //--- открываем позицию на покупку (Buy) + if(mytrade.Buy(Lot,_Symbol,mprice,stloss,tprofit)) + //if(mytrade.PositionOpen(_Symbol,ORDER_TYPE_BUY,Lot,mprice,stloss,tprofit)) //--- Request is completed or order placed + { + Alert("Ордер на покупку успешно помещен с тикетом #",mytrade.ResultDeal(),"!!"); + } + else + { + Alert("Запрос на покупку с объемом:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", цена:", mytrade.RequestPrice(), " не выполнен -ошибка:",mytrade.ResultRetcodeDescription()); + return; + } + } +/* + 2. Проверка условий на продажу : MA падает, + предыдущая цена закрытия ниже находится ее, ADX > adxmin, -DI > +DI +*/ + if(checkSell()==true) + { + //--- есть ли открытая позиция на продажу? + if(Sell_opened) + { + Alert("У нас уже есть позиция на продажу!!!"); + return; //--- выходим и ждем нового бара + } + + double sprice=NormalizeDouble(mysymbol.Bid(),_Digits); //--- последняя цена Bid + double ssloss=NormalizeDouble(mysymbol.Bid()+STP*_Point,_Digits); //--- Stop Loss + double stprofit=NormalizeDouble(mysymbol.Bid()-TKP*_Point,_Digits); //--- Take Profit + //--- проверка маржи + if(ConfirmMargin(ORDER_TYPE_SELL,sprice)==false) + { + Alert("Недостаточно средств для совершения торговой операции с параметрами, которые установлены"); + return; + } + //--- открываем позицию на продажу и проверяем результат + if(mytrade.Sell(Lot,_Symbol,sprice,ssloss,stprofit)) + //if(mytrade.PositionOpen(_Symbol,ORDER_TYPE_SELL,Lot,sprice,ssloss,stprofit)) //---Request is completed or order placed + { + Alert("Ордер на продажу успешно помещен с тикетом #:",mytrade.ResultDeal(),"!!"); + } + else + { + Alert("Запрос на покупку с объемом:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", цена:", mytrade.RequestPrice(), " не выполнен - ошибка:",mytrade.ResultRetcodeDescription()); + return; + } + } + } +//+------------------------------------------------------------------+ \ No newline at end of file diff --git a/mql5_standardclass_ea_stoporders.mq5 b/mql5_standardclass_ea_stoporders.mq5 new file mode 100644 index 0000000..c79a7dc --- /dev/null +++ b/mql5_standardclass_ea_stoporders.mq5 @@ -0,0 +1,341 @@ +//+------------------------------------------------------------------+ +//| mql5_standardclass_ea_stoporders.mq5 | +//| Copyright 2010, MetaQuotes Software Corp. | +//| http://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2010, MetaQuotes Software Corp." +#property link "http://www.mql5.com" +#property version "1.00" +//+------------------------------------------------------------------+ +//| Включаемые файлы с классами, которые будут использованы | +//+------------------------------------------------------------------+ +//--- Объект класса CTrade +#include +//--- Объект класса CPositionInfo +#include +//--- Объект класса CSymbolInfo +#include +//--- Объект класса COrderInfo +#include +//+------------------------------------------------------------------+ +//| Входные параметры | +//+------------------------------------------------------------------+ +input int StopLoss=30; // Stop Loss +input int TakeProfit=60; // Take Profit +input int ADX_Period=14; // Период ADX +input int MA_Period=15; // Период Moving Average +input ulong EA_Magic=99977; // Magic Number советника +input double Adx_Min=25.0; // Минимальное значение ADX +input double Lot=0.1; // Количество лотов для торговли +input ulong dev=100; // Проскальзывание +//+------------------------------------------------------------------+ +//| Другие полезные параметры | +//+------------------------------------------------------------------+ +int adxHandle; //--- хэндл нашего индикатора ADX +int maHandle; //--- хэндл нашего индикатора Moving Average +double plsDI[],minDI[],adxVal[]; //--- динамические массивы для хранения значений +DI, -DI и ADX каждого бара +double maVal[]; //--- динамический массив для хранения значений индикатора Moving Average для каждого бара +double p_close; //--- переменная для хранения текущих значений цены закрытия бара +int STP,TKP; //--- будут использоваться для Stop Loss, Take Profit +double TPC; //--- будет использован для контроля маржи + +//--- определим структуру типа MqlRates, которую будем использовать в торговле +//--- для хранения цен, объемов и спреда каждого бара +MqlRates mrate[]; + +//+------------------------------------------------------------------+ +//| Создаем объекты классов | +//+------------------------------------------------------------------+ +//--- Объект класса CTrade +CTrade mytrade; +//--- Объект класса СPositionInfo +CPositionInfo myposition; +//--- Объект класса CSymbolInfo +CSymbolInfo mysymbol; +//--- Объект класса COrderInfo +COrderInfo myorder; +//+------------------------------------------------------------------+ +//| Пользовательские функции | +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| Проверяет условия на покупку | +//+------------------------------------------------------------------+ +bool checkBuy() + { + bool dobuy=false; + if((maVal[0]>maVal[1]) && (maVal[1]>maVal[2]) && (p_close>maVal[1])) + { + //--- MA растет и цена закрытия предыдущего бара выше MA + if((adxVal[1]>Adx_Min) && (plsDI[1]>minDI[1])) + { + //--- значение ADX больше, чем минимально требуемое, и +DI больше, чем -DI + dobuy=true; + } + } + return(dobuy); + } +//+------------------------------------------------------------------+ +//| Проверяет условия на продажу | +//+------------------------------------------------------------------+ +bool checkSell() + { + bool dosell=false; + if((maVal[0]Adx_Min) && (minDI[1]>plsDI[1])) + { + //--- значение ADX больше, чем минимально требуемое, и -DI больше, чем +DI + dosell=true; + } + } + return(dosell); + } +//+------------------------------------------------------------------+ +//| Считает общее количество ордеров, | +//| размещенных советником на данном символе | +//+------------------------------------------------------------------+ +int CountOrders() + { + int mark=0; + + for(int i=OrdersTotal()-1; i>=0; i--) + { + if(myorder.Select(OrderGetTicket(i))) + { + if(myorder.Magic()==EA_Magic && myorder.Symbol()==_Symbol) mark++; + } + } + return(mark); + } +//+------------------------------------------------------------------+ +//| Проверяет и удаляет отложенные ордера | +//+------------------------------------------------------------------+ +bool DeletePending() + { + bool marker=false; +//--- проверка всех отложенных ордеров + for(int i=OrdersTotal()-1; i>=0; i--) + { + if(myorder.Select(OrderGetTicket(i))) + { + if(myorder.Magic()==EA_Magic && myorder.Symbol()==_Symbol) + { + //--- проверим время ордера - оно должно быть меньше, чем время два бара назад + if(myorder.TimeSetup()3) + { + DeletePending(); + return; + } + //--- есть ли открытая позиция? + bool Buy_opened=false,Sell_opened=false; + if(myposition.Select(_Symbol)==true) //--- есть открытая позиция + { + if(myposition.PositionType()==POSITION_TYPE_BUY) + { + Buy_opened=true; //--- длинная (buy) позиция + return; //--- выходим и ждем нового бара + } + else if(myposition.PositionType()==POSITION_TYPE_SELL) + { + Sell_opened=true; //--- короткая (sell) позиция + return; //--- выходим и ждем нового бара + } + + } +/* + Проверка условий покупки : MA растет, + предыдущая цена закрытия больше ее, ADX > adxmin, +DI > -DI +*/ + if(checkBuy()==true) + { + Alert("Общее количество установленных отложенных ордеров: ",CountOrders(),"!!"); + //--- есть ли позиция на покупку? + if(Buy_opened) + { + Alert("У нас уже есть позиция на покупку!!!"); + return; //--- Не открываем новую позицию на покупку + } + //--- Цена покупки = bar 1 High + 2 pip + spread + int sprd=mysymbol.Spread(); + double bprice =mrate[1].high + 10*_Point + sprd*_Point; + double mprice=NormalizeDouble(bprice,_Digits); //--- Цена покупки + double stloss = NormalizeDouble(bprice - STP*_Point,_Digits); //--- Stop Loss + double tprofit = NormalizeDouble(bprice+ TKP*_Point,_Digits); //--- Take Profit + //--- Размещаем ордер Buy Stop + if(mytrade.BuyStop(Lot,mprice,_Symbol,stloss,tprofit)) + //if(mytrade.OrderOpen(_Symbol,ORDER_TYPE_BUY_STOP,Lot,0.0,bprice,stloss,tprofit,ORDER_TIME_GTC,0)) + { + //--- запрос выполнен или ордер размещен + Alert("Ордер Buy Stop был успешно размещен с тикетом #:",mytrade.ResultOrder(),"!!"); + return; + } + else + { + Alert("Запрос на размещение ордера BuyStop с объемом:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", цена:", mytrade.RequestPrice(), " не выполнен - ошибка:",mytrade.ResultRetcodeDescription()); + return; + } + } + +/* + 2. Проверка условий на продажу : MA падает, + предыдущая цена закрытия ниже находится ее, ADX > adxmin, -DI > +DI +*/ + if(checkSell()==true) + { + Alert("Общее количество установленных отложенных ордеров:",CountOrders(),"!!"); + //--- есть ли открытая позиция на продажу? + if(Sell_opened) + { + Alert("У нас уже есть позиция на продажу!!!"); + return; //--- выходим и ждем нового бара + } + //--- цена продажи = bar 1 Low - 2 pip + double sprice=mrate[1].low-10*_Point; + double slprice=NormalizeDouble(sprice,_Digits); //--- цена продажи + double ssloss=NormalizeDouble(sprice+STP*_Point,_Digits); //--- Stop Loss + double stprofit=NormalizeDouble(sprice-TKP*_Point,_Digits); //--- Take Profit + //--- Размещаем ордер Sell Stop + if(mytrade.SellStop(Lot,slprice,_Symbol,ssloss,stprofit)) + //if(mytrade.OrderOpen(_Symbol,ORDER_TYPE_SELL_STOP,Lot,0.0,slprice,ssloss,stprofit,ORDER_TIME_GTC,0)) + { + //--- Запрос выполнен или ордер размещен + Alert("Отложенный ордер Sell Stop успешно размещен с тикетом #",mytrade.ResultOrder(),"!!"); + return; + } + else + { + Alert("Запрос о размещении отложенного ордера Sell Stop с объемом:",mytrade.RequestVolume(), ", sl:", mytrade.RequestSL(),", tp:",mytrade.RequestTP(), ", цена:", mytrade.RequestPrice(), " не выполнен - ошибка:",mytrade.ResultRetcodeDescription()); + return; + } + } + } +//+------------------------------------------------------------------+ \ No newline at end of file diff --git a/my_first_ea.mq5 b/my_first_ea.mq5 new file mode 100644 index 0000000..9b2a96f --- /dev/null +++ b/my_first_ea.mq5 @@ -0,0 +1,278 @@ +//+------------------------------------------------------------------+ +//| My_First_EA.mq5 | +//| Copyright 2010, MetaQuotes Software Corp. | +//| http://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2010, MetaQuotes Software Corp." +#property link "http://www.mql5.com" +#property version "1.00" +//--- input parameters +input int StopLoss=30; // Stop Loss +input int TakeProfit=100; // Take Profit +input int ADX_Period=8; // ADX Period +input int MA_Period=8; // Moving Average Period +input int EA_Magic=12345; // EA Magic Number +input double Adx_Min=22.0; // Minimum ADX Value +input double Lot=0.1; // Lots to Trade +//--- Other parameters +int adxHandle; // handle for our ADX indicator +int maHandle; // handle for our Moving Average indicator +double plsDI[],minDI[],adxVal[]; // Dynamic arrays to hold the values of +DI, -DI and ADX values for each bars +double maVal[]; // Dynamic array to hold the values of Moving Average for each bars +double p_close; // Variable to store the close value of a bar +int STP, TKP; // To be used for Stop Loss & Take Profit values +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { +//--- Get handle for ADX indicator + adxHandle=iADX(NULL,0,ADX_Period); +//--- Get the handle for Moving Average indicator + maHandle=iMA(_Symbol,_Period,MA_Period,0,MODE_EMA,PRICE_CLOSE); +//--- What if handle returns Invalid Handle + if(adxHandle<0 || maHandle<0) + { + Alert("Error Creating Handles for indicators - error: ",GetLastError(),"!!"); + return(-1); + } + +//--- Let us handle currency pairs with 5 or 3 digit prices instead of 4 + STP = StopLoss; + TKP = TakeProfit; + if(_Digits==5 || _Digits==3) + { + STP = STP*10; + TKP = TKP*10; + } + return(0); + } +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { +//--- Release our indicator handles + IndicatorRelease(adxHandle); + IndicatorRelease(maHandle); + } +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() + { +//--- Do we have enough bars to work with + if(Bars(_Symbol,_Period)<60) // if total bars is less than 60 bars + { + Alert("We have less than 60 bars, EA will now exit!!"); + return; + } + +// We will use the static Old_Time variable to serve the bar time. +// At each OnTick execution we will check the current bar time with the saved one. +// If the bar time isn't equal to the saved time, it indicates that we have a new tick. + + static datetime Old_Time; + datetime New_Time[1]; + bool IsNewBar=false; + +// copying the last bar time to the element New_Time[0] + int copied=CopyTime(_Symbol,_Period,0,1,New_Time); + if(copied>0) // ok, the data has been copied successfully + { + if(Old_Time!=New_Time[0]) // if old time isn't equal to new bar time + { + IsNewBar=true; // if it isn't a first call, the new bar has appeared + if(MQL5InfoInteger(MQL5_DEBUGGING)) Print("We have new bar here ",New_Time[0]," old time was ",Old_Time); + Old_Time=New_Time[0]; // saving bar time + } + } + else + { + Alert("Error in copying historical times data, error =",GetLastError()); + ResetLastError(); + return; + } + +//--- EA should only check for new trade if we have a new bar + if(IsNewBar==false) + { + return; + } + +//--- Do we have enough bars to work with + int Mybars=Bars(_Symbol,_Period); + if(Mybars<60) // if total bars is less than 60 bars + { + Alert("We have less than 60 bars, EA will now exit!!"); + return; + } + +//--- Define some MQL5 Structures we will use for our trade + MqlTick latest_price; // To be used for getting recent/latest price quotes + MqlTradeRequest mrequest; // To be used for sending our trade requests + MqlTradeResult mresult; // To be used to get our trade results + MqlRates mrate[]; // To be used to store the prices, volumes and spread of each bar + ZeroMemory(mrequest); // Initialization of mrequest structure +/* + Let's make sure our arrays values for the Rates, ADX Values and MA values + is store serially similar to the timeseries array +*/ +// the rates arrays + ArraySetAsSeries(mrate,true); +// the ADX DI+values array + ArraySetAsSeries(plsDI,true); +// the ADX DI-values array + ArraySetAsSeries(minDI,true); +// the ADX values arrays + ArraySetAsSeries(adxVal,true); +// the MA-8 values arrays + ArraySetAsSeries(maVal,true); + + +//--- Get the last price quote using the MQL5 MqlTick Structure + if(!SymbolInfoTick(_Symbol,latest_price)) + { + Alert("Error getting the latest price quote - error:",GetLastError(),"!!"); + return; + } + +//--- Get the details of the latest 3 bars + if(CopyRates(_Symbol,_Period,0,3,mrate)<0) + { + Alert("Error copying rates/history data - error:",GetLastError(),"!!"); + ResetLastError(); + return; + } + +//--- Copy the new values of our indicators to buffers (arrays) using the handle + if(CopyBuffer(adxHandle,0,0,3,adxVal)<0 || CopyBuffer(adxHandle,1,0,3,plsDI)<0 + || CopyBuffer(adxHandle,2,0,3,minDI)<0) + { + Alert("Error copying ADX indicator Buffers - error:",GetLastError(),"!!"); + ResetLastError(); + return; + } + if(CopyBuffer(maHandle,0,0,3,maVal)<0) + { + Alert("Error copying Moving Average indicator buffer - error:",GetLastError()); + ResetLastError(); + return; + } +//--- we have no errors, so continue +//--- Do we have positions opened already? + bool Buy_opened=false; // variable to hold the result of Buy opened position + bool Sell_opened=false; // variables to hold the result of Sell opened position + + if(PositionSelect(_Symbol)==true) // we have an opened position + { + if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) + { + Buy_opened=true; //It is a Buy + } + else if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) + { + Sell_opened=true; // It is a Sell + } + } + +// Copy the bar close price for the previous bar prior to the current bar, that is Bar 1 + p_close=mrate[1].close; // bar 1 close price + +/* + 1. Check for a long/Buy Setup : MA-8 increasing upwards, + previous price close above it, ADX > 22, +DI > -DI +*/ +//--- Declare bool type variables to hold our Buy Conditions + bool Buy_Condition_1=(maVal[0]>maVal[1]) && (maVal[1]>maVal[2]); // MA-8 Increasing upwards + bool Buy_Condition_2 = (p_close > maVal[1]); // previuos price closed above MA-8 + bool Buy_Condition_3 = (adxVal[0]>Adx_Min); // Current ADX value greater than minimum value (22) + bool Buy_Condition_4 = (plsDI[0]>minDI[0]); // +DI greater than -DI + +//--- Putting all together + if(Buy_Condition_1 && Buy_Condition_2) + { + if(Buy_Condition_3 && Buy_Condition_4) + { + // any opened Buy position? + if(Buy_opened) + { + Alert("We already have a Buy Position!!!"); + return; // Don't open a new Buy Position + } + ZeroMemory(mrequest); + mrequest.action = TRADE_ACTION_DEAL; // immediate order execution + mrequest.price = NormalizeDouble(latest_price.ask,_Digits); // latest ask price + mrequest.sl = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss + mrequest.tp = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take Profit + mrequest.symbol = _Symbol; // currency pair + mrequest.volume = Lot; // number of lots to trade + mrequest.magic = EA_Magic; // Order Magic Number + mrequest.type = ORDER_TYPE_BUY; // Buy Order + mrequest.type_filling = ORDER_FILLING_FOK; // Order execution type + mrequest.deviation=100; // Deviation from current price + //--- send order + OrderSend(mrequest,mresult); + // get the result code + if(mresult.retcode==10009 || mresult.retcode==10008) //Request is completed or order placed + { + Alert("A Buy order has been successfully placed with Ticket#:",mresult.order,"!!"); + } + else + { + Alert("The Buy order request could not be completed -error:",GetLastError()); + ResetLastError(); + return; + } + } + } +/* + 2. Check for a Short/Sell Setup : MA-8 decreasing downwards, + previous price close below it, ADX > 22, -DI > +DI +*/ +//--- Declare bool type variables to hold our Sell Conditions + bool Sell_Condition_1 = (maVal[0]Adx_Min); // Current ADX value greater than minimum (22) + bool Sell_Condition_4 = (plsDI[0]