This cBot follows a mean reversion strategy, also known as counter trend. It’s best suited for ranging forex markets, therefore it cannot handle long trending moves.
The strategy:
When the current price deviates enough pips from the MA, we enter a high probability reversal zone. We open a trade expecting the price to return to the MA. If the price still goes against us, we keep adding positions each ‘x’ pips (hence the grid) while incrementing the position size. All positions are closed when the price touches the MA.
This strategy was tailored for the current EURUSD market conditions.
The results:
EURUSD backtest of this year.
The Code:
using System; using System.Net; using cAlgo.API; using cAlgo.API.Indicators; using cAlgo.API.Internals; using cAlgo.Indicators; namespace cAlgo.Robots { [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] public class MAGrid : Robot { [Parameter("MA Periods", DefaultValue = 50)] public int MAPeriods { get; set; } [Parameter("MA Type", DefaultValue = MovingAverageType.Exponential)] public MovingAverageType MAType { get; set; } [Parameter("Entry Distance", DefaultValue = 50)] public int EntryDistancePips { get; set; } [Parameter(DefaultValue = 14)] public int AtrPeriods { get; set; } [Parameter(DefaultValue = 3)] public double GridAtrMultiplier { get; set; } [Parameter(DefaultValue = 80)] public double MaxEquityDrawdownPercent { get; set; } [Parameter(DefaultValue = 0.1, MinValue = 0.01)] public double LotsPerThousandBalance { get; set; } [Parameter(DefaultValue = 0.1)] public double LotIncrement { get; set; } private double buylevel = double.MinValue, selllevel = double.MaxValue; private int nbuys = 0, nsells = 0; private double minprofit = double.MaxValue, maxprofit = double.MinValue; private MovingAverage ma; private AverageTrueRange atr; protected override void OnStart() { ma = Indicators.MovingAverage(Bars.ClosePrices, MAPeriods, MAType); atr = Indicators.AverageTrueRange(AtrPeriods, MovingAverageType.Exponential); var buys = Positions.FindAll(ToString(), Symbol.Name, TradeType.Buy); var sells = Positions.FindAll(ToString(), Symbol.Name, TradeType.Sell); nbuys = buys.Length; nsells = sells.Length; double lots = Math.Max(Math.Floor(100 * Account.Balance / 1000 * (LotsPerThousandBalance + LotIncrement * (nbuys + nsells))) / 100, Symbol.VolumeInUnitsToQuantity(Symbol.VolumeInUnitsMin)); if (nbuys > 0) { double min = double.MaxValue; foreach (var position in buys) if (position.EntryPrice < min) min = position.EntryPrice; buylevel = min - GridAtrMultiplier * atr.Result.LastValue; } if (nsells > 0) { double max = double.MinValue; foreach (var position in sells) if (position.EntryPrice > max) max = position.EntryPrice; selllevel = max + GridAtrMultiplier * atr.Result.LastValue; } Print("nbuys: {0}, nsells: {1}, buylevel: {2}, selllevel: {3}, lots: {4}", nbuys, nsells, buylevel > double.MinValue ? buylevel.ToString("0.00000") : "n/a", selllevel < double.MaxValue ? selllevel.ToString("0.00000") : "n/a", lots); } protected override void OnBar() { // entry logic if (nsells + nbuys == 0) { if (Symbol.Bid > ma.Result.Last(0) + EntryDistancePips * Symbol.PipSize) OpenOrder(TradeType.Sell); else if (Symbol.Bid < ma.Result.Last(0) - EntryDistancePips * Symbol.PipSize) OpenOrder(TradeType.Buy); } } protected override void OnTick() { double profit = getMyUnrealizedNetProfit(); minprofit = profit < minprofit ? profit : minprofit; maxprofit = profit > maxprofit ? profit : maxprofit; // exit logic var pos = Positions.FindAll(ToString(), Symbol.Name); if (pos.Length == 0 || (profit > 0 && ((nsells > 0 && Symbol.Bid < ma.Result.Last(0)) || (nbuys > 0 && Symbol.Bid > ma.Result.Last(0)))) || -profit > (MaxEquityDrawdownPercent / 100) * Account.Balance) { if (nbuys + nsells > 0) { Print(string.Format("Closing {0} position(s), maxdd: {1}%, profit: {2}, balance: {3}", nbuys + nsells, (minprofit / Account.Balance * 100).ToString("0.0"), profit, Account.Balance)); foreach (var p in pos) ClosePositionAsync(p); } nbuys = nsells = 0; maxprofit = buylevel = double.MinValue; minprofit = selllevel = double.MaxValue; } // grid logic if (Symbol.Ask <= buylevel) OpenOrder(TradeType.Buy); if (Symbol.Bid >= selllevel) OpenOrder(TradeType.Sell); } protected override void OnStop() { if (IsBacktesting) { var positions = Positions.FindAll(ToString(), Symbol.Name); foreach (var p in positions) ClosePositionAsync(p); } } private void OpenOrder(TradeType tt) { double lots = Math.Max(Math.Floor(100 * Account.Balance / 1000 * (LotsPerThousandBalance + LotIncrement * (nbuys + nsells))) / 100, Symbol.VolumeInUnitsToQuantity(Symbol.VolumeInUnitsMin)); TradeResult tr = ExecuteMarketOrder(tt, Symbol.Name, Symbol.QuantityToVolumeInUnits(lots), ToString()); if (tr.Position != null) { if (tt == TradeType.Sell) { nsells++; selllevel = tr.Position.EntryPrice + GridAtrMultiplier * atr.Result.LastValue; } else { nbuys++; buylevel = tr.Position.EntryPrice - GridAtrMultiplier * atr.Result.LastValue; } Print(tr.Error.HasValue ? string.Format("Order error: {0}", tr.Error) : string.Format("{0} #{1} {2} {3} b: {4}, s: {5}", tr.Position.TradeType, nbuys + nsells, lots.ToString("0.00"), Symbol.Name, buylevel > double.MinValue ? buylevel.ToString("0.00000") : "n/a", selllevel < double.MaxValue ? selllevel.ToString("0.00000") : "n/a")); } } private double getMyUnrealizedNetProfit() { double netprofit = 0; var positions = Positions.FindAll(ToString(), Symbol.Name); foreach (var p in positions) netprofit += p.NetProfit; return netprofit; } } }
Can you fix the code and share it here? It’s incomplete, seems like you forgot to post part of it.
Thank you for letting me know the code was broken. I fixed it and added a screenshot of the backtesting.
Hello
For what ist the ATR Period ?
thank you for this great EA :)
The ATR is used to calculate the price level at which the next position of the grid will be opened. With this approach, the grid size will be bigger when the market is more volatile, reducing the risk.