diff --git a/EMACrossOverSkirmish/main.mq5 b/EMACrossOverSkirmish/main.mq5 new file mode 100644 index 0000000..2ecebfe --- /dev/null +++ b/EMACrossOverSkirmish/main.mq5 @@ -0,0 +1,456 @@ +//+------------------------------------------------------------------+ +//| ScoringTrade.mq5 | +//| Generated by ChatGPT | +//| | +//+------------------------------------------------------------------+ +#property strict +#include + +// Input parameters +input int MagicNumber = 42; +input int scoreThreshold = 5200; // Score threshold for trade entry +input int slopeThreshold = 93; // EMA slope threshold +input double maxScore = 7900; // Max score value for clamping +input int cooldownMinutes = 18; // Cooldown period in minutes (37 minutes) +input int tradeCooldownMinutes = 24; // Trade debounce cooldown period (5 minutes) +input ENUM_TIMEFRAMES emaTimeFrame = PERIOD_H1; // EMA Timeframe +input double delayClampAbsolute = 1690; +input int emaPeriod = 64; // EMA period +input double crossOverStep = 950; +input double slopeThresholdStep = 635; +input double emaDistanceStep = 150; +input double emaDecayStep = 0; +input double decayMultiplier = 0.08; // Decay multiplier +input double distanceThreshold = 28.5; // Set your distance threshold (adjust as necessary) +input double atrMultiplier = 7.6; // Multiplier for dynamic SL and TP calculation +input double TrailingStop = 5; +input bool ApplyTrailingStop = true; +input int maxCrossoverTrades = 4; // Maximum number of trades per crossover +input double max_drawdown = 0.1; // Maximum drawdown percentage +input bool resetCrossoverTradeOnDistance = false; +input int resetCrossoverNumber = 0; +input double minimumLotSize = 0.01; +input int maxTimeInPosition = 9; +input int tradeLengthThreshold = 98; +input int reverseTP = 32; +input int reverseLotSizeMultiplier = 15; +input int secondaryPositionHoldTime = 32; +// 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) { + Print("Failed to copy EMA values. Error code: ", GetLastError()); + 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; + + // 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 + Print("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 + Print("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 + + if (MathAbs(priceToEmaDistance) > distanceThreshold) { + if (priceToEmaDistance > 0) { // Bullish (price above EMA) + currentScore += emaDistanceStep; + Print("Bullish distance score added. Price: ", closeCurr, " EMA: ", emaCurrentValue); + } + else if (priceToEmaDistance < 0) { // Bearish (price below EMA) + currentScore -= emaDistanceStep; + Print("Bearish distance score added. Price: ", closeCurr, " EMA: ", emaCurrentValue); + } + } + 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) { + Print("Failed to get ATR values. Error code: ", GetLastError()); + 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) { + Print("maxCrossover"); + Print(crossoverTradeCount); + // Open buy position with dynamic SL and TP + trade.SetExpertMagicNumber(MagicNumber); + if (trade.Buy(lotSize, Symbol(), currentPrice, Bid - dynamicSL, 0)) { + Print("Buy order executed with score: ", currentScore); + 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) { + Print("maxCrossover"); + Print(crossoverTradeCount); + // Open sell position with dynamic SL and TP + trade.SetExpertMagicNumber(MagicNumber); + if (trade.Sell(lotSize, Symbol(), currentPrice, Ask + dynamicSL, 0)) { + Print("Sell order executed with score: ", currentScore); + crossoverTradeCount++; // Increment trade count + lastTradeTime = TimeCurrent(); // Update the last trade time + } + } + } + } else { + Print("Trade skipped due to debounce: ", currentScore); + } + + // Check existing positions for profit and place reverse trade if needed + CheckPositions(); +} + +//+------------------------------------------------------------------+ +//| Check existing positions for profit and place reverse trade if needed | +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| Check existing positions for duration 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(); + Print("All positions closed due to exceeding secondaryPositionHoldTime"); + 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))) { + Print("Reversal sell order executed with increased lot size"); + } 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))) { + Print("Reversal buy order executed with increased lot size"); + } 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); + Print("Single position with volume equal to lotSize * reverseLotSizeMultiplier closed"); + } + + // 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); + Print("Original buy position closed due to double down stop loss."); + } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { + trade.PositionClose(ticket); + Print("Original sell position closed due to double down stop loss."); + } + } + } +} +//+------------------------------------------------------------------+ +//| 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); + Print("Buy position closed at score crossover."); + } + else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { + trade.PositionClose(ticket); + Print("Sell position closed at score crossover."); + } + } + } +} + +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 + { + Print("MN " + magicNumber); + trade.PositionClose(ticket); + } + } + } + else + { + int errorCode = GetLastError(); + Print("aaaa PositionSelect failed with error code: ", errorCode); + } + } +} + +//+------------------------------------------------------------------+ +//| 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 +} diff --git a/EMACrossOverSkirmish/test-balance.jpg b/EMACrossOverSkirmish/test-balance.jpg new file mode 100644 index 0000000..cbff813 Binary files /dev/null and b/EMACrossOverSkirmish/test-balance.jpg differ diff --git a/README.md b/README.md index 383d3ad..3c38cc1 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,70 @@ A strategy that combines RSI (Relative Strength Index) divergence detection with | Equity Drawdown Relative | 15.85% ($250.78) | **Balance Sheet:** -![Balance Sheet](RSIDivergenceRebound/test-balance.jpg) +![RSI Balance Sheet](RSIDivergenceRebound/test-balance.jpg) + +### 2. EMA Crossover Skirmish +A strategy that uses Exponential Moving Average (EMA) crossovers with advanced scoring and position management. + +**Key Features:** +- EMA crossover detection +- Advanced scoring system +- Trailing stop management +- Position scaling and reversal capabilities + +**Strategy Settings:** +- Symbol: XAUUSD +- Period: H1 (2021.01.01 - 2025.04.11) +- Magic Number: 42 +- Score Threshold: 5200 +- Slope Threshold: 93 +- Max Score: 7900.0 +- Cooldown Minutes: 18 +- Trade Cooldown Minutes: 24 +- EMA Time Frame: 16385 +- EMA Period: 64 +- Cross Over Step: 950.0 +- Slope Threshold Step: 635.0 +- EMA Distance Step: 150.0 +- ATR Multiplier: 7.6 +- Trailing Stop: 5.0 +- Max Crossover Trades: 4 +- Max Drawdown: 10% +- Minimum Lot Size: 0.01 +- Max Time in Position: 9 hours +- Trade Length Threshold: 98 +- Reverse TP: 32 +- Reverse Lot Size Multiplier: 15 +- Secondary Position Hold Time: 32 + +**Performance Metrics:** +| Metric | Value | +|--------|-------| +| Total Net Profit | $388.52 | +| Gross Profit | $391.65 | +| Gross Loss | -$3.13 | +| Profit Factor | 125.13 | +| Recovery Factor | 11.66 | +| Expected Payoff | $0.72 | +| Sharpe Ratio | 41.49 | +| AHPR | 1.0006 (0.06%) | +| GHPR | 1.0006 (0.06%) | + +**Trade Statistics:** +| Statistic | Value | +|-----------|-------| +| History Quality | 82% real ticks | +| Total Bars | 25,283 | +| Total Ticks | 165,999,507 | +| Balance Drawdown Absolute | $0.00 | +| Equity Drawdown Absolute | $0.10 | +| Balance Drawdown Maximal | $0.69 (0.05%) | +| Equity Drawdown Maximal | $33.32 (2.56%) | +| Balance Drawdown Relative | 0.05% ($0.69) | +| Equity Drawdown Relative | 2.92% ($30.23) | + +**Balance Sheet:** +![EMA Balance Sheet](EMACrossOverSkirmish/test-balance.jpg) ## Technical Details Each EA is implemented in MQL5 and includes: