//+------------------------------------------------------------------+ //| Scalper.mq5 | //| Copyright 2026, MasterOfPuppets | //| https://forge.mql5.io/masterofpuppets/mql5 | //+------------------------------------------------------------------+ #include #include #include #include #include #property copyright "Copyright 2026, MasterOfPuppets" #property link "https://forge.mql5.io/masterofpuppets/mql5" #property version "1.00" //+------------------------------------------------------------------+ //| Classes | //+------------------------------------------------------------------+ class Indexes { public: int data[]; Indexes() { ArrayFree(data); } ~Indexes() { ArrayFree(data); } }; //+------------------------------------------------------------------+ //| Enums | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Key to Action enum | //| ASCII codes for keys | //| https://www.w3.org/2002/09/tests/keys.html | //+------------------------------------------------------------------+ enum Action { BUY = 'B', CLOSE_ALL_POSITIONS = 'Q', CLOSE_EXPERT_POSITIONS = 'C', DEFEND_ALL_POSITIONS = 'F', DEFEND_EXPERT_POSITIONS = 'D', EXIT_SCALPER = 'E', REVERSE_ALL_POSITIONS = 'R', REVERSE_EXPERT_POSITIONS = 'A', SELL = 'S', UNKNOWN = -1 }; enum ActionKey { A = 'A', B = 'B', C = 'C', D = 'D', E = 'E', F = 'F', G = 'G', H = 'H', I = 'I', J = 'J', K = 'K', L = 'L', M = 'M', N = 'N', O = 'O', P = 'P', Q = 'Q', R = 'R', S = 'S', T = 'T', U = 'U', V = 'V', W = 'W', X = 'X', Y = 'Y', Z = 'Z', _1 = '1', _2 = '2', _3 = '3', _4 = '4', _5 = '5', _6 = '6', _7 = '7', _8 = '8', _9 = '9', _0 = '0' }; //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input double CONTRACTS = 0.01; input int INDENT_SIZE = 144; input int LAST_PROFITS_SIZE = 10; input double STOP_LOSS = 7.0; input string SOUND_FILE_NAME = "ok.wav"; input double TAKE_PROFIT = 0.0; input group "Action keys" input ActionKey buyActionKey = B; // BUY input ActionKey closeAllPositionsActionKey = Q; // CLOSE_ALL_POSITIONS input ActionKey closeExpertPositionsActionKey = C; // CLOSE_EXPERT_POSITIONS input ActionKey defendAllPositionsActionKey = F; // DEFEND_ALL_POSITIONS input ActionKey defendExpertPositionsActionKey = D; // DEFEND_EXPERT_POSITIONS input ActionKey exitScalperActionKey = E; // EXIT_SCALPER input ActionKey reverseAllPositionsActionKey = R; // REVERSE_ALL_POSITIONS input ActionKey reverseExpertPositionsActionKey = A; // REVERSE_EXPERT_POSITIONS input ActionKey sellActionKey = S; // SELL //+------------------------------------------------------------------+ //| Constants | //+------------------------------------------------------------------+ const string INDENT_FORMAT = StringFormat("%%%is\n", INDENT_SIZE); const ulong MAGIC = 1234567; const string MESSAGE_BUY = "BUY "; const string MESSAGE_SELL = "SELL "; //+------------------------------------------------------------------+ //| Interfaces | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Modifier interface | //+------------------------------------------------------------------+ interface Modifier { void Modify(); }; //+------------------------------------------------------------------+ //| Structs | //+------------------------------------------------------------------+ struct LastProfit { string message; double profit; string suffix; }; //+------------------------------------------------------------------+ //| Variables | //+------------------------------------------------------------------+ CHashMap actions; CHashMap actionNames; CHashMap keyToIndexesMap; string lastProfit; LastProfit lastProfits[]; CPositionInfo positionInfo; CSymbolInfo symbolInfo; double totalLastProfit; CTrade trade; //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int32_t id, const long &lparam, const double &dparam, const string &sparam) { if(id != CHARTEVENT_KEYDOWN) { return; } int action; actions.TryGetValue(lparam, action); switch(action) { case Action::BUY: case Action::SELL: Deal(action); break; case Action::CLOSE_ALL_POSITIONS: CloseAllPositions(); break; case Action::CLOSE_EXPERT_POSITIONS: CloseExpertPositions(); break; case Action::DEFEND_ALL_POSITIONS: DefendAllPositions(); break; case Action::DEFEND_EXPERT_POSITIONS: DefendExpertPositions(); break; case Action::EXIT_SCALPER: ExpertRemove(); PlaySound(SOUND_FILE_NAME); break; case Action::REVERSE_ALL_POSITIONS: ReverseAllPositions(); break; case Action::REVERSE_EXPERT_POSITIONS: ReverseExpertPositions(); break; } } //+------------------------------------------------------------------+ //| Deal function | //+------------------------------------------------------------------+ void Deal(int action) { ENUM_ORDER_TYPE orderType; double price; double deltaStopLoss; double deltaTakeProfit; symbolInfo.RefreshRates(); switch(action) { case Action::BUY: orderType = ENUM_ORDER_TYPE::ORDER_TYPE_BUY; price = symbolInfo.Ask(); deltaStopLoss = -STOP_LOSS; deltaTakeProfit = TAKE_PROFIT; break; case Action::SELL: orderType = ENUM_ORDER_TYPE::ORDER_TYPE_SELL; price = symbolInfo.Bid(); deltaStopLoss = STOP_LOSS; deltaTakeProfit = -TAKE_PROFIT; break; default: return; } Print(EnumToString((Action) action)); double stopLoss = IsZero(STOP_LOSS) ? 0.0 : symbolInfo.NormalizePrice(price + deltaStopLoss); double takeProfit = IsZero(TAKE_PROFIT) ? 0.0 : symbolInfo.NormalizePrice(price + deltaTakeProfit); trade.PositionOpen(_Symbol, orderType, CONTRACTS, price, stopLoss, takeProfit); PlaySound(SOUND_FILE_NAME); } //+------------------------------------------------------------------+ //| ClosePositionModifier Modifier class | //+------------------------------------------------------------------+ class ClosePositionModifier : public Modifier { private: bool m_withExpert; public: ClosePositionModifier(bool withExpert = false) { m_withExpert = withExpert; } void Modify() { if(m_withExpert && positionInfo.Magic() != MAGIC) { return; } trade.PositionClose(positionInfo.Ticket()); string message; switch(positionInfo.PositionType()) { case ENUM_POSITION_TYPE::POSITION_TYPE_BUY: message = MESSAGE_BUY; break; case ENUM_POSITION_TYPE::POSITION_TYPE_SELL: message = MESSAGE_SELL; break; } LastProfit lastProfitStruct; lastProfitStruct.message = message; lastProfitStruct.profit = positionInfo.Profit(); AddLastProfit(lastProfitStruct); CalculateTotalLastProfit(); SetLastProfit(); } }; //+------------------------------------------------------------------+ //| Add last profit function | //+------------------------------------------------------------------+ void AddLastProfit(LastProfit &lastProfitStruct) { if(lastProfits.Size() == LAST_PROFITS_SIZE) { ArrayRemove(lastProfits, 0, 1); } ArrayResize(lastProfits, lastProfits.Size() + 1); lastProfits[lastProfits.Size() - 1] = lastProfitStruct; } //+------------------------------------------------------------------+ //| Calculate total last profit function | //+------------------------------------------------------------------+ void CalculateTotalLastProfit() { totalLastProfit = 0.0; for(int i = 0; i < (int) lastProfits.Size(); i++) { totalLastProfit += lastProfits[i].profit; } } //+------------------------------------------------------------------+ //| Set last profit function | //+------------------------------------------------------------------+ void SetLastProfit() { lastProfit = "\n"; for(int i = 0; i < (int) lastProfits.Size(); i++) { lastProfit += StringFormat(INDENT_FORMAT, lastProfits[i].message + lastProfits[i].suffix + DoubleToString(lastProfits[i].profit, 2)); } lastProfit += StringFormat(INDENT_FORMAT, DoubleToString(totalLastProfit, 2)); } //+------------------------------------------------------------------+ //| Close all positions function | //+------------------------------------------------------------------+ void CloseAllPositions() { ClosePositionModifier closePositionModifier; Modify(EnumToString(Action::CLOSE_ALL_POSITIONS), &closePositionModifier); } //+------------------------------------------------------------------+ //| Close expert positions function | //+------------------------------------------------------------------+ void CloseExpertPositions() { ClosePositionModifier closePositionModifier(true); Modify(EnumToString(Action::CLOSE_EXPERT_POSITIONS), &closePositionModifier); } //+------------------------------------------------------------------+ //| DefendModifier Modifier class | //+------------------------------------------------------------------+ class DefendModifier : public Modifier { private: bool m_withExpert; public: DefendModifier(bool withExpert = false) { this.m_withExpert = withExpert; } void Modify() { if(m_withExpert && positionInfo.Magic() != MAGIC) { return; } double deltaStopLoss = 0.0; switch(positionInfo.PositionType()) { case ENUM_POSITION_TYPE::POSITION_TYPE_BUY: deltaStopLoss = STOP_LOSS; break; case ENUM_POSITION_TYPE::POSITION_TYPE_SELL: deltaStopLoss = -STOP_LOSS; break; } trade.PositionModify(positionInfo.Ticket(), symbolInfo.NormalizePrice(positionInfo.StopLoss() + deltaStopLoss), positionInfo.TakeProfit()); } }; //+------------------------------------------------------------------+ //| Defend all positions function | //+------------------------------------------------------------------+ void DefendAllPositions() { DefendModifier defendModifier; Modify(EnumToString(Action::DEFEND_ALL_POSITIONS), &defendModifier); } //+------------------------------------------------------------------+ //| Defend expert positions function | //+------------------------------------------------------------------+ void DefendExpertPositions() { DefendModifier defendModifier(true); Modify(EnumToString(Action::DEFEND_EXPERT_POSITIONS), &defendModifier); } //+------------------------------------------------------------------+ //| Modify function | //+------------------------------------------------------------------+ void Modify(string message, Modifier* modifier) { Print(message); ModifyPositions(modifier); PlaySound(SOUND_FILE_NAME); } //+------------------------------------------------------------------+ //| Modify positions function | //+------------------------------------------------------------------+ void ModifyPositions(Modifier* modifier) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(positionInfo.SelectByIndex(i)) { modifier.Modify(); } } } //+------------------------------------------------------------------+ //| Reverse all positions function | //+------------------------------------------------------------------+ void ReverseAllPositions() { ReversePositions(true); } //+------------------------------------------------------------------+ //| Reverse expert positions function | //+------------------------------------------------------------------+ void ReverseExpertPositions() { ReversePositions(false); } //+------------------------------------------------------------------+ //| Reverse positions function | //+------------------------------------------------------------------+ void ReversePositions(bool allPositions) { Action action; if(allPositions) { action = Action::REVERSE_ALL_POSITIONS; } else { action = Action::REVERSE_EXPERT_POSITIONS; } Print(EnumToString(action)); if(positionInfo.SelectByIndex(0)) { switch(positionInfo.PositionType()) { case ENUM_POSITION_TYPE::POSITION_TYPE_BUY: action = Action::SELL; break; case ENUM_POSITION_TYPE::POSITION_TYPE_SELL: action = Action::BUY; break; } } if(allPositions) { CloseAllPositions(); } else { CloseExpertPositions(); } if(action != Action::UNKNOWN) { Deal(action); } } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(!symbolInfo.Name(_Symbol)) { return(INIT_FAILED); } if(!IsKeysCorrect()) { return(INIT_PARAMETERS_INCORRECT); } EventSetTimer(1); trade.SetExpertMagicNumber(MAGIC); InitActions(); InitActionNames(); InitLastProfits(); Print(Usage()); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Is keys correct function | //+------------------------------------------------------------------+ bool IsKeysCorrect() { string names[9]; names[0] = EnumToString(Action::BUY); names[1] = EnumToString(Action::CLOSE_ALL_POSITIONS); names[2] = EnumToString(Action::CLOSE_EXPERT_POSITIONS); names[3] = EnumToString(Action::DEFEND_ALL_POSITIONS); names[4] = EnumToString(Action::DEFEND_EXPERT_POSITIONS); names[5] = EnumToString(Action::EXIT_SCALPER); names[6] = EnumToString(Action::REVERSE_ALL_POSITIONS); names[7] = EnumToString(Action::REVERSE_EXPERT_POSITIONS); names[8] = EnumToString(Action::SELL); AddIndexesToMap(buyActionKey, 0); AddIndexesToMap(closeAllPositionsActionKey, 1); AddIndexesToMap(closeExpertPositionsActionKey, 2); AddIndexesToMap(defendAllPositionsActionKey, 3); AddIndexesToMap(defendExpertPositionsActionKey, 4); AddIndexesToMap(exitScalperActionKey, 5); AddIndexesToMap(reverseAllPositionsActionKey, 6); AddIndexesToMap(reverseExpertPositionsActionKey, 7); AddIndexesToMap(sellActionKey, 8); string error; int keys[]; Indexes* indexes[]; keyToIndexesMap.CopyTo(keys, indexes); for(int i = 0; i < (int) keys.Size(); i++) { int key = keys[i]; if(indexes[i].data.Size() > 1) { string mappedActionNames; for(int j = 0; j < (int) indexes[i].data.Size(); j++) { if(j > 0) { mappedActionNames += ", "; } mappedActionNames += names[indexes[i].data[j]]; } error += StringFormat("Error: duplicated key [%s] for %s\n", CharToString((uchar) key), mappedActionNames); } } ClearMemory(); if(error.Length() > 0) { Alert(error); return false; } return true; } //+------------------------------------------------------------------+ //| Add indexes to map function | //+------------------------------------------------------------------+ void AddIndexesToMap(int key, int index) { Indexes* indexes; if(!keyToIndexesMap.TryGetValue(key, indexes)) { indexes = new Indexes(); keyToIndexesMap.Add(key, indexes); } int size = (int) indexes.data.Size(); ArrayResize(indexes.data, size + 1); indexes.data[size] = index; } //+------------------------------------------------------------------+ //| Clear memory function | //+------------------------------------------------------------------+ void ClearMemory() { if(keyToIndexesMap.Count() > 0) { int keys[]; Indexes* indexes[]; keyToIndexesMap.CopyTo(keys, indexes); for(int i = 0; i < (int) indexes.Size(); i++) { if(CheckPointer(indexes[i]) == POINTER_DYNAMIC) { delete indexes[i]; } } } keyToIndexesMap.Clear(); } //+------------------------------------------------------------------+ //| Actions initialization function | //+------------------------------------------------------------------+ void InitActions() { actions.Add(buyActionKey, Action::BUY); actions.Add(closeAllPositionsActionKey, Action::CLOSE_ALL_POSITIONS); actions.Add(closeExpertPositionsActionKey, Action::CLOSE_EXPERT_POSITIONS); actions.Add(defendAllPositionsActionKey, Action::DEFEND_ALL_POSITIONS); actions.Add(defendExpertPositionsActionKey, Action::DEFEND_EXPERT_POSITIONS); actions.Add(exitScalperActionKey, Action::EXIT_SCALPER); actions.Add(reverseAllPositionsActionKey, Action::REVERSE_ALL_POSITIONS); actions.Add(reverseExpertPositionsActionKey, Action::REVERSE_EXPERT_POSITIONS); actions.Add(sellActionKey, Action::SELL); } //+------------------------------------------------------------------+ //| Action names initialization function | //+------------------------------------------------------------------+ void InitActionNames() { actionNames.Add(buyActionKey, EnumToString(Action::BUY)); actionNames.Add(closeAllPositionsActionKey, EnumToString(Action::CLOSE_ALL_POSITIONS)); actionNames.Add(closeExpertPositionsActionKey, EnumToString(Action::CLOSE_EXPERT_POSITIONS)); actionNames.Add(defendAllPositionsActionKey, EnumToString(Action::DEFEND_ALL_POSITIONS)); actionNames.Add(defendExpertPositionsActionKey, EnumToString(Action::DEFEND_EXPERT_POSITIONS)); actionNames.Add(exitScalperActionKey, EnumToString(Action::EXIT_SCALPER)); actionNames.Add(reverseAllPositionsActionKey, EnumToString(Action::REVERSE_ALL_POSITIONS)); actionNames.Add(reverseExpertPositionsActionKey, EnumToString(Action::REVERSE_EXPERT_POSITIONS)); actionNames.Add(sellActionKey, EnumToString(Action::SELL)); } //+------------------------------------------------------------------+ //| Last profits initialization function | //+------------------------------------------------------------------+ void InitLastProfits() { datetime now = TimeCurrent(); datetime yesterday = now - 86400; // PERIOD_D1 * 60 HistorySelect(yesterday, now); int lastProfitIndex = 0; LastProfit historyLastProfits[]; for(int i = HistoryDealsTotal() - 1; i >= 0; i--) { if(lastProfitIndex >= LAST_PROFITS_SIZE) { break; } ulong dealTicket = HistoryDealGetTicket(i); double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); if(IsZero(dealProfit)) { continue; } long dealReason = HistoryDealGetInteger(dealTicket, DEAL_REASON); if(dealReason == DEAL_REASON_CLIENT || dealReason == DEAL_REASON_EXPERT || DEAL_REASON_MOBILE || dealReason == DEAL_REASON_SL || dealReason == DEAL_REASON_TP || DEAL_REASON_WEB) { LastProfit lastProfitStruct; lastProfitStruct.message = GetMessage(dealTicket); lastProfitStruct.profit = dealProfit; lastProfitStruct.suffix = GetSuffix(dealReason); ArrayResize(historyLastProfits, historyLastProfits.Size() + 1); historyLastProfits[lastProfitIndex] = lastProfitStruct; lastProfitIndex++; } } for(int i = (int) historyLastProfits.Size() - 1; i >= 0; i--) { AddLastProfit(historyLastProfits[i]); } CalculateTotalLastProfit(); SetLastProfit(); } //+------------------------------------------------------------------+ //| Get message function | //+------------------------------------------------------------------+ string GetMessage(ulong dealTicket) { switch((int) HistoryDealGetInteger(dealTicket, DEAL_TYPE)) { case ENUM_DEAL_TYPE::DEAL_TYPE_BUY: return MESSAGE_SELL; case ENUM_DEAL_TYPE::DEAL_TYPE_SELL: return MESSAGE_BUY; } return ""; } //+------------------------------------------------------------------+ //| Get suffix function | //+------------------------------------------------------------------+ string GetSuffix(long dealReason) { if(dealReason == DEAL_REASON_SL) { return "(stop loss) "; } if(dealReason == DEAL_REASON_TP) { return "(take profit) "; } return ""; } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { string comment = StringFormat(INDENT_FORMAT, TimeToString(TimeLocal(), TIME_MINUTES | TIME_SECONDS)); comment += lastProfit; comment += Usage(); Comment(comment); } //+------------------------------------------------------------------+ //| Trade transaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { if(trans.type != TRADE_TRANSACTION_DEAL_ADD) { return; } ulong dealTicket = trans.deal; if(!HistoryDealSelect(dealTicket)) { return; } long dealReason = HistoryDealGetInteger(dealTicket, DEAL_REASON); if(dealReason == DEAL_REASON_SL || dealReason == DEAL_REASON_TP) { LastProfit lastProfitStruct; lastProfitStruct.message = GetMessage(dealTicket); lastProfitStruct.profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); lastProfitStruct.suffix = GetSuffix(dealReason); AddLastProfit(lastProfitStruct); CalculateTotalLastProfit(); SetLastProfit(); } PlaySound(SOUND_FILE_NAME); } //+------------------------------------------------------------------+ //| Usage function | //+------------------------------------------------------------------+ string Usage() { return Usage(buyActionKey) + Usage(closeAllPositionsActionKey) + Usage(closeExpertPositionsActionKey) + Usage(defendAllPositionsActionKey) + Usage(defendExpertPositionsActionKey) + Usage(exitScalperActionKey) + Usage(reverseAllPositionsActionKey) + Usage(reverseExpertPositionsActionKey) + Usage(sellActionKey); } //+------------------------------------------------------------------+ //| Action usage function | //+------------------------------------------------------------------+ string Usage(ActionKey actionKey) { string actionName; actionNames.TryGetValue(actionKey, actionName); return StringFormat("[%c] %s\n", actionKey, actionName); } //+------------------------------------------------------------------+ //| Is zero function | //+------------------------------------------------------------------+ bool IsZero(double value) { return MathAbs(value) < DBL_EPSILON ? true : false; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| ToDo | //| - pending order | //| - trailing stop | //| - objects Drag'n'Drp | //| - error processing | //+------------------------------------------------------------------+