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