Overall Statistics |
Total Trades 1 Average Win 0% Average Loss 0% Compounding Annual Return 99.013% Drawdown 0.400% Expectancy 0 Net Profit 0.947% Sharpe Ratio 14.525 Probabilistic Sharpe Ratio 100% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0.312 Beta 0.192 Annual Standard Deviation 0.056 Annual Variance 0.003 Information Ratio -7.945 Tracking Error 0.226 Treynor Ratio 4.233 Total Fees $0.08 |
namespace QuantConnect { public class OneCancelsOtherTicketSet { public OneCancelsOtherTicketSet(params OrderTicket[] orderTickets) { this.OrderTickets = orderTickets; } private OrderTicket[] OrderTickets { get; set; } public void Filled() { // Cancel all the outstanding tickets. foreach (var orderTicket in this.OrderTickets) { if (orderTicket.Status == OrderStatus.Submitted) { orderTicket.Cancel(); } } } } }
namespace QuantConnect.Algorithm.CSharp { public class FX_Gap_Trader : QCAlgorithm { /* **************************** MOST IMPORTANT PARAMS *****************************/ private readonly decimal TradeSize = 2500; private readonly List<string> symbolList = new List<string>() { "EURUSD" }; int period = 1; Resolution resolution = Resolution.Second; /* **************************** **************************** */ List<StockDataClass> stockDatas = new List<StockDataClass>(); decimal limitRatio = 0m; int priceDecimals = 2; TimeSpan orderExpiryTime = new TimeSpan(0,0,0,59); // One cancels the other module private OrderTicket EntryOrder { get; set; } private Func<QCAlgorithm, string, decimal, OneCancelsOtherTicketSet> OnOrderFilledEvent { get; set; } private OneCancelsOtherTicketSet ProfitLossOrders { get; set; } public override void Initialize() { SetStartDate(2020, 5, 15); //Set Start Date SetEndDate(2020, 5, 19); SetCash(TradeSize * (symbolList.Count)); //Set Strategy Cash SetBrokerageModel(BrokerageName.FxcmBrokerage); foreach (string ticker in symbolList) // primary reference: https://www.quantconnect.com/forum/discussion/469/indicators-with-multiple-resolutions-for-multiple-symbols/p1 { AddForex(ticker, resolution); StockDataClass stockData = new StockDataClass(ticker); stockData.rollingwindow = new RollingWindow<QuoteBar>(period); stockData.OldPL = 0m; stockData.NewPL= 0m; // add the symbol's class members to the list of tickers stockDatas.Add(stockData); } if (LiveMode) { // print daily P&L figures during live trading //Schedule.On(DateRules.EveryDay("MLAB"), TimeRules.BeforeMarketClose("MLAB", 0), PrintPL); } // Trigger on FX market open at 5pm Sunday's EST Schedule.On(DateRules.Every(DayOfWeek.Sunday), TimeRules.At(17, 0), () => { Trade(); }); } public override void OnData(Slice data) { for (int i = 0; i < stockDatas.Count; i++) { //Debug("stock datas element " + Datas[i].Ticker); // if data contains key ticker in symbrolList and bar is not empty if (data.Bars.ContainsKey(stockDatas[i].Ticker) && data.Bars[stockDatas[i].Ticker] != null) { // Add TradeBar to rolling window; return if not ready stockDatas[i].rollingwindow.Add(data[stockDatas[i].Ticker]); if (!stockDatas[i].rollingwindow.IsReady) return; // Get Friday's close if (Time.DayOfWeek == DayOfWeek.Friday && Time.Hour == 16 && Time.Minute == 59 && Time.Second == 59) { stockDatas[i].fri_close = stockDatas[i].rollingwindow[0].Close; } } } } public void Trade() { for (int i = 0; i < stockDatas.Count; i++) { var history = History<QuoteBar>(stockDatas[i].Ticker,period,resolution); foreach (var bar in history) { stockDatas[i].rollingwindow.Add(bar); } if (!stockDatas[i].rollingwindow.IsReady) return; // Get Friday's closing price, should be at window index [1].Close // Get Open price now, , should be at window index [0].Open // Calculate Gap from Friday decimal sun_open = stockDatas[i].rollingwindow[0].Open; decimal fri_close = stockDatas[i].fri_close; decimal gap = sun_open - fri_close; // Trading rule // Long if Gap <0; else short if (gap < 0) { // buy market TradeLong(stockDatas[i].Ticker,sun_open,fri_close); } else if (gap > 0) { // buy market TradeShort(stockDatas[i].Ticker,sun_open,fri_close); } } } public void TradeLong(string ticker, decimal sun_open, decimal fri_close) { int quantity = (int)Math.Floor((TradeSize) / sun_open); decimal gap = Math.Abs(sun_open - fri_close); decimal target = fri_close; //DefaultOrderProperties.TimeInForce = TimeInForce.Day; // On order event status == filled this.OnOrderFilledEvent = (algo, symbol, filledPrice) => { // if filled, set stop loss and trailing stops as GTC //DefaultOrderProperties.TimeInForce = TimeInForce.GoodTilCanceled; return new OneCancelsOtherTicketSet( // if order placed, Stop loss as a multiple of gap below entry algo.LimitOrder(symbol, -quantity, target, "Long Profit Target"), algo.StopMarketOrder(symbol, -quantity, filledPrice - gap, "Long Stop Loss")); // Set target equal to friday's close }; if (EntryOpened(ticker, quantity) == false) { //DefaultOrderProperties.TimeInForce = TimeInForce.Day; this.EntryOrder = MarketOrder(ticker, quantity); } } public void TradeShort(string ticker, decimal sun_open, decimal fri_close) { decimal quantity = Math.Floor((TradeSize) / sun_open); decimal gap = Math.Abs(sun_open - fri_close); decimal target = fri_close; // On order event status == filled this.OnOrderFilledEvent = (algo, symbol, filledPrice) => { // if filled, set stop loss and trailing stops as GTC // DefaultOrderProperties.TimeInForce = TimeInForce.GoodTilCanceled; return new OneCancelsOtherTicketSet( // if order placed, Stop loss as a multiple of gap below entry // Set stop limit order 2x size of gap algo.LimitOrder(symbol, quantity, target , "short Profit Target"), algo.StopMarketOrder(symbol, quantity, filledPrice + gap, "short Stop Loss")); // Set target equal to friday's close }; if (EntryOpened(ticker, -quantity) == false) { //DefaultOrderProperties.TimeInForce = TimeInForce.Day; this.EntryOrder = MarketOrder(ticker, - quantity); } } public bool EntryOpened(string symbol, decimal quantity) { List<Order> orders = Transactions.GetOpenOrders(symbol); foreach (Order order in orders) { if (order.Symbol == symbol) { if (Math.Sign(quantity) == Math.Sign(order.Quantity)) // fine for now but long short will need further5 filter { return true; //Debug("Order already placed. order.Quantity: " + order.Quantity + " quantity: " + quantity + " price: " + price + "order.Price" + order.Price); } } } return false; } public override void OnOrderEvent(OrderEvent orderEvent) { if (EntryOrder != null) { this.EntryOrder = null; } if (orderEvent.Status == OrderStatus.Filled || orderEvent.Status == OrderStatus.PartiallyFilled) { if (this.OnOrderFilledEvent != null) { this.ProfitLossOrders = OnOrderFilledEvent(this, orderEvent.Symbol, orderEvent.FillPrice); OnOrderFilledEvent = null; } else if (this.ProfitLossOrders != null) { this.ProfitLossOrders.Filled(); this.ProfitLossOrders = null; } // Log round trip profits Log(orderEvent.Symbol + " Last trade Profit = " + Portfolio[orderEvent.Symbol].LastTradeProfit); } } public bool OrderIsPlaced(string symbol, decimal quantity) { List<Order> orders = Transactions.GetOpenOrders(symbol); foreach (Order order in orders) { if (order.Symbol.Value == symbol) { if (Math.Sign(quantity) == Math.Sign(order.Quantity)) { return true; } } } return false; } // Backtesting stats public override void OnEndOfAlgorithm() { // Dicts for total stats Dictionary<string, decimal> CumPL = new Dictionary<string, decimal>(); // Total P&L per symbol for (int i = 0; i < stockDatas.Count; i++) { // Populate cumulative P&L dict CumPL.Add(stockDatas[i].Ticker,Math.Round(Portfolio[stockDatas[i].Ticker].NetProfit, priceDecimals)); int wins = 0; int losers = 0; decimal profits = 0m; decimal losses = 0m; decimal maxDD = 0m; //Aggregate trades by symbol and figure out stats foreach (var trade in TradeBuilder.ClosedTrades) { // wins & losses if (trade.Symbol == stockDatas[i].Ticker) { if (trade.ProfitLoss > 0) { wins += 1; profits += trade.ProfitLoss; } else { losers += 1; losses += trade.ProfitLoss; // max drawdown if (trade.ProfitLoss < maxDD) maxDD = trade.ProfitLoss; } } } if (wins != 0) { Log("Number of round trip trades for " +stockDatas[i].Ticker + ": " + (wins + losers)); // Calc win rate Log("Win rate for " +stockDatas[i].Ticker + ": " + Math.Round(Decimal.Divide(wins,(wins + losers))*100,priceDecimals) + "%"); // Average win average loss Log("Average win for " +stockDatas[i].Ticker + ": " + Math.Round(profits/wins,priceDecimals)); Log("Average loss for " +stockDatas[i].Ticker + ": " + Math.Round(losses/wins,priceDecimals)); Log("Max drawdown for " +stockDatas[i].Ticker + ": " + Math.Round(maxDD,priceDecimals)); } } string CumPL_output = string.Join("; ", CumPL.Select(x => string.Join(" = $", x.Key, x.Value))); string cumPL_report = ("Cumulative net profits since launch: " + Environment.NewLine + CumPL_output); Log(cumPL_report); } public class StockDataClass { public string Ticker; public RollingWindow<QuoteBar> rollingwindow; public StockDataClass(string ticker) { Ticker = ticker; } public decimal fri_close; public decimal sun_open; // Performance metrics public decimal OldPL; public decimal NewPL; } } }