Overall Statistics |
Total Trades 220 Average Win 0.14% Average Loss -0.21% Compounding Annual Return -58.389% Drawdown 14.600% Expectancy -0.666 Net Profit -14.593% Sharpe Ratio -2.021 Probabilistic Sharpe Ratio 0.004% Loss Rate 80% Win Rate 20% Profit-Loss Ratio 0.67 Alpha -0.449 Beta 0.027 Annual Standard Deviation 0.22 Annual Variance 0.048 Information Ratio -2.445 Tracking Error 0.255 Treynor Ratio -16.303 Total Fees $5657.67 Estimated Strategy Capacity $310000.00 Lowest Capacity Asset TQQQ UK280CGTCB51 |
#region imports using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Globalization; using System.Drawing; using QuantConnect; using QuantConnect.Algorithm.Framework; using QuantConnect.Algorithm.Framework.Selection; using QuantConnect.Algorithm.Framework.Alphas; using QuantConnect.Algorithm.Framework.Portfolio; using QuantConnect.Algorithm.Framework.Execution; using QuantConnect.Algorithm.Framework.Risk; using QuantConnect.Parameters; using QuantConnect.Benchmarks; using QuantConnect.Brokerages; using QuantConnect.Util; using QuantConnect.Interfaces; using QuantConnect.Algorithm; using QuantConnect.Indicators; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Custom; using QuantConnect.DataSource; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Notifications; using QuantConnect.Orders; using QuantConnect.Orders.Fees; using QuantConnect.Orders.Fills; using QuantConnect.Orders.Slippage; using QuantConnect.Scheduling; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Securities.Forex; using QuantConnect.Securities.Crypto; using QuantConnect.Securities.Interfaces; using QuantConnect.Storage; using QuantConnect.Data.Custom.AlphaStreams; using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm; using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm; #endregion /* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.Collections.Concurrent; namespace QuantConnect.Algorithm.CSharp { /// <summary> /// Tests a wide variety of liquid and illiquid stocks together, with bins /// of 20 ranging from micro-cap to mega-cap stocks. /// </summary> public class ScalpingMACDCrossOverAlgorithmMultipleStocks : QCAlgorithm { public readonly Dictionary<string, SymbolData> Data = new Dictionary<string, SymbolData>(); private ConcurrentDictionary<Symbol, SymbolDto> _tickets = null; public readonly TimeSpan BarPeriod = TimeSpan.FromMinutes(2); public readonly int RollingWindowSize = 2; private bool _tradingEnabled = false; public override void Initialize() { SetStartDate(2023, 1, 1); SetEndDate(2023, 3, 12); // SetWarmup(1000); SetCash(100000); _tickets = new ConcurrentDictionary<Symbol, SymbolDto>(); Data.Add("TQQQ", new SymbolData(this, AddEquity("TQQQ", Resolution.Minute).Symbol, takeProfitPercent: 0.002m, 100, BarPeriod, RollingWindowSize, 0.95m)); Schedule.On(DateRules.EveryDay(), TimeRules.At(9, 45), () => { _tradingEnabled = true; }); Schedule.On(DateRules.EveryDay(), TimeRules.At(16, 55), () => { _tradingEnabled = false; }); // loop through all our symbols and request data subscriptions and initialize indicatora foreach (var kvp in Data) { var symbolData = kvp.Value; // define a consolidator to consolidate data for this symbol on the requested period var consolidator = new TradeBarConsolidator(BarPeriod); // define our indicator symbolData.SMA20 = new SimpleMovingAverage(CreateIndicatorName(symbolData.Symbol, "SMA" + RollingWindowSize, Resolution.Minute), RollingWindowSize); // wire up our consolidator to update the indicator consolidator.DataConsolidated += (sender, baseData) => { // 'bar' here is our newly consolidated data var bar = (IBaseDataBar)baseData; // update the indicator symbolData.SMA20.Update(bar.Time, bar.Close); // we're also going to add this bar to our rolling window so we have access to it later symbolData.Bars.Add(bar); }; // we need to add this consolidator so it gets auto updates SubscriptionManager.AddConsolidator(symbolData.Symbol, consolidator); } } private class SymbolDto { public OrderTicket BuyOrderTicket { get; set; } public OrderTicket TakeProfitTicket { get; set; } public decimal StopLossAt { get; set; } public decimal TakeProfitAt { get; set; } public decimal StockPrice { get; set; } } public class SymbolData { public int Priority; public Symbol Symbol; public SimpleMovingAverage SMA20; public RollingWindow<IBaseDataBar> Bars; public TimeSpan BarPeriod; public decimal TargetPercent; public IDataConsolidator _consolidator; public decimal TakeProfitPercent { get; set; } public SymbolData(QCAlgorithm algorithm, Symbol symbol, decimal takeProfitPercent, int priority, TimeSpan barPeriod, int windowSize, decimal targetPercent) { Symbol = symbol; SMA20 = algorithm.SMA(symbol, 20, Resolution.Minute); TakeProfitPercent = takeProfitPercent; Priority = priority; Bars = new RollingWindow<IBaseDataBar>(windowSize); BarPeriod = barPeriod; TargetPercent = targetPercent; _consolidator = new TradeBarConsolidator(windowSize); } public bool WasJustUpdated(DateTime current) { return Bars.Count > 0 && Bars[0].Time == current - BarPeriod; } } public override void OnData(Slice data) { if (!_tradingEnabled) return; foreach (var sd in Data) { if (!sd.Value.Bars.IsReady || !sd.Value.SMA20.IsReady) continue; var currBar = sd.Value.Bars[0]; // Current bar had index zero. var pastBar = sd.Value.Bars[1]; // Past bar has index one. if (pastBar.Close < sd.Value.SMA20 && currBar.Close > sd.Value.SMA20 && !Portfolio[sd.Value.Symbol].Invested ) { Debug($"Time {Time} Price: {pastBar.Time} -> {pastBar.Close} ... {currBar.Time} -> {currBar.Close}...sd.Value.SMA20 {sd.Value.SMA20}"); if ( _tickets.ContainsKey(sd.Value.Symbol) && _tickets[sd.Value.Symbol].BuyOrderTicket.Status != OrderStatus.Filled ) { Debug($"BuyOrderTicket {_tickets[sd.Value.Symbol].BuyOrderTicket.OrderId}"); Debug($"Time {Time}"); continue; } var quantity = CalculateOrderQuantity(sd.Value.Symbol, sd.Value.TargetPercent); if (quantity <= 0) continue; _tickets[sd.Value.Symbol] = new SymbolDto(); _tickets[sd.Value.Symbol].BuyOrderTicket = LimitOrder(sd.Value.Symbol, quantity, currBar.Close, orderProperties: new OrderProperties { TimeInForce = TimeInForce.GoodTilDate(Time.AddMinutes(2)) }); ; break; } if (Portfolio[sd.Value.Symbol].Invested && _tickets[sd.Value.Symbol].TakeProfitTicket != null) { Liquidate(sd.Value.Symbol); } } } public override void OnOrderEvent(OrderEvent orderEvent) { if (orderEvent.Status== OrderStatus.Filled) { var sd = Data.First(x => x.Value.Symbol == orderEvent.Symbol); if (orderEvent.Direction == OrderDirection.Buy) { _tickets[sd.Value.Symbol].TakeProfitAt = Math.Max(orderEvent.FillPrice * (1 + sd.Value.TakeProfitPercent), sd.Value.Bars[0].Close); _tickets[sd.Value.Symbol].TakeProfitTicket = LimitOrder(sd.Value.Symbol, -orderEvent.FillQuantity, _tickets[sd.Value.Symbol].TakeProfitAt); } else if (orderEvent.Direction == OrderDirection.Sell) { _tickets[sd.Value.Symbol].TakeProfitTicket = null; } } } } }