//+------------------------------------------------------------------+ //| ScoringTrade.mq5 | //| Generated by ChatGPT | //| | //+------------------------------------------------------------------+ #property strict #include // Input parameters input int MagicNumber = 42; // Unique identifier for this EA's trades input int scoreThreshold = 15000; // Minimum score required to enter a trade (increased for BTC's larger price movements) input int slopeThreshold = 3500; // Minimum EMA slope value to consider trend significant (increased for BTC's steeper trends) input double maxScore = 25000; // Maximum allowed score before clamping (prevents excessive trade signals) input int cooldownMinutes = 18; // Minimum time between crossover signals (prevents over-trading) input int tradeCooldownMinutes = 44; // Minimum time between consecutive trades (trade debounce period) input ENUM_TIMEFRAMES emaTimeFrame = PERIOD_H1; // Timeframe for EMA calculation (1-hour candles) input double delayClampAbsolute = 5000; // Score threshold for applying decay (prevents score from growing too large) input int emaPeriod = 139; // Number of periods for EMA calculation (longer period for smoother trend) input double crossOverStep = 2500; // Score adjustment when price crosses EMA (increased for BTC's larger moves) input double slopeThresholdStep = 2000; // Score adjustment for significant slope changes (increased for BTC) input double emaDistanceStep = 500; // Score adjustment for price distance from EMA (increased for BTC) input double emaDecayStep = 0; // Score decay rate when no significant signals (0 means no decay) input double decayMultiplier = 0.08; // Multiplier applied to score when above delayClampAbsolute input double distanceThreshold = 7100; // Minimum price-EMA distance to trigger score adjustment (increased for BTC) input double atrMultiplier = 3.9; // Multiplier for dynamic stop loss calculation based on ATR input double TrailingStop = 10; // Distance in points for trailing stop loss input bool ApplyTrailingStop = true; // Enable/disable trailing stop functionality input int maxCrossoverTrades = 14; // Maximum number of trades allowed per crossover signal input double max_drawdown = 0.1; // Maximum allowed drawdown as percentage of account balance input bool resetCrossoverTradeOnDistance = false; // Reset trade count when price moves beyond distance threshold input int resetCrossoverNumber = 0; // Number of trades to reset to when resetCrossoverTradeOnDistance is true input double minimumLotSize = 0.01; // Minimum trade size allowed input int maxTimeInPosition = 1; // Maximum time in hours to hold a position input int tradeLengthThreshold = 31; // Time in minutes before considering a reverse trade input int reverseTP = 707; // Take profit level for reverse trades (increased for BTC) input int reverseLotSizeMultiplier = 4; // Multiplier for lot size in reverse trades input int secondaryPositionHoldTime = 75; // Maximum time in minutes to hold a secondary position // Global variables int emaHandle; // EMA handle double prevScore = 0; // Previous score double currentScore = 0; // Current score double emaPrevValue = 0; // Previous EMA value double emaCurrentValue = 0; // Current EMA value double emaSlope = 0; // EMA slope value CTrade trade; // Trading object datetime lastCrossoverTime = 0; // Time of last crossover datetime lastTradeTime = 0; // Time of last trade int crossoverTradeCount = 0; // Count of trades after each crossover //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Create EMA handle (e.g., 14-period EMA on the closing price) emaHandle = iMA(Symbol(), emaTimeFrame, emaPeriod, 0, MODE_EMA, PRICE_CLOSE); if (emaHandle == INVALID_HANDLE) { Print("Failed to create EMA handle"); return INIT_FAILED; } return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Release the EMA handle if (emaHandle != INVALID_HANDLE) { ExpertRemove(); } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Buffer to hold the EMA values double emaBuffer[]; // Get dynamic lot size based on current balance and max drawdown double lotSize = CalculateLotSize(); if(lotSize < minimumLotSize) { lotSize = minimumLotSize; } // Get the current Ask and Bid prices double Ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double Bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); // Copy the last 2 EMA values (current and previous) int copied = CopyBuffer(emaHandle, 0, 0, 2, emaBuffer); if (copied < 2) { return; } // Get the current and previous EMA values emaPrevValue = emaBuffer[1]; // Previous EMA value (index 1) emaCurrentValue = emaBuffer[0]; // Current EMA value (index 0) // Calculate the EMA slope (change in EMA values) emaSlope = - (emaCurrentValue - emaPrevValue) * 100; Print("EMA Slope: ", emaSlope); // Check for price action crossover with EMA double closePrev = iClose(Symbol(), Period(), 1); // Close of previous bar double closeCurr = iClose(Symbol(), Period(), 0); // Close of current bar // Check if enough time has passed for the cooldown (cooldownMinutes) if (TimeCurrent() - lastCrossoverTime >= cooldownMinutes * 60) { if (closePrev < emaPrevValue && closeCurr > emaCurrentValue) { // Bullish crossover currentScore += crossOverStep; crossoverTradeCount = 0; // Reset trade count after new crossover lastCrossoverTime = TimeCurrent(); // Update the last crossover time } else if (closePrev > emaPrevValue && closeCurr < emaCurrentValue) { // Bearish crossover currentScore -= crossOverStep; crossoverTradeCount = 0; // Reset trade count after new crossover lastCrossoverTime = TimeCurrent(); // Update the last crossover time } } // Check EMA slope if (emaSlope > slopeThreshold) { // Positive slope currentScore += slopeThresholdStep; } else if (emaSlope < -slopeThreshold) { // Negative slope currentScore -= slopeThresholdStep; } else { if (MathAbs(currentScore) > delayClampAbsolute) { currentScore *= decayMultiplier; } } if(ApplyTrailingStop) { ApplyTrailingStop(); } // Calculate distance to EMA and adjust score double priceToEmaDistance = closeCurr - emaCurrentValue; // Distance between the current price and the EMA Print("Price to EMA Distance: ", priceToEmaDistance); if (MathAbs(priceToEmaDistance) > distanceThreshold) { if (priceToEmaDistance > 0) { // Bullish (price above EMA) currentScore += emaDistanceStep; } else if (priceToEmaDistance < 0) { // Bearish (price below EMA) currentScore -= emaDistanceStep; } } else { if (currentScore > 0) { currentScore -= emaDecayStep; } else { currentScore += emaDecayStep; } } // Close all positions if score crosses zero if ((prevScore > 0 && currentScore <= 0) || (prevScore < 0 && currentScore >= 0)) { Close_Position_MN(MagicNumber); } // Update the previous score prevScore = currentScore; if (crossoverTradeCount > maxCrossoverTrades) { return; } // Debounce check: Ensure enough time has passed since the last trade if (TimeCurrent() - lastTradeTime >= tradeCooldownMinutes * 60) { // Calculate ATR (Average True Range) for stop loss calculation double atrArray[]; int atrPeriod = 14; // ATR period (can be adjusted) int copied = CopyBuffer(iATR(Symbol(), Period(), atrPeriod), 0, 0, 1, atrArray); if (copied < 1) { return; } // Get the current price (using Bid price) double currentPrice = Bid; // Get ATR value double atrValue = atrArray[0]; // Latest ATR value // Get the minimum stop level and freeze level for the symbol int stopLevel = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL); int freezeLevel = SymbolInfoInteger(Symbol(), SYMBOL_TRADE_FREEZE_LEVEL); // Calculate the minimum stop loss in price units (converted from pips) double minStopLoss = stopLevel * SymbolInfoDouble(Symbol(), SYMBOL_POINT); double minFreezeLevel = freezeLevel * SymbolInfoDouble(Symbol(), SYMBOL_POINT); // Dynamic Stop Loss and Take Profit calculation based on ATR double dynamicSL = atrValue * atrMultiplier; double dynamicTP = atrValue * atrMultiplier; // Adjust SL and TP if they are smaller than the minimum stop level dynamicSL = MathMax(dynamicSL, minStopLoss); dynamicTP = MathMax(dynamicTP, dynamicSL); // Ensure TP is at least the same as SL // Trade logic based on the score if (currentScore > scoreThreshold) { // Buy signal if (PositionSelect(Symbol()) == false || !PositionGetInteger(POSITION_MAGIC) == MagicNumber && crossoverTradeCount < maxCrossoverTrades) { // Open buy position with dynamic SL and TP trade.SetExpertMagicNumber(MagicNumber); if (trade.Buy(lotSize, Symbol(), currentPrice, Bid - dynamicSL, 0)) { crossoverTradeCount++; // Increment trade count lastTradeTime = TimeCurrent(); // Update the last trade time } } } else if (currentScore < -scoreThreshold) { // Sell signal if (PositionSelect(Symbol()) == false || !PositionGetInteger(POSITION_MAGIC) == MagicNumber && crossoverTradeCount < maxCrossoverTrades) { // Open sell position with dynamic SL and TP trade.SetExpertMagicNumber(MagicNumber); if (trade.Sell(lotSize, Symbol(), currentPrice, Ask + dynamicSL, 0)) { crossoverTradeCount++; // Increment trade count lastTradeTime = TimeCurrent(); // Update the last trade time } } } } // Check existing positions for profit and place reverse trade if needed CheckPositions(); } //+------------------------------------------------------------------+ //| Check existing positions for profit and place reverse trade if needed | //+------------------------------------------------------------------+ void CheckPositions() { // Check if there are any open positions if (PositionsTotal() > 0) { // Check if there are exactly 2 open positions if (PositionsTotal() == 2) { for (int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { datetime openTime = PositionGetInteger(POSITION_TIME); int tradeLength = TimeCurrent() - openTime; // Trade duration in seconds // Check if the trade has been open for more than the secondaryPositionHoldTime if (tradeLength > secondaryPositionHoldTime * 60) { // Convert threshold to seconds // Close all positions CloseAllPositions(); return; // Exit the function after closing all positions } } } } else if (PositionsTotal() < 2) { for (int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { double profit = PositionGetDouble(POSITION_PROFIT); datetime openTime = PositionGetInteger(POSITION_TIME); int tradeLength = TimeCurrent() - openTime; // Trade duration in seconds // Check if the trade has been open for more than the tradeLengthThreshold if (tradeLength > tradeLengthThreshold * 60) { // Convert threshold to seconds double lotSize = PositionGetDouble(POSITION_VOLUME); double newLotSize = lotSize * reverseLotSizeMultiplier; // 10 times the original lot size crossoverTradeCount = maxCrossoverTrades + 1; // Place a reverse trade if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { trade.SetExpertMagicNumber(MagicNumber); if (trade.Sell(newLotSize, Symbol(), SymbolInfoDouble(Symbol(), SYMBOL_BID))) { } else { Print("Failed to execute reversal sell order"); } } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { trade.SetExpertMagicNumber(MagicNumber); if (trade.Buy(newLotSize, Symbol(), SymbolInfoDouble(Symbol(), SYMBOL_ASK))) { } else { Print("Failed to execute reversal buy order"); } } } // Close the trade if profit meets the take profit level if (profit >= reverseTP) { Close_Position_MN(MagicNumber); CloseAllPositions(); } // Check if there is only one position and its volume is lotSize * reverseLotSizeMultiplier if (PositionsTotal() == 1 && PositionGetDouble(POSITION_VOLUME) == minimumLotSize * reverseLotSizeMultiplier) { trade.PositionClose(ticket); } // Get the current Ask and Bid prices double Ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double Bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); // Check if the double down trade is exited by stop loss if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && PositionGetDouble(POSITION_SL) > 0 && Bid <= PositionGetDouble(POSITION_SL)) { // Close the original trade CloseOriginalTrade(); } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetDouble(POSITION_SL) > 0 && Ask >= PositionGetDouble(POSITION_SL)) { // Close the original trade CloseOriginalTrade(); } } } } } } // Function to close the original trade void CloseOriginalTrade() { for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { trade.PositionClose(ticket); } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { trade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ //| Function to close all positions | //+------------------------------------------------------------------+ void CloseAllPositions() { // Loop through all positions and close them for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { trade.PositionClose(ticket); } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { trade.PositionClose(ticket); } } } } void ApplyTrailingStop() { for(int i=PositionsTotal()-1; i>=0; i--) { string symbol = PositionGetSymbol(i); ulong PositionTicket = PositionGetTicket(i); long trade_type = PositionGetInteger(POSITION_TYPE); if(!PositionGetInteger(POSITION_MAGIC) == MagicNumber) { return; } double POINT = SymbolInfoDouble( symbol, SYMBOL_POINT ); int DIGIT = (int) SymbolInfoInteger( symbol, SYMBOL_DIGITS ); if(trade_type == 0) { double Bid = NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_BID),DIGIT); if(Bid-PositionGetDouble(POSITION_PRICE_OPEN) > NormalizeDouble(POINT * TrailingStop,DIGIT)) { if(PositionGetDouble(POSITION_SL) < NormalizeDouble(Bid - POINT * TrailingStop,DIGIT)) { trade.PositionModify(PositionTicket,NormalizeDouble(Bid - POINT * TrailingStop,DIGIT),PositionGetDouble(POSITION_TP)); } } } if(trade_type == 1) { double Ask = NormalizeDouble(SymbolInfoDouble(symbol,SYMBOL_ASK),DIGIT); if((PositionGetDouble(POSITION_PRICE_OPEN) - Ask) > NormalizeDouble( POINT * TrailingStop,DIGIT)) { if((PositionGetDouble(POSITION_SL) > NormalizeDouble(Ask + POINT * TrailingStop,DIGIT)) || (PositionGetDouble(POSITION_SL)==0)) { trade.PositionModify(PositionTicket,NormalizeDouble(Ask + POINT * TrailingStop,DIGIT),PositionGetDouble(POSITION_TP)); } } } } } void Close_Position_MN(ulong magicNumber) { int total = PositionsTotal(); for(int i = total - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); // Use PositionSelect by symbol instead of ticket string symbol = PositionGetSymbol(i); if(PositionSelect(symbol)) { if (PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetInteger(POSITION_TICKET) == ticket) { if(symbol == _Symbol) // Verify the symbol { trade.PositionClose(ticket); } } } } } //+------------------------------------------------------------------+ //| Calculate the dynamic lot size based on max drawdown | //+------------------------------------------------------------------+ double CalculateLotSize() { double balance = AccountInfoDouble(ACCOUNT_BALANCE); // Get account balance double allowedDrawdown = balance * max_drawdown; // Calculate allowed drawdown in account currency double baseDrawdownPerLot = 150; // Assumed drawdown per 0.01 lots as per backtest // Calculate lot size based on maximum drawdown double lotSize = (allowedDrawdown / baseDrawdownPerLot) * 0.01; return NormalizeDouble(lotSize, 2); // Normalize lot size to 2 decimal places }