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;
                }

            }
        }

    }
}