Description
I have programmed a very simple implementation of the London breakout strategy.
To know more about the London breakout strategy, see the videos in Youbute: https://www.youtube.com/results?search_query=london+breakout+strategy
This work has been inspired by this thread:
https://www.forexfactory.com/thread/730458-eurjpy-m15-system-80
However, the settings proposed by the author for EURJPY M15 in the first post are not profitable in my robot.
This robot has been tested for EURUSD, GBPJPY, USDJPY, EURJPY, CADJPY and CHFJPY, all of them in M15. Long term profitable configurations were found only for USDJPY and EURJPY.
If you find other profitable configurations for other symbols please let us know in the comments. If you know how to program and you manage to make this robot more profitable, let us know too.
Download the robot, the source code, the manual, settings files and more from this link:
https://drive.google.com/drive/folders/1O5qUPsnOd2THwuKmh4kWLS4Kl1W97aw6?usp=sharing
Before you ask questions about the robot, make sure that you have downloaded, read and understood the manual.
Updates:
version 1.1 - 2021/5/31 Correction of a bug.
version 1.2 - 2021/6/2 Added parameter Label (optional) to label positions; SL and TP rounded to one decimal to avoid bug in cTrader.
/*
Implementation of the London Breakout strategy
https://www.youtube.com/results?search_query=london+breakout+strategy
Thread in forum
https://www.forexfactory.com/thread/730458-eurjpy-m15-system-80
Expert advisor for MT4
https://www.forexfactory.com/thread/post/10715754#post10715754
Download manual, settings and more
https://drive.google.com/drive/folders/1O5qUPsnOd2THwuKmh4kWLS4Kl1W97aw6?usp=sharing
v 1.1 - 2021/5/31 Correction of a bug
v 1.2 - 2021/6/2 Added parameter Label; SL and TP rounded to one decimal to avoid bug in cTrader.
*/
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;
using System.Collections.Generic;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class NightBreakoutcBot : Robot
{
// ======================================================================
[Parameter("GMT start hour", Group = "Box hours and size", DefaultValue = 22, MinValue = 0, MaxValue = 23)]
public int ParamStartHour { get; set; }
[Parameter("GMT start minute", Group = "Box hours and size", DefaultValue = 0, MinValue = 0, MaxValue = 59)]
public int ParamStartMinute { get; set; }
[Parameter("GMT end hour", Group = "Box hours and size", DefaultValue = 2, MinValue = 0, MaxValue = 23)]
public int ParamEndHour { get; set; }
[Parameter("GMT end minute", Group = "Box hours and size", DefaultValue = 0, MinValue = 0, MaxValue = 59)]
public int ParamEndMinute { get; set; }
[Parameter("Adjust summer time", Group = "Box hours and size", DefaultValue = true)]
public bool ParamAdjustSummerTime { get; set; }
[Parameter("Min box size (pips)", Group = "Box hours and size", DefaultValue = 15, MinValue = 0)]
public int ParamMinBoxHeight { get; set; }
[Parameter("Max box size (pips)", Group = "Box hours and size", DefaultValue = 45, MinValue = 0)]
public int ParamMaxBoxHeight { get; set; }
// ======================================================================
// Choose fixed lot size or risk
[Parameter("Method", Group = "Money management", DefaultValue = MMType.FIXED_LOT)]
public MMType ParamMoneyMgmntType { get; set; }
// Fixed lot size
[Parameter("Lot size", Group = "Money management", DefaultValue = 0.01, MinValue = 0.01)]
public double ParamLotSize { get; set; }
// Percent of risk
[Parameter("Risk %", Group = "Money management", DefaultValue = 2, MinValue = 0.1, MaxValue = 50.0)]
public double ParamRisk { get; set; }
// ===============================================================================
[Parameter("Draw boxes in chart", Group = "Chart", DefaultValue = true)]
public bool ParamDrawBoxes { get; set; }
// ===============================================================================
[Parameter("Label", Group = "Label", DefaultValue = "")]
public string ParamLabel { get; set; }
private string label = "NightBreakoutcBot";
private string version = "1.2";
private Status status;
private double volume;
private double hightakeProfitPrice;
private double highStopLossPrice;
private double highEntryPrice;
private double lowtakeProfitPrice;
private double lowStopLossPrice;
private double lowEntryPrice;
private double boxWidth;
private string tradeLabel;
protected override void OnStart()
{
try
{
Positions.Closed += onPositionClosed;
this.tradeLabel = this.label;
if (this.ParamLabel != null && this.ParamLabel.Trim().Length > 0)
{
this.tradeLabel = this.ParamLabel.Trim();
}
this.volume = Symbol.QuantityToVolumeInUnits(this.ParamLotSize);
if (this.ParamDrawBoxes && (this.RunningMode == RunningMode.RealTime || this.RunningMode == RunningMode.VisualBacktesting))
{
this.drawPastBoxes();
}
this.status = Status.IDLE;
foreach (Position position in Positions)
{
if (position.SymbolName.Equals(Symbol.Name) && position.Label != null && this.tradeLabel.Equals(position.Label))
{
this.status = Status.TRADING;
}
}
this.hightakeProfitPrice = 0.0;
this.highStopLossPrice = 0.0;
this.highEntryPrice = 0.0;
this.lowtakeProfitPrice = 0.0;
this.lowStopLossPrice = 0.0;
this.lowEntryPrice = 0.0;
if (this.ParamMinBoxHeight >= this.ParamMaxBoxHeight)
{
throw new Exception("Error, min box size >= max box size");
}
if (this.RunningMode == RunningMode.RealTime || this.RunningMode == RunningMode.VisualBacktesting)
{
string str = "NightBreakout cBot " + this.version;
if (this.ParamLabel != null && this.ParamLabel.Trim().Length > 0)
{
str += "\n" + this.ParamLabel.Trim();
}
Chart.DrawStaticText("version", str, VerticalAlignment.Bottom, HorizontalAlignment.Left, Chart.ColorSettings.ForegroundColor);
}
Print("Started " + this.label + " version " + this.version);
} catch (Exception exc)
{
Print(exc.Message + " " + exc.StackTrace);
throw exc;
}
}
private void drawPastBoxes()
{
int finalIndex = Bars.Count - 1;
for (int i = 30; i <= finalIndex; i++)
{
BoxHours boxHours = this.getBoxHours(Bars.OpenTimes[i]);
int firstindex = 0;
if ((Bars.OpenTimes[i].Hour * 60 + Bars.OpenTimes[i].Minute) == boxHours.endDayMinute)
{
for (int j = i - 1; j > 0; j--)
{
if ((Bars.OpenTimes[j].Hour * 60 + Bars.OpenTimes[j].Minute) == boxHours.startDayMinute)
{
firstindex = j;
break;
}
}
double high = Bars.HighPrices[firstindex];
double low = Bars.LowPrices[firstindex];
for (int k = firstindex; k <= i; k++)
{
high = Math.Max(high, Bars.HighPrices[k]);
low = Math.Min(low, Bars.LowPrices[k]);
}
double delta = high - low;
Color color = Color.Lime;
if (delta < this.ParamMinBoxHeight * Symbol.PipSize || delta > this.ParamMaxBoxHeight * Symbol.PipSize)
{
color = Color.Red;
}
Chart.DrawRectangle(Guid.NewGuid().ToString(), firstindex, high, i - 1, low, color, 2);
Chart.DrawTrendLine(Guid.NewGuid().ToString(), i - 1, high + delta, i + 20, high + delta, color);
Chart.DrawTrendLine(Guid.NewGuid().ToString(), i - 1, low - delta, i + 20, low - delta, color);
}
}
}
protected override void OnBar()
{
try
{
BoxHours boxHours = this.getBoxHours(Bars.OpenTimes.LastValue);
if (this.status != Status.IDLE && Bars.OpenTimes.LastValue.Hour == boxHours.startHour && Bars.OpenTimes.LastValue.Minute == boxHours.startMinute)
{
this.status = Status.IDLE;
}
if (Bars.OpenTimes.LastValue.Hour == boxHours.endHour && Bars.OpenTimes.LastValue.Minute == boxHours.endMinute)
{
int lastIndex = Bars.OpenTimes.GetIndexByTime(Bars.OpenTimes.Last(1));
int firstIndex = 0;
for (int j = lastIndex - 1; j > 0; j--)
{
if (Bars.OpenTimes[j].Hour == boxHours.startHour && Bars.OpenTimes[j].Minute == boxHours.startMinute)
{
firstIndex = j;
break;
}
}
double high = Bars.HighPrices[firstIndex];
double low = Bars.LowPrices[firstIndex];
for (int k = firstIndex; k <= lastIndex; k++)
{
high = Math.Max(high, Bars.HighPrices[k]);
low = Math.Min(low, Bars.LowPrices[k]);
}
double delta = high - low;
this.hightakeProfitPrice = high + delta;
this.highStopLossPrice = high - delta;
this.highEntryPrice = high;
this.lowtakeProfitPrice = low - delta;
this.lowStopLossPrice = low + delta;
this.lowEntryPrice = low;
this.status = Status.WAITING;
this.boxWidth = delta;
Color color = Color.Lime;
if (delta < this.ParamMinBoxHeight * Symbol.PipSize || delta > this.ParamMaxBoxHeight * Symbol.PipSize)
{
color = Color.Red;
this.status = Status.IDLE;
}
if (this.ParamDrawBoxes && (this.RunningMode == RunningMode.RealTime || this.RunningMode == RunningMode.VisualBacktesting))
{
Chart.DrawRectangle(Guid.NewGuid().ToString(), firstIndex, high, lastIndex, low, color, 2);
Chart.DrawTrendLine(Guid.NewGuid().ToString(), lastIndex, high + delta, lastIndex + 20, high + delta, color);
Chart.DrawTrendLine(Guid.NewGuid().ToString(), lastIndex, low - delta, lastIndex + 20, low - delta, color);
}
}
if (this.status == Status.WAITING)
{
if (Bars.ClosePrices.LastValue > this.highEntryPrice && Bars.ClosePrices.Last(1) > this.highEntryPrice)
{
double deltaPips = (this.highEntryPrice - this.lowEntryPrice) / Symbol.PipSize;
deltaPips = Math.Round(deltaPips, 1);
TradeResult result = ExecuteMarketOrder(TradeType.Buy, Symbol.Name, this.calculateVolume(deltaPips), this.tradeLabel, deltaPips, deltaPips);
if (result.IsSuccessful)
{
this.status = Status.TRADING;
}
}
if (Bars.ClosePrices.LastValue < this.lowEntryPrice && Bars.ClosePrices.Last(1) < this.lowEntryPrice)
{
double deltaPips = (this.highEntryPrice - this.lowEntryPrice) / Symbol.PipSize;
deltaPips = Math.Round(deltaPips, 1);
TradeResult result = ExecuteMarketOrder(TradeType.Sell, Symbol.Name, this.calculateVolume(deltaPips), this.tradeLabel, deltaPips, deltaPips);
if (result.IsSuccessful)
{
this.status = Status.TRADING;
}
}
}
} catch (Exception exc)
{
Print(exc.Message + " " + exc.StackTrace);
throw exc;
}
}
protected override void OnStop()
{
}
private void onPositionClosed(PositionClosedEventArgs args)
{
if (args.Position.SymbolName.Equals(Symbol.Name) && this.tradeLabel.Equals(args.Position.Label))
{
this.status = Status.IDLE;
}
}
private BoxHours getBoxHours(DateTime dt)
{
BoxHours bh = new BoxHours();
if (this.ParamAdjustSummerTime)
{
if (this.isSummer(dt))
{
bh.startHour = this.ParamStartHour - 1;
if (bh.startHour < 0)
{
bh.startHour += 24;
}
bh.endHour = this.ParamEndHour - 1;
if (bh.endHour < 0)
{
bh.endHour += 24;
}
}
else
{
bh.startHour = this.ParamStartHour;
bh.endHour = this.ParamEndHour;
}
}
else
{
bh.startHour = this.ParamStartHour;
bh.endHour = this.ParamEndHour;
}
bh.startMinute = this.ParamStartMinute;
bh.endMinute = this.ParamEndMinute;
bh.startDayMinute = bh.startHour * 60 + this.ParamStartMinute;
bh.endDayMinute = bh.endHour * 60 + this.ParamEndMinute;
return bh;
}
private bool isSummer(DateTime dt)
{
// Winter: November to March
// Summer: April to October
return dt.Month >= 4 && dt.Month <= 10;
}
private double calculateVolume(double stopLossPips)
{
double vol = this.volume;
switch (this.ParamMoneyMgmntType)
{
case MMType.FIXED_LOT:
vol = this.volume;
break;
case MMType.RISK:
double conversionRate = 1.0;
if (Account.Currency.Equals("EUR"))
{
Symbol symbolEURYYY = Symbols.GetSymbol("EUR" + Symbol.Name.Substring(3, 3));
conversionRate = symbolEURYYY.Bid;
}
vol = this.normalizeVolume(Account.Equity * this.ParamRisk * conversionRate / (100.0 * stopLossPips * Symbol.PipSize));
break;
}
return vol;
}
private double normalizeVolume(double vol)
{
double result = 1000;
while (result + 1000 < vol)
{
result += 1000;
}
return result;
}
}
public class BoxHours
{
public int startHour { get; set; }
public int startMinute { get; set; }
public int startDayMinute { get; set; }
public int endHour { get; set; }
public int endMinute { get; set; }
public int endDayMinute { get; set; }
}
public enum Status
{
IDLE,
WAITING,
TRADING
}
public enum MMType
{
FIXED_LOT,
RISK
}
}
guillermo
Joined on 07.06.2019
- Distribution: Free
- Language: C#
- Trading platform: cTrader Automate
- File name: NightBreakout cBot.algo
- Rating: 0
- Installs: 2154
- Modified: 13/10/2021 09:54
Comments
The bug is corrected, and version 1.1 is uploaded here.
The bot works well when backtesting but it does not place trades in demo or live accounts.
Hi, Thanks for the cbot. I wanted to try out on gold but the cbot did not place any orders. I had backtested it before and I have got a good result on 3 min time frame. I wanted to use it on 2% of the risk setup. What could be the problem here? Thank you for helping.