diff --git a/Algorithm.CSharp/BasicTemplateOptionEquityStrategyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateOptionEquityStrategyAlgorithm.cs index ebb4f54c1e00..6c198175131f 100644 --- a/Algorithm.CSharp/BasicTemplateOptionEquityStrategyAlgorithm.cs +++ b/Algorithm.CSharp/BasicTemplateOptionEquityStrategyAlgorithm.cs @@ -138,7 +138,7 @@ public override void OnOrderEvent(OrderEvent orderEvent) {"Estimated Strategy Capacity", "$70000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "61.31%"}, - {"OrderListHash", "3adbc743489fe6902267726150770f82"} + {"OrderListHash", "a36c60c5fb020121d6541683138d8f28"} }; } } diff --git a/Algorithm.CSharp/BasicTemplateOptionStrategyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateOptionStrategyAlgorithm.cs index f0ae2080c0a7..3ceba2ce9f2f 100644 --- a/Algorithm.CSharp/BasicTemplateOptionStrategyAlgorithm.cs +++ b/Algorithm.CSharp/BasicTemplateOptionStrategyAlgorithm.cs @@ -148,7 +148,7 @@ public override void OnOrderEvent(OrderEvent orderEvent) {"Estimated Strategy Capacity", "$3000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZFMEBBB2E|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "338.60%"}, - {"OrderListHash", "cbe76e0a8288fb9ccfc42b4c20142131"} + {"OrderListHash", "c9eb598f33939941206efc018eb6ee45"} }; } } diff --git a/Algorithm.CSharp/BasicTemplateSPXWeeklyIndexOptionsStrategyAlgorithm.cs b/Algorithm.CSharp/BasicTemplateSPXWeeklyIndexOptionsStrategyAlgorithm.cs index 08a1f5805c2a..d0eb7de5b0e6 100644 --- a/Algorithm.CSharp/BasicTemplateSPXWeeklyIndexOptionsStrategyAlgorithm.cs +++ b/Algorithm.CSharp/BasicTemplateSPXWeeklyIndexOptionsStrategyAlgorithm.cs @@ -145,7 +145,7 @@ public override void OnOrderEvent(OrderEvent orderEvent) {"Estimated Strategy Capacity", "$13000000.00"}, {"Lowest Capacity Asset", "SPXW XKX6S2GM9PGU|SPX 31"}, {"Portfolio Turnover", "0.28%"}, - {"OrderListHash", "ddc78aa04824180bc36aac3472ae22b4"} + {"OrderListHash", "69b28e4f5b33ff54f173c14ea1e00c50"} }; } } diff --git a/Algorithm.CSharp/ComboLegLimitOrderAlgorithm.cs b/Algorithm.CSharp/ComboLegLimitOrderAlgorithm.cs index c88e4e50094e..90d2d3f1952b 100644 --- a/Algorithm.CSharp/ComboLegLimitOrderAlgorithm.cs +++ b/Algorithm.CSharp/ComboLegLimitOrderAlgorithm.cs @@ -25,11 +25,28 @@ namespace QuantConnect.Algorithm.CSharp /// public class ComboLegLimitOrderAlgorithm : ComboOrderAlgorithm { + private List _originalLimitPrices = new(); + protected override IEnumerable PlaceComboOrder(List legs, int quantity, decimal? limitPrice = null) { + foreach (var leg in legs) + { + _originalLimitPrices.Add(leg.OrderPrice.Value); + leg.OrderPrice *= 2; // Won't fill + } + return ComboLegLimitOrder(legs, quantity); } + protected override void UpdateComboOrder(List tickets) + { + // Let's updated the limit prices to the original values + for (int i = 0; i < tickets.Count; i++) + { + tickets[i].Update(new UpdateOrderFields { LimitPrice = _originalLimitPrices[i] }); + } + } + public override void OnEndOfAlgorithm() { base.OnEndOfAlgorithm(); @@ -88,8 +105,8 @@ public override void OnEndOfAlgorithm() {"Total Fees", "$26.00"}, {"Estimated Strategy Capacity", "$58000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"}, - {"Portfolio Turnover", "30.16%"}, - {"OrderListHash", "c081092ac475a3aa95fc55a2718e2b25"} + {"Portfolio Turnover", "30.22%"}, + {"OrderListHash", "216608fa33a238275b5f4f08e50e45b4"} }; } } diff --git a/Algorithm.CSharp/ComboLimitOrderAlgorithm.cs b/Algorithm.CSharp/ComboLimitOrderAlgorithm.cs index ce60dd634cbb..6409911cf907 100644 --- a/Algorithm.CSharp/ComboLimitOrderAlgorithm.cs +++ b/Algorithm.CSharp/ComboLimitOrderAlgorithm.cs @@ -28,6 +28,9 @@ public class ComboLimitOrderAlgorithm : ComboOrderAlgorithm private decimal _limitPrice; private int _comboQuantity; + private decimal _temporaryLimitPrice; + private int _temporaryComboQuantity; + private int _fillCount; private decimal _liquidatedQuantity; @@ -47,10 +50,23 @@ protected override IEnumerable PlaceComboOrder(List legs, int { _limitPrice = limitPrice.Value; _comboQuantity = quantity; + _temporaryLimitPrice = limitPrice.Value - Math.Sign(quantity) * limitPrice.Value * 0.5m; // Won't fill + _temporaryComboQuantity = quantity * 10; legs.ForEach(x => { x.OrderPrice = null; }); - return ComboLimitOrder(legs, quantity, _limitPrice); + // First, let's place a limit order that won't fill so we can update it later + return ComboLimitOrder(legs, _temporaryComboQuantity, _temporaryLimitPrice); + } + + protected override void UpdateComboOrder(List tickets) + { + // Let's update the quantity and limit price to the real values + tickets[0].Update(new UpdateOrderFields + { + Quantity = _comboQuantity, + LimitPrice = _limitPrice + }); } public override void OnOrderEvent(OrderEvent orderEvent) @@ -156,7 +172,7 @@ public override void OnEndOfAlgorithm() {"Estimated Strategy Capacity", "$5000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "60.91%"}, - {"OrderListHash", "6ca75024c436ecca6efbe1ddaace4c71"} + {"OrderListHash", "0a8f9edaff4857d0e7731c7b936e4288"} }; } } diff --git a/Algorithm.CSharp/ComboMarketOrderAlgorithm.cs b/Algorithm.CSharp/ComboMarketOrderAlgorithm.cs index 14987fe9786a..2913b25503d1 100644 --- a/Algorithm.CSharp/ComboMarketOrderAlgorithm.cs +++ b/Algorithm.CSharp/ComboMarketOrderAlgorithm.cs @@ -88,7 +88,7 @@ public override void OnOrderEvent(OrderEvent orderEvent) {"Estimated Strategy Capacity", "$70000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "30.35%"}, - {"OrderListHash", "3c43fa5b49373e3c77e000d2283fe4bc"} + {"OrderListHash", "71cb9fdd55ff925f70677ae01a18b90d"} }; } } diff --git a/Algorithm.CSharp/ComboOrderAlgorithm.cs b/Algorithm.CSharp/ComboOrderAlgorithm.cs index 23682650860f..78497af621bb 100644 --- a/Algorithm.CSharp/ComboOrderAlgorithm.cs +++ b/Algorithm.CSharp/ComboOrderAlgorithm.cs @@ -31,6 +31,9 @@ public abstract class ComboOrderAlgorithm : QCAlgorithm, IRegressionAlgorithmDef { private Symbol _optionSymbol; + private List Tickets { get; set; } + private bool _updated; + protected List FillOrderEvents { get; private set; } = new(); protected List OrderLegs { get; private set; } @@ -85,9 +88,19 @@ public override void OnData(Slice slice) Leg.Create(callContracts[1].Symbol, -2, 14.6m), Leg.Create(callContracts[2].Symbol, 1, 14.0m) }; - PlaceComboOrder(OrderLegs, ComboOrderQuantity, 1.9m); + Tickets = PlaceComboOrder(OrderLegs, ComboOrderQuantity, 1.9m).ToList(); } } + // Let's test order updates + else if (Tickets.All(ticket => ticket.OrderType != OrderType.ComboMarket) && FillOrderEvents.Count == 0 && !_updated) + { + UpdateComboOrder(Tickets); + _updated = true; + } + } + + protected virtual void UpdateComboOrder(List tickets) + { } public override void OnOrderEvent(OrderEvent orderEvent) @@ -107,6 +120,11 @@ public override void OnEndOfAlgorithm() throw new Exception("Combo order legs were not initialized"); } + if (Tickets.All(ticket => ticket.OrderType != OrderType.ComboMarket) && !_updated) + { + throw new Exception("Combo order was not updated"); + } + if (FillOrderEvents.Count != ExpectedFillCount) { throw new Exception($"Expected {ExpectedFillCount} fill order events, found {FillOrderEvents.Count}"); diff --git a/Algorithm.CSharp/ComboOrderTicketDemoAlgorithm.cs b/Algorithm.CSharp/ComboOrderTicketDemoAlgorithm.cs index d3747021c5de..8d9a1b0f7414 100644 --- a/Algorithm.CSharp/ComboOrderTicketDemoAlgorithm.cs +++ b/Algorithm.CSharp/ComboOrderTicketDemoAlgorithm.cs @@ -209,7 +209,7 @@ private void ComboLegLimitOrders() foreach (var ticket in combo1) { - var newLimit = ticket.Get(OrderField.LimitPrice) + (ticket.Quantity > 0 ? 1m : -1m) * 0.01m; + var newLimit = Math.Round(ticket.Get(OrderField.LimitPrice) + (ticket.Quantity > 0 ? 1m : -1m) * 0.01m, 2); Debug($"Updating limits - Combo #1: {newLimit.ToStringInvariant("0.00")}"); ticket.Update(new UpdateOrderFields @@ -221,7 +221,7 @@ private void ComboLegLimitOrders() foreach (var ticket in combo2) { - var newLimit = ticket.Get(OrderField.LimitPrice) + (ticket.Quantity > 0 ? 1m : -1m) * 0.01m; + var newLimit = Math.Round(ticket.Get(OrderField.LimitPrice) + (ticket.Quantity > 0 ? 1m : -1m) * 0.01m, 2); Debug($"Updating limits - Combo #2: {newLimit.ToStringInvariant("0.00")}"); ticket.Update(new UpdateOrderFields @@ -367,7 +367,7 @@ public override void OnEndOfAlgorithm() {"Estimated Strategy Capacity", "$2000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "58.98%"}, - {"OrderListHash", "1254db09e88826bed15bbce3aff11365"} + {"OrderListHash", "c455fd803ce5f4b7902a97d84c14629a"} }; } } diff --git a/Algorithm.CSharp/ComboOrdersFillModelAlgorithm.cs b/Algorithm.CSharp/ComboOrdersFillModelAlgorithm.cs index 5f2c8636ff5d..bea9f2b077e7 100644 --- a/Algorithm.CSharp/ComboOrdersFillModelAlgorithm.cs +++ b/Algorithm.CSharp/ComboOrdersFillModelAlgorithm.cs @@ -155,7 +155,7 @@ public override void OnEndOfAlgorithm() {"Estimated Strategy Capacity", "$250000.00"}, {"Lowest Capacity Asset", "IBM R735QTJ8XC9X"}, {"Portfolio Turnover", "9.81%"}, - {"OrderListHash", "411685f7f8454b5ee59a3fe42ddec90a"} + {"OrderListHash", "ea77edfba185b1bee9961fcbcd3a20ef"} }; } diff --git a/Algorithm.CSharp/CoveredAndProtectiveCallStrategiesAlgorithm.cs b/Algorithm.CSharp/CoveredAndProtectiveCallStrategiesAlgorithm.cs index f1e698347343..d77b6fc2218f 100644 --- a/Algorithm.CSharp/CoveredAndProtectiveCallStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/CoveredAndProtectiveCallStrategiesAlgorithm.cs @@ -133,7 +133,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$120000.00"}, {"Lowest Capacity Asset", "GOOCV WBGM92QHIYO6|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "32.18%"}, - {"OrderListHash", "8e62cd986a2ba74341db4f17816a1583"} + {"OrderListHash", "a22851491e8b6a12e1a2f741b084781c"} }; } } diff --git a/Algorithm.CSharp/CoveredAndProtectivePutStrategiesAlgorithm.cs b/Algorithm.CSharp/CoveredAndProtectivePutStrategiesAlgorithm.cs index 9fc1e845369d..552908f1738b 100644 --- a/Algorithm.CSharp/CoveredAndProtectivePutStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/CoveredAndProtectivePutStrategiesAlgorithm.cs @@ -133,7 +133,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$160000.00"}, {"Lowest Capacity Asset", "GOOCV 30AKMEIPOSS1Y|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "32.12%"}, - {"OrderListHash", "dc343e84c50c44cb1ee72aaea67f5bfb"} + {"OrderListHash", "87079f2dfad700b5362ce26a1f80c1ef"} }; } } diff --git a/Algorithm.CSharp/CoveredCallComboLimitOrderAlgorithm.cs b/Algorithm.CSharp/CoveredCallComboLimitOrderAlgorithm.cs index 7af13a7e8802..c2e385acc35f 100644 --- a/Algorithm.CSharp/CoveredCallComboLimitOrderAlgorithm.cs +++ b/Algorithm.CSharp/CoveredCallComboLimitOrderAlgorithm.cs @@ -142,7 +142,7 @@ public override void OnOrderEvent(OrderEvent orderEvent) {"Estimated Strategy Capacity", "$8000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZFMEBBB2E|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "227.27%"}, - {"OrderListHash", "5d9434c81e36c12d3e34b0d2c39468b2"} + {"OrderListHash", "4521f5e6f5d0929a907e9832ce31c4a3"} }; } } diff --git a/Algorithm.CSharp/DefaultMarginComboOrderRegressionAlgorithm.cs b/Algorithm.CSharp/DefaultMarginComboOrderRegressionAlgorithm.cs index 65dfe81f8c37..926cc0102917 100644 --- a/Algorithm.CSharp/DefaultMarginComboOrderRegressionAlgorithm.cs +++ b/Algorithm.CSharp/DefaultMarginComboOrderRegressionAlgorithm.cs @@ -79,7 +79,7 @@ protected override void AssertState(OrderTicket ticket, int expectedGroupCount, {"Estimated Strategy Capacity", "$0"}, {"Lowest Capacity Asset", ""}, {"Portfolio Turnover", "0%"}, - {"OrderListHash", "2c50d5dba71820a101e55e61236b5caa"} + {"OrderListHash", "a0ccd30c00ec046a942c6e2885dfb237"} }; } } diff --git a/Algorithm.CSharp/IronCondorStrategyAlgorithm.cs b/Algorithm.CSharp/IronCondorStrategyAlgorithm.cs index b57e3f7db39a..471edfd2f2a1 100644 --- a/Algorithm.CSharp/IronCondorStrategyAlgorithm.cs +++ b/Algorithm.CSharp/IronCondorStrategyAlgorithm.cs @@ -155,7 +155,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$4000.00"}, {"Lowest Capacity Asset", "GOOCV 306CZL2DIL4G6|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "2.00%"}, - {"OrderListHash", "4d6ce6322499eb3f564c973fdc2a6fc7"} + {"OrderListHash", "95337d99685b140f2534ffb21afed10b"} }; } } diff --git a/Algorithm.CSharp/LargeQuantityOptionStrategyAlgorithm.cs b/Algorithm.CSharp/LargeQuantityOptionStrategyAlgorithm.cs index 9a0db381f178..534fd14672f2 100644 --- a/Algorithm.CSharp/LargeQuantityOptionStrategyAlgorithm.cs +++ b/Algorithm.CSharp/LargeQuantityOptionStrategyAlgorithm.cs @@ -142,7 +142,7 @@ public override void OnEndOfAlgorithm() {"Estimated Strategy Capacity", "$6000.00"}, {"Lowest Capacity Asset", "GOOCV 30AKMELSHQVZA|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "208.51%"}, - {"OrderListHash", "3b38055cdc7e0643cc32f0ce24d651a9"} + {"OrderListHash", "f53992823027e3b976ab8de0b3f96100"} }; } } diff --git a/Algorithm.CSharp/LiquidatingMultipleOptionStrategiesRegressionAlgorithm.cs b/Algorithm.CSharp/LiquidatingMultipleOptionStrategiesRegressionAlgorithm.cs index 1945639be440..34ad8ce6b53f 100644 --- a/Algorithm.CSharp/LiquidatingMultipleOptionStrategiesRegressionAlgorithm.cs +++ b/Algorithm.CSharp/LiquidatingMultipleOptionStrategiesRegressionAlgorithm.cs @@ -183,7 +183,7 @@ public override void OnEndOfAlgorithm() {"Estimated Strategy Capacity", "$13000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "1.31%"}, - {"OrderListHash", "85d15c33656b556a9bae6304ec8d6193"} + {"OrderListHash", "5c3873497b1108a1caeb6c6ddc941c56"} }; } } diff --git a/Algorithm.CSharp/LongAndShortButterflyCallStrategiesAlgorithm.cs b/Algorithm.CSharp/LongAndShortButterflyCallStrategiesAlgorithm.cs index c97cd97158f6..25a1698056dd 100644 --- a/Algorithm.CSharp/LongAndShortButterflyCallStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/LongAndShortButterflyCallStrategiesAlgorithm.cs @@ -155,7 +155,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$7000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZFMEBBB2E|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "2.17%"}, - {"OrderListHash", "8d756e8d2da7e9afeb2bb09a6ee29cf3"} + {"OrderListHash", "d03d3bf7e0247f8c41fb58639d0bcd03"} }; } } diff --git a/Algorithm.CSharp/LongAndShortButterflyPutStrategiesAlgorithm.cs b/Algorithm.CSharp/LongAndShortButterflyPutStrategiesAlgorithm.cs index 662ee1077194..5c3952d1d36e 100644 --- a/Algorithm.CSharp/LongAndShortButterflyPutStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/LongAndShortButterflyPutStrategiesAlgorithm.cs @@ -155,7 +155,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$4000.00"}, {"Lowest Capacity Asset", "GOOCV 306CZL2DIL4G6|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "2.23%"}, - {"OrderListHash", "b0cddb103e05acf099ab78f92fff9cbb"} + {"OrderListHash", "64404785e4781b384f457e3661c41393"} }; } } diff --git a/Algorithm.CSharp/LongAndShortCallCalendarSpreadStrategiesAlgorithm.cs b/Algorithm.CSharp/LongAndShortCallCalendarSpreadStrategiesAlgorithm.cs index 110871256c39..0597c0e0102a 100644 --- a/Algorithm.CSharp/LongAndShortCallCalendarSpreadStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/LongAndShortCallCalendarSpreadStrategiesAlgorithm.cs @@ -138,7 +138,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$7000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZEOEHQRYE|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "1.85%"}, - {"OrderListHash", "713df4f63925b9193a81f3e5e6136870"} + {"OrderListHash", "5de301650de7e202677b008e52c0f9a1"} }; } } diff --git a/Algorithm.CSharp/LongAndShortPutCalendarSpreadStrategiesAlgorithm.cs b/Algorithm.CSharp/LongAndShortPutCalendarSpreadStrategiesAlgorithm.cs index 629b799fd11b..15a43ad64611 100644 --- a/Algorithm.CSharp/LongAndShortPutCalendarSpreadStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/LongAndShortPutCalendarSpreadStrategiesAlgorithm.cs @@ -138,7 +138,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$14000.00"}, {"Lowest Capacity Asset", "GOOCV 306CZK4DP0LC6|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "1.87%"}, - {"OrderListHash", "f870b902db2b022768f705a1befa4f93"} + {"OrderListHash", "2d36b79f33d5637b18f77a0dd4bb2b18"} }; } } diff --git a/Algorithm.CSharp/LongAndShortStraddleStrategiesAlgorithm.cs b/Algorithm.CSharp/LongAndShortStraddleStrategiesAlgorithm.cs index a7da8002b6c2..d312566f01e4 100644 --- a/Algorithm.CSharp/LongAndShortStraddleStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/LongAndShortStraddleStrategiesAlgorithm.cs @@ -135,7 +135,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$16000.00"}, {"Lowest Capacity Asset", "GOOCV WBGM92QHIYO6|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "4.31%"}, - {"OrderListHash", "ba72827dda76607a57eeca6f8e87e862"} + {"OrderListHash", "cdf4b3ca6cae4aba68be24deb93ff0a3"} }; } } diff --git a/Algorithm.CSharp/LongAndShortStrangleStrategiesAlgorithm.cs b/Algorithm.CSharp/LongAndShortStrangleStrategiesAlgorithm.cs index 0a4a04eaa885..ee0f0543bd0a 100644 --- a/Algorithm.CSharp/LongAndShortStrangleStrategiesAlgorithm.cs +++ b/Algorithm.CSharp/LongAndShortStrangleStrategiesAlgorithm.cs @@ -145,7 +145,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$15000.00"}, {"Lowest Capacity Asset", "GOOCV 30AKMELSHQVZA|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "4.21%"}, - {"OrderListHash", "e2227791e3ca5bf42a6286ef8014744e"} + {"OrderListHash", "dff4c8a7a24047e7a857fda355502b36"} }; } } diff --git a/Algorithm.CSharp/NakedCallStrategyAlgorithm.cs b/Algorithm.CSharp/NakedCallStrategyAlgorithm.cs index b28700b31f23..810ab10eb256 100644 --- a/Algorithm.CSharp/NakedCallStrategyAlgorithm.cs +++ b/Algorithm.CSharp/NakedCallStrategyAlgorithm.cs @@ -124,7 +124,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$8000.00"}, {"Lowest Capacity Asset", "GOOCV WBGM92QHIYO6|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "2.19%"}, - {"OrderListHash", "01b1231544147c79964ba9c0c4ede790"} + {"OrderListHash", "44e53ac6f2dff1ad4a7c34bcd4e5f0d4"} }; } } diff --git a/Algorithm.CSharp/NakedPutStrategyAlgorithm.cs b/Algorithm.CSharp/NakedPutStrategyAlgorithm.cs index 09e022c2089a..8aee7e16d7f7 100644 --- a/Algorithm.CSharp/NakedPutStrategyAlgorithm.cs +++ b/Algorithm.CSharp/NakedPutStrategyAlgorithm.cs @@ -124,7 +124,7 @@ protected override void LiquidateStrategy() {"Estimated Strategy Capacity", "$10000.00"}, {"Lowest Capacity Asset", "GOOCV 30AKMEIPOSS1Y|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "2.13%"}, - {"OrderListHash", "c9e0862ca284ca35a4d1abb9ce58ea46"} + {"OrderListHash", "27cefe3a419018bb6b0bd26735cad576"} }; } } diff --git a/Algorithm.CSharp/NakedShortOptionStrategyOverMarginAlgorithm.cs b/Algorithm.CSharp/NakedShortOptionStrategyOverMarginAlgorithm.cs index 33588b53ba7d..8692b68afe25 100644 --- a/Algorithm.CSharp/NakedShortOptionStrategyOverMarginAlgorithm.cs +++ b/Algorithm.CSharp/NakedShortOptionStrategyOverMarginAlgorithm.cs @@ -167,7 +167,7 @@ public override void OnEndOfAlgorithm() {"Estimated Strategy Capacity", "$1800000.00"}, {"Lowest Capacity Asset", "GOOCV 30AKMEIPOSS1Y|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "7.50%"}, - {"OrderListHash", "0418745669e8e8c3c98ff75c10deeab5"} + {"OrderListHash", "e2e0ddfc52c963683da287cf7600fcc5"} }; } } diff --git a/Algorithm.CSharp/NullMarginComboOrderRegressionAlgorithm.cs b/Algorithm.CSharp/NullMarginComboOrderRegressionAlgorithm.cs index 491249c62e5e..d783135becd0 100644 --- a/Algorithm.CSharp/NullMarginComboOrderRegressionAlgorithm.cs +++ b/Algorithm.CSharp/NullMarginComboOrderRegressionAlgorithm.cs @@ -72,7 +72,7 @@ protected override void PlaceTrades(OptionContract optionContract) {"Estimated Strategy Capacity", "$8800000.00"}, {"Lowest Capacity Asset", "GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "7580.62%"}, - {"OrderListHash", "ea97bdd2b85abb9e628d44788bad0eba"} + {"OrderListHash", "d19c696bc30e5d6ea743d7ba33b07925"} }; } } diff --git a/Algorithm.CSharp/RevertComboOrderPositionsAlgorithm.cs b/Algorithm.CSharp/RevertComboOrderPositionsAlgorithm.cs index 08fa5ec48676..abf21a1ff8ac 100644 --- a/Algorithm.CSharp/RevertComboOrderPositionsAlgorithm.cs +++ b/Algorithm.CSharp/RevertComboOrderPositionsAlgorithm.cs @@ -209,7 +209,7 @@ private decimal GetComboOrderFillPrice(List orderTickets) {"Estimated Strategy Capacity", "$16000.00"}, {"Lowest Capacity Asset", "GOOCV W78ZERHAOVVQ|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "2088.83%"}, - {"OrderListHash", "28d2da503142331e4129ea11cd36857a"} + {"OrderListHash", "f2b37471b38c6ee668024690407a2131"} }; } } diff --git a/Algorithm.CSharp/RollOutFrontMonthToBackMonthOptionUsingCalendarSpreadRegressionAlgorithm.cs b/Algorithm.CSharp/RollOutFrontMonthToBackMonthOptionUsingCalendarSpreadRegressionAlgorithm.cs index 8824c96c77ce..a50cc3455874 100644 --- a/Algorithm.CSharp/RollOutFrontMonthToBackMonthOptionUsingCalendarSpreadRegressionAlgorithm.cs +++ b/Algorithm.CSharp/RollOutFrontMonthToBackMonthOptionUsingCalendarSpreadRegressionAlgorithm.cs @@ -173,7 +173,7 @@ public override void OnEndOfAlgorithm() {"Estimated Strategy Capacity", "$190000.00"}, {"Lowest Capacity Asset", "GOOCV 306CZK4DP0LC6|GOOCV VP83T1ZUHROL"}, {"Portfolio Turnover", "1.19%"}, - {"OrderListHash", "c7672fdeef16c3c524e1e10557e2e4e3"} + {"OrderListHash", "4a5e3aac203b4a44532db1e657de3245"} }; } } diff --git a/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py b/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py index efbc77771ec4..8da95a3dc685 100644 --- a/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py +++ b/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py @@ -152,7 +152,7 @@ def ComboLegLimitOrders(self): # if neither order has filled, bring in the limits by a penny for ticket in combo1: - newLimit = ticket.Get(OrderField.LimitPrice) + (1 if ticket.Quantity > 0 else -1) * 0.01 + newLimit = round(ticket.Get(OrderField.LimitPrice) + (1 if ticket.Quantity > 0 else -1) * 0.01, 2) self.Debug(f"Updating limits - Combo #1: {newLimit:.2f}") fields = UpdateOrderFields() fields.LimitPrice = newLimit @@ -160,7 +160,7 @@ def ComboLegLimitOrders(self): ticket.Update(fields) for ticket in combo2: - newLimit = ticket.Get(OrderField.LimitPrice) + (1 if ticket.Quantity > 0 else -1) * 0.01 + newLimit = round(ticket.Get(OrderField.LimitPrice) + (1 if ticket.Quantity > 0 else -1) * 0.01, 2) self.Debug(f"Updating limits - Combo #2: {newLimit:.2f}") fields.LimitPrice = newLimit fields.Tag = f"Update #{len(ticket.UpdateRequests) + 1}" diff --git a/Algorithm/QCAlgorithm.Plotting.cs b/Algorithm/QCAlgorithm.Plotting.cs index 853645dd6823..34c81826425b 100644 --- a/Algorithm/QCAlgorithm.Plotting.cs +++ b/Algorithm/QCAlgorithm.Plotting.cs @@ -340,32 +340,22 @@ private bool TryGetChartSeries(string chartName, string seriesName, out T ser { if (reservedSeriesNames.Count == 0) { - throw new Exception($"Algorithm.Plot(): '{chartName}' is a reserved chart name."); + throw new ArgumentException($"'{chartName}' is a reserved chart name."); } if (reservedSeriesNames.Contains(seriesName)) { - throw new Exception($"Algorithm.Plot(): '{seriesName}' is a reserved series name for chart '{chartName}'."); + throw new ArgumentException($"'{seriesName}' is a reserved series name for chart '{chartName}'."); } } - // If we don't have the chart, create it - _charts.TryAdd(chartName, new Chart(chartName)); + if(!_charts.TryGetValue(chartName, out var chart)) + { + // If we don't have the chart, create it + _charts[chartName] = chart = new Chart(chartName); + } - var chart = _charts[chartName]; if (!chart.Series.TryGetValue(seriesName, out var chartSeries)) { - //Number of series in total, excluding reserved charts - var seriesCount = _charts.Select(x => x.Value) - .Aggregate(0, (i, c) => ReservedChartSeriesNames.TryGetValue(c.Name, out reservedSeriesNames) - ? i + c.Series.Values.Count(s => reservedSeriesNames.Count > 0 && !reservedSeriesNames.Contains(s.Name)) - : i + c.Series.Count); - - if (seriesCount > 10) - { - Error("Exceeded maximum series count: Each backtest can have up to 10 series in total."); - return false; - } - chartSeries = new T() { Name = seriesName }; chart.AddSeries(chartSeries); } @@ -549,22 +539,20 @@ public void SetSummaryStatistic(string name, decimal value) /// List of chart updates since the last request /// GetChartUpdates returns the latest updates since previous request. [DocumentationAttribute(Charting)] - public List GetChartUpdates(bool clearChartData = false) + public IEnumerable GetChartUpdates(bool clearChartData = false) { - var updates = _charts.Select(x => x.Value).Select(chart => chart.GetUpdates()).ToList(); - - if (clearChartData) + foreach (var chart in _charts.Values) { - // we can clear this data out after getting updates to prevent unnecessary memory usage - foreach (var chart in _charts) + yield return chart.GetUpdates(); + if (clearChartData) { - foreach (var series in chart.Value.Series) + // we can clear this data out after getting updates to prevent unnecessary memory usage + foreach (var series in chart.Series) { series.Value.Purge(); } } } - return updates; } } } diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs index 7707000ccd6f..e404809cf8a6 100644 --- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs +++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs @@ -574,7 +574,7 @@ public void OnEndOfTimeStep() /// /// /// List of Chart Updates - public List GetChartUpdates(bool clearChartData = false) => _baseAlgorithm.GetChartUpdates(clearChartData); + public IEnumerable GetChartUpdates(bool clearChartData = false) => _baseAlgorithm.GetChartUpdates(clearChartData); /// /// Gets whether or not this algorithm has been locked and fully initialized diff --git a/Common/Extensions.cs b/Common/Extensions.cs index 43d0b6510898..a9bc182302b8 100644 --- a/Common/Extensions.cs +++ b/Common/Extensions.cs @@ -2409,7 +2409,6 @@ public static OrderTicket ToOrderTicket(this Order order, SecurityTransactionMan switch (order.Type) { case OrderType.Limit: - case OrderType.ComboLegLimit: var limitOrder = order as LimitOrder; limitPrice = limitOrder.LimitPrice; break; @@ -2444,6 +2443,10 @@ public static OrderTicket ToOrderTicket(this Order order, SecurityTransactionMan case OrderType.ComboLimit: limitPrice = order.GroupOrderManager.LimitPrice; break; + case OrderType.ComboLegLimit: + var legLimitOrder = order as ComboLegLimitOrder; + limitPrice = legLimitOrder.LimitPrice; + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/Common/Interfaces/IAlgorithm.cs b/Common/Interfaces/IAlgorithm.cs index 48788585a697..ba6cb5f177ff 100644 --- a/Common/Interfaces/IAlgorithm.cs +++ b/Common/Interfaces/IAlgorithm.cs @@ -642,7 +642,7 @@ InsightManager Insights /// /// /// List of Chart Updates - List GetChartUpdates(bool clearChartData = false); + IEnumerable GetChartUpdates(bool clearChartData = false); /// /// Set a required SecurityType-symbol and resolution for algorithm diff --git a/Common/Orders/ComboLegLimitOrder.cs b/Common/Orders/ComboLegLimitOrder.cs index cf61b443b73d..6fca459dd2e2 100644 --- a/Common/Orders/ComboLegLimitOrder.cs +++ b/Common/Orders/ComboLegLimitOrder.cs @@ -23,13 +23,18 @@ namespace QuantConnect.Orders /// Combo leg limit order type /// /// Limit price per leg in the combo order - public class ComboLegLimitOrder : LimitOrder + public class ComboLegLimitOrder : ComboOrder { /// /// Combo Limit Leg Order Type /// public override OrderType Type => OrderType.ComboLegLimit; + /// + /// Limit price for this order. + /// + public decimal LimitPrice { get; internal set; } + /// /// Added a default constructor for JSON Deserialization: /// @@ -49,9 +54,32 @@ public ComboLegLimitOrder() : base() /// The order properties for this order public ComboLegLimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, DateTime time, GroupOrderManager groupOrderManager, string tag = "", IOrderProperties properties = null) - : base(symbol, quantity, limitPrice, time, tag, properties) + : base(symbol, quantity, time, groupOrderManager, tag, properties) { GroupOrderManager = groupOrderManager; + LimitPrice = limitPrice; + } + + /// + /// Gets the order value in units of the security's quote currency + /// + /// The security matching this order's symbol + protected override decimal GetValueImpl(Security security) + { + return LimitOrder.CalculateOrderValue(Quantity, LimitPrice, security.Price); + } + + /// + /// Modifies the state of this order to match the update request + /// + /// The request to update this order object + public override void ApplyUpdateOrderRequest(UpdateOrderRequest request) + { + base.ApplyUpdateOrderRequest(request); + if (request.LimitPrice.HasValue) + { + LimitPrice = request.LimitPrice.Value; + } } /// diff --git a/Common/Orders/ComboLimitOrder.cs b/Common/Orders/ComboLimitOrder.cs index 5f410e5fa2bc..df86f43be4b1 100644 --- a/Common/Orders/ComboLimitOrder.cs +++ b/Common/Orders/ComboLimitOrder.cs @@ -23,7 +23,7 @@ namespace QuantConnect.Orders /// Combo limit order type /// /// Single limit price for the whole combo order - public class ComboLimitOrder : Order + public class ComboLimitOrder : ComboOrder { /// /// Combo Limit Order Type @@ -60,19 +60,7 @@ public ComboLimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, Date /// The security matching this order's symbol protected override decimal GetValueImpl(Security security) { - // selling, so higher price will be used - if (Quantity < 0) - { - return Quantity * Math.Max(GroupOrderManager.LimitPrice, security.Price); - } - - // buying, so lower price will be used - if (Quantity > 0) - { - return Quantity * Math.Min(GroupOrderManager.LimitPrice, security.Price); - } - - return 0m; + return LimitOrder.CalculateOrderValue(Quantity, GroupOrderManager.LimitPrice, security.Price); } /// diff --git a/Common/Orders/ComboMarketOrder.cs b/Common/Orders/ComboMarketOrder.cs index be5a458d6bc6..391555285b4a 100644 --- a/Common/Orders/ComboMarketOrder.cs +++ b/Common/Orders/ComboMarketOrder.cs @@ -22,7 +22,7 @@ namespace QuantConnect.Orders /// /// Combo market order type /// - public class ComboMarketOrder : MarketOrder + public class ComboMarketOrder : ComboOrder { /// /// Combo Market Order Type @@ -47,9 +47,17 @@ public ComboMarketOrder() : base() /// The order properties for this order public ComboMarketOrder(Symbol symbol, decimal quantity, DateTime time, GroupOrderManager groupOrderManager, string tag = "", IOrderProperties properties = null) - : base(symbol, quantity, time, tag, properties) + : base(symbol, quantity, time, groupOrderManager, tag, properties) { - GroupOrderManager = groupOrderManager; + } + + /// + /// Gets the order value in units of the security's quote currency + /// + /// The security matching this order's symbol + protected override decimal GetValueImpl(Security security) + { + return Quantity * security.Price; } /// diff --git a/Common/Orders/ComboOrder.cs b/Common/Orders/ComboOrder.cs new file mode 100644 index 000000000000..31192d3db82f --- /dev/null +++ b/Common/Orders/ComboOrder.cs @@ -0,0 +1,91 @@ +/* + * 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; +using QuantConnect.Interfaces; + +namespace QuantConnect.Orders +{ + /// + /// Combo order type + /// + public abstract class ComboOrder : Order + { + private decimal _ratio; + + /// + /// Number of shares to execute. + /// For combo orders, we store the ratio of each leg instead of the quantity, + /// and the actual quantity is calculated when requested using the group order manager quantity. + /// This allows for a single quantity update to be applied to all the legs of the combo. + /// + public override decimal Quantity + { + get + { + return _ratio.GetOrderLegGroupQuantity(GroupOrderManager).Normalize(); + } + internal set + { + _ratio = value.GetOrderLegRatio(GroupOrderManager); + } + } + + /// + /// Added a default constructor for JSON Deserialization: + /// + public ComboOrder() : base() + { + } + + /// + /// New market order constructor + /// + /// Symbol asset we're seeking to trade + /// Quantity of the asset we're seeking to trade + /// Time the order was placed + /// Manager for the orders in the group + /// User defined data tag for this order + /// The order properties for this order + public ComboOrder(Symbol symbol, decimal quantity, DateTime time, GroupOrderManager groupOrderManager, string tag = "", + IOrderProperties properties = null) + : base(symbol, 0m, time, tag, properties) + { + GroupOrderManager = groupOrderManager; + Quantity = quantity; + } + + /// + /// Modifies the state of this order to match the update request + /// + /// The request to update this order object + public override void ApplyUpdateOrderRequest(UpdateOrderRequest request) + { + if (request.OrderId != Id) + { + throw new ArgumentException("Attempted to apply updates to the incorrect order!"); + } + if (request.Tag != null) + { + Tag = request.Tag; + } + if (request.Quantity.HasValue) + { + // For combo orders, the updated quantity is the quantity of the group + GroupOrderManager.Quantity = request.Quantity.Value; + } + } + } +} diff --git a/Common/Orders/GroupOrderManager.cs b/Common/Orders/GroupOrderManager.cs index bdf94c624f9e..02eb69f4b888 100644 --- a/Common/Orders/GroupOrderManager.cs +++ b/Common/Orders/GroupOrderManager.cs @@ -32,7 +32,7 @@ public class GroupOrderManager /// /// The group order quantity /// - public decimal Quantity { get; } + public decimal Quantity { get; internal set; } /// /// The total order count associated with this order group diff --git a/Common/Orders/LimitOrder.cs b/Common/Orders/LimitOrder.cs index 5470f443f825..867300544f2a 100644 --- a/Common/Orders/LimitOrder.cs +++ b/Common/Orders/LimitOrder.cs @@ -71,19 +71,7 @@ public LimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, DateTime /// The security matching this order's symbol protected override decimal GetValueImpl(Security security) { - // selling, so higher price will be used - if (Quantity < 0) - { - return Quantity*Math.Max(LimitPrice, security.Price); - } - - // buying, so lower price will be used - if (Quantity > 0) - { - return Quantity*Math.Min(LimitPrice, security.Price); - } - - return 0m; + return CalculateOrderValue(Quantity, LimitPrice, security.Price); } /// @@ -121,5 +109,22 @@ public override Order Clone() CopyTo(order); return order; } + + internal static decimal CalculateOrderValue(decimal quantity, decimal limitPrice, decimal price) + { + // selling, so higher price will be used + if (quantity < 0) + { + return quantity * Math.Max(limitPrice, price); + } + + // buying, so lower price will be used + if (quantity > 0) + { + return quantity * Math.Min(limitPrice, price); + } + + return 0m; + } } } diff --git a/Common/Orders/Order.cs b/Common/Orders/Order.cs index 768c7012db37..d492cdfff72a 100644 --- a/Common/Orders/Order.cs +++ b/Common/Orders/Order.cs @@ -101,7 +101,7 @@ public decimal Price /// /// Number of shares to execute. /// - public decimal Quantity + public virtual decimal Quantity { get { return _quantity; } internal set { _quantity = value.Normalize(); } @@ -346,6 +346,9 @@ public override string ToString() protected void CopyTo(Order order) { order.Id = Id; + // The group order manager has to be set before the quantity, + // since combo orders might need it to calculate the quantity in the Quantity setter. + order.GroupOrderManager = GroupOrderManager; order.Time = Time; order.LastFillTime = LastFillTime; order.LastUpdateTime = LastUpdateTime; @@ -360,7 +363,6 @@ protected void CopyTo(Order order) order.Tag = Tag; order.Properties = Properties.Clone(); order.OrderSubmissionData = OrderSubmissionData?.Clone(); - order.GroupOrderManager = GroupOrderManager; order.PriceAdjustmentMode = PriceAdjustmentMode; } diff --git a/Common/Orders/OrderTicket.cs b/Common/Orders/OrderTicket.cs index 1b32510d0a03..02b30c493abb 100644 --- a/Common/Orders/OrderTicket.cs +++ b/Common/Orders/OrderTicket.cs @@ -253,7 +253,11 @@ public T Get(OrderField field) { fieldValue = AccessOrder(this, field, o => o.GroupOrderManager.LimitPrice, r => r.LimitPrice); } - else if (_submitRequest.OrderType == OrderType.Limit || _submitRequest.OrderType == OrderType.ComboLegLimit) + else if (_submitRequest.OrderType == OrderType.ComboLegLimit) + { + fieldValue = AccessOrder(this, field, o => o.LimitPrice, r => r.LimitPrice); + } + else if (_submitRequest.OrderType == OrderType.Limit) { fieldValue = AccessOrder(this, field, o => o.LimitPrice, r => r.LimitPrice); } diff --git a/Common/Packets/Controls.cs b/Common/Packets/Controls.cs index b4d2efc9319e..ab97882c489d 100644 --- a/Common/Packets/Controls.cs +++ b/Common/Packets/Controls.cs @@ -103,6 +103,12 @@ public class Controls [JsonProperty(PropertyName = "iMaximumDataPointsPerChartSeries")] public int MaximumDataPointsPerChartSeries; + /// + /// Limits the amount of chart series. Applies only for backtesting + /// + [JsonProperty(PropertyName = "iMaximumChartSeries")] + public int MaximumChartSeries; + /// /// The amount seconds used for timeout limits /// @@ -162,6 +168,7 @@ public Controls() RemainingLogAllowance = 10000; MaximumRuntimeMinutes = 60 * 24 * 100; // 100 days default BacktestingMaxInsights = 10000; + MaximumChartSeries = 10; MaximumDataPointsPerChartSeries = 4000; SecondTimeOut = 300; StorageLimit = 10737418240; diff --git a/Common/SeriesSampler.cs b/Common/SeriesSampler.cs index ecd4e1243477..eee2c59e628e 100644 --- a/Common/SeriesSampler.cs +++ b/Common/SeriesSampler.cs @@ -55,6 +55,16 @@ public SeriesSampler(TimeSpan resolution) /// The sampled series public virtual BaseSeries Sample(BaseSeries series, DateTime start, DateTime stop, bool truncateValues = false) { + if (!SubSample && series.Values.Count > 1) + { + var dataDiff = series.Values[1].Time - series.Values[0].Time; + if (dataDiff >= Step) + { + // we don't want to subsample this case, directly return what we are given as long as is within the range + return GetIdentitySeries(series.Clone(empty: true), series, start, stop, truncateValues: false); + } + } + if (series is Series seriesToSample) { return SampleSeries(seriesToSample, start, stop, truncateValues); diff --git a/Configuration/ToolboxArgumentParser.cs b/Configuration/ToolboxArgumentParser.cs index 71d6652598f0..6c143a1dc090 100644 --- a/Configuration/ToolboxArgumentParser.cs +++ b/Configuration/ToolboxArgumentParser.cs @@ -40,15 +40,15 @@ public static class ToolboxArgumentParser + "/KrakenDownloader or KDL/OandaDownloader or ODL/QuandlBitfinexDownloader or QBDL" + "/YahooDownloader or YDL/AlgoSeekFuturesConverter or ASFC" + "/IVolatilityEquityConverter or IVEC/KaikoDataConverter or KDC/NseMarketDataConverter or NMDC" - + "/QuantQuoteConverter or QQC/CoarseUniverseGenerator or CUG/\n" + + "/CoarseUniverseGenerator or CUG/\n" + "RandomDataGenerator or RDG\n" + "Example 1: --app=DDL\n" + "Example 2: --app=NseMarketDataConverter\n" + "Example 3: --app=RDG"), new CommandLineOption("tickers", CommandOptionType.MultipleValue, "[REQUIRED ALL downloaders (except QBDL)] " + "--tickers=SPY,AAPL,etc"), - new CommandLineOption("resolution", CommandOptionType.SingleValue, "[REQUIRED ALL downloaders (except QBDL, CDL) and IVolatilityEquityConverter," - + " QuantQuoteConverter] *Not all downloaders support all resolutions. Send empty for more information.*" + new CommandLineOption("resolution", CommandOptionType.SingleValue, "[REQUIRED ALL downloaders (except QBDL, CDL) and IVolatilityEquityConverter]" + + " *Not all downloaders support all resolutions. Send empty for more information.*" + " CASE SENSITIVE: --resolution=Tick/Second/Minute/Hour/Daily/All" +Environment.NewLine+ "[OPTIONAL for RandomDataGenerator - same format as downloaders, Options only support Minute"), new CommandLineOption("from-date", CommandOptionType.SingleValue, "[REQUIRED ALL downloaders] --from-date=yyyyMMdd-HH:mm:ss"), @@ -59,9 +59,9 @@ public static class ToolboxArgumentParser new CommandLineOption("date", CommandOptionType.SingleValue, "[REQUIRED for AlgoSeekFuturesConverter, AlgoSeekOptionsConverter, KaikoDataConverter]" + "Date for the option bz files: --date=yyyyMMdd"), new CommandLineOption("source-dir", CommandOptionType.SingleValue, "[REQUIRED for IVolatilityEquityConverter, KaikoDataConverter," - + " CoinApiDataConverter, NseMarketDataConverter, QuantQuoteConverter]"), + + " CoinApiDataConverter, NseMarketDataConverter]"), new CommandLineOption("destination-dir", CommandOptionType.SingleValue, "[REQUIRED for IVolatilityEquityConverter, " - + "NseMarketDataConverter, QuantQuoteConverter]"), + + "NseMarketDataConverter]"), new CommandLineOption("source-meta-dir", CommandOptionType.SingleValue, "[REQUIRED for IVolatilityEquityConverter]"), new CommandLineOption("start", CommandOptionType.SingleValue, "[REQUIRED for RandomDataGenerator. Format yyyyMMdd Example: --start=20010101]"), new CommandLineOption("end", CommandOptionType.SingleValue, "[REQUIRED for RandomDataGenerator. Format yyyyMMdd Example: --end=20020101]"), diff --git a/Engine/Results/BacktestingResultHandler.cs b/Engine/Results/BacktestingResultHandler.cs index 8210d368dc08..1d41520cefcc 100644 --- a/Engine/Results/BacktestingResultHandler.cs +++ b/Engine/Results/BacktestingResultHandler.cs @@ -44,6 +44,8 @@ public class BacktestingResultHandler : BaseResultsHandler, IResultHandler private string _errorMessage; private int _daysProcessedFrontier; private readonly HashSet _chartSeriesExceededDataPoints; + private readonly HashSet _chartSeriesCount; + private bool _chartSeriesCountExceededError; private BacktestProgressMonitor _progressMonitor; @@ -70,7 +72,8 @@ public BacktestingResultHandler() ResamplePeriod = TimeSpan.FromMinutes(4); NotificationPeriod = TimeSpan.FromSeconds(2); - _chartSeriesExceededDataPoints = new HashSet(); + _chartSeriesExceededDataPoints = new(); + _chartSeriesCount = new(); // Delay uploading first packet _nextS3Update = StartTime.AddSeconds(5); @@ -570,7 +573,7 @@ protected override void SampleCapacity(DateTime time) /// Add a range of samples from the users algorithms to the end of our current list. /// /// Chart updates since the last request. - protected void SampleRange(List updates) + protected void SampleRange(IEnumerable updates) { lock (ChartLock) { @@ -587,6 +590,22 @@ protected void SampleRange(List updates) //Add these samples to this chart. foreach (var series in update.Series.Values) { + // let's assert we are within series count limit + if (_chartSeriesCount.Count < _job.Controls.MaximumChartSeries) + { + _chartSeriesCount.Add(series.Name); + } + else if (!_chartSeriesCount.Contains(series.Name)) + { + // above the limit and this is a new series + if(!_chartSeriesCountExceededError) + { + _chartSeriesCountExceededError = true; + DebugMessage($"Exceeded maximum chart series count, new series will be ignored. Limit is currently set at {_job.Controls.MaximumChartSeries}"); + } + continue; + } + if (series.Values.Count > 0) { var thisSeries = chart.TryAddAndGetSeries(series.Name, series, forceAddNew: false); diff --git a/Engine/Results/LiveTradingResultHandler.cs b/Engine/Results/LiveTradingResultHandler.cs index 4049a80f3d8a..a8e37bdc98e1 100644 --- a/Engine/Results/LiveTradingResultHandler.cs +++ b/Engine/Results/LiveTradingResultHandler.cs @@ -653,7 +653,7 @@ protected override void Sample(string chartName, string seriesName, int seriesIn /// /// Chart updates since the last request. /// - protected void SampleRange(List updates) + protected void SampleRange(IEnumerable updates) { Log.Debug("LiveTradingResultHandler.SampleRange(): Begin sampling"); lock (ChartLock) diff --git a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs index 3a53c597363e..a0c5d10d01cb 100644 --- a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs +++ b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs @@ -1136,10 +1136,13 @@ private void HandleOrderEvents(List orderEvents) switch (order.Type) { case OrderType.Limit: - case OrderType.ComboLegLimit: var limit = order as LimitOrder; orderEvent.LimitPrice = limit.LimitPrice; break; + case OrderType.ComboLegLimit: + var legLimitOrder = order as ComboLegLimitOrder; + orderEvent.LimitPrice = legLimitOrder.LimitPrice; + break; case OrderType.StopMarket: var marketOrder = order as StopMarketOrder; orderEvent.StopPrice = marketOrder.StopPrice; diff --git a/Launcher/config.json b/Launcher/config.json index 37ca4221c7b8..ac30e33758eb 100644 --- a/Launcher/config.json +++ b/Launcher/config.json @@ -55,7 +55,8 @@ "maximum-warmup-history-days-look-back": 5, // limits the amount of data points per chart series. Applies only for backtesting - "maximum-data-points-per-chart-series": 4000, + "maximum-data-points-per-chart-series": 1000000, + "maximum-chart-series": 30, // if one uses true in following token, market hours will remain open all hours and all days. // if one uses false will make lean operate only during regular market hours. diff --git a/Queues/JobQueue.cs b/Queues/JobQueue.cs index d75aedd70015..747800e9cafe 100644 --- a/Queues/JobQueue.cs +++ b/Queues/JobQueue.cs @@ -153,7 +153,8 @@ public AlgorithmNodePacket NextJob(out string location) SecondLimit = Config.GetInt("symbol-second-limit", 10000), TickLimit = Config.GetInt("symbol-tick-limit", 10000), RamAllocation = int.MaxValue, - MaximumDataPointsPerChartSeries = Config.GetInt("maximum-data-points-per-chart-series", 4000), + MaximumDataPointsPerChartSeries = Config.GetInt("maximum-data-points-per-chart-series", 1000000), + MaximumChartSeries = Config.GetInt("maximum-chart-series", 30), StorageLimit = Config.GetValue("storage-limit", 10737418240L), StorageFileCount = Config.GetInt("storage-file-count", 10000), StoragePermissions = (FileAccess)Config.GetInt("storage-permissions", (int)FileAccess.ReadWrite) diff --git a/Tests/Algorithm/AlgorithmPlottingTests.cs b/Tests/Algorithm/AlgorithmPlottingTests.cs index fb8ed489da25..69e27897a5d7 100644 --- a/Tests/Algorithm/AlgorithmPlottingTests.cs +++ b/Tests/Algorithm/AlgorithmPlottingTests.cs @@ -29,7 +29,7 @@ namespace QuantConnect.Tests.Algorithm { - [TestFixture] + [TestFixture, Parallelizable(ParallelScope.Fixtures)] public class AlgorithmPlottingTests { private Symbol _spy; @@ -105,7 +105,7 @@ public void TestGetChartUpdatesWhileAdding() { for (var i = 0; i < 1000; i++) { - _algorithm.GetChartUpdates(true); + _algorithm.GetChartUpdates(true).ToList(); Thread.Sleep(1); } }); @@ -168,7 +168,7 @@ def Update(self, input): customIndicator.Update(input); customIndicator.Current.Value = customIndicator.Value; Assert.DoesNotThrow(() => _algorithm.Plot("PlotTest", customIndicator)); - var charts = _algorithm.GetChartUpdates(); + var charts = _algorithm.GetChartUpdates().ToList(); Assert.IsTrue(charts.Where(x => x.Name == "PlotTest").Any()); Assert.AreEqual(10, charts.First().Series["custom"].GetValues().First().y); } @@ -194,7 +194,7 @@ def Update(self, input): var customIndicator = module.GetAttr("CustomIndicator").Invoke(); Assert.DoesNotThrow(() => _algorithm.Plot("PlotTest", customIndicator)); - var charts = _algorithm.GetChartUpdates(); + var charts = _algorithm.GetChartUpdates().ToList(); Assert.IsFalse(charts.Where(x => x.Name == "PlotTest").Any()); Assert.IsTrue(charts.Where(x => x.Name == "Strategy Equity").Any()); Assert.AreEqual(10, charts.First().Series["PlotTest"].GetValues().First().y); @@ -213,7 +213,7 @@ public void PlotIndicatorPlotsBaseIndicator() sma1.Update(new DateTime(2022, 11, 15), 1); sma2.Update(new DateTime(2022, 11, 15), 2); - var charts = _algorithm.GetChartUpdates(); + var charts = _algorithm.GetChartUpdates().ToList(); Assert.IsTrue(charts.Where(x => x.Name == "PlotTest").Any()); var chart = charts.First(); diff --git a/Tests/Common/Brokerages/BinanceBrokerageModelTests.cs b/Tests/Common/Brokerages/BinanceBrokerageModelTests.cs index 99101cfc28c5..478d9541cf0d 100644 --- a/Tests/Common/Brokerages/BinanceBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/BinanceBrokerageModelTests.cs @@ -44,13 +44,8 @@ public void Init() [TestCase(0.000009, false)] public void CanSubmitMarketOrder_OrderSizeIsLargeEnough(decimal orderQuantity, bool isValidOrderQuantity) { - var order = new Mock - { - Object = - { - Quantity = orderQuantity - } - }; + var order = new Mock(); + order.Setup(mock => mock.Quantity).Returns(orderQuantity); _security.Cache.AddData(new Tick { @@ -77,14 +72,9 @@ public void CanSubmitMarketOrder_OrderSizeIsLargeEnough(decimal orderQuantity, b [TestCase(0.0002, 4500, false)] public void CanSubmitLimitOrder_OrderSizeIsLargeEnough(decimal orderQuantity, decimal limitPrice, bool isValidOrderQuantity) { - var order = new Mock - { - Object = - { - Quantity = orderQuantity, - LimitPrice = limitPrice - } - }; + var order = new Mock(); + order.Setup(mock => mock.Quantity).Returns(orderQuantity); + order.Object.LimitPrice = limitPrice; Assert.AreEqual(isValidOrderQuantity, BinanceBrokerageModel.CanSubmitOrder(_security, order.Object, out var message)); Assert.AreEqual(isValidOrderQuantity, message == null); @@ -101,15 +91,10 @@ public void CanSubmitLimitOrder_OrderSizeIsLargeEnough(decimal orderQuantity, de [TestCase(0.003, 5500, 4500, true)] public void CanSubmitStopLimitOrder_OrderSizeIsLargeEnough(decimal orderQuantity, decimal stopPrice, decimal limitPrice, bool isValidOrderQuantity) { - var order = new Mock - { - Object = - { - Quantity = orderQuantity, - StopPrice = stopPrice, - LimitPrice = limitPrice - } - }; + var order = new Mock(); + order.Setup(mock => mock.Quantity).Returns(orderQuantity); + order.Object.StopPrice = stopPrice; + order.Object.LimitPrice = limitPrice; Assert.AreEqual(isValidOrderQuantity, BinanceBrokerageModel.CanSubmitOrder(_security, order.Object, out var message)); Assert.AreEqual(isValidOrderQuantity, message == null); diff --git a/Tests/Common/Brokerages/BitfinexBrokerageModelTests.cs b/Tests/Common/Brokerages/BitfinexBrokerageModelTests.cs index dfd5ec2eebd3..a3e53825ca60 100644 --- a/Tests/Common/Brokerages/BitfinexBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/BitfinexBrokerageModelTests.cs @@ -185,8 +185,7 @@ public void CanSubmitOrder_WhenQuantityIsLargeEnough(decimal orderQuantity, bool { BrokerageMessageEvent message; var order = new Mock(); - - order.Object.Quantity = orderQuantity; + order.Setup(x => x.Quantity).Returns(orderQuantity); Assert.AreEqual(isValidOrderQuantity, _bitfinexBrokerageModel.CanSubmitOrder(TestsHelpers.GetSecurity(market: Market.Bitfinex), order.Object, out message)); } diff --git a/Tests/Common/Brokerages/BybitBrokerageModelTests.cs b/Tests/Common/Brokerages/BybitBrokerageModelTests.cs index 49c058aba928..b643b90bd171 100644 --- a/Tests/Common/Brokerages/BybitBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/BybitBrokerageModelTests.cs @@ -54,13 +54,8 @@ public void Init() [TestCase(0.000001, false)] public void CanSubmitMarketOrder_OrderSizeIsLargeEnough(decimal orderQuantity, bool isValidOrderQuantity) { - var order = new Mock - { - Object = - { - Quantity = orderQuantity - } - }; + var order = new Mock(); + order.Setup(x => x.Quantity).Returns(orderQuantity); _crypto.Cache.AddData(new Tick { @@ -89,11 +84,12 @@ public void CanSubmitLimitOrder_OrderSizeIsLargeEnough(decimal orderQuantity, de { Object = { - Quantity = orderQuantity, LimitPrice = limitPrice } }; + order.Setup(x => x.Quantity).Returns(orderQuantity); + Assert.AreEqual(isValidOrderQuantity, BybitBrokerageModel.CanSubmitOrder(_crypto, order.Object, out var message)); Assert.AreEqual(isValidOrderQuantity, message == null); if (!isValidOrderQuantity) @@ -113,11 +109,11 @@ public void CanSubmitStopLimitOrder_OrderSizeIsLargeEnough(decimal orderQuantity { Object = { - Quantity = orderQuantity, StopPrice = stopPrice, LimitPrice = limitPrice } }; + order.Setup(x => x.Quantity).Returns(orderQuantity); Assert.AreEqual(isValidOrderQuantity, BybitBrokerageModel.CanSubmitOrder(_crypto, order.Object, out var message)); Assert.AreEqual(isValidOrderQuantity, message == null); @@ -130,13 +126,8 @@ public void CanSubmitStopLimitOrder_OrderSizeIsLargeEnough(decimal orderQuantity [Test] public void CanSubmitMarketOrder_IfPriceNotInitialized() { - var order = new Mock - { - Object = - { - Quantity = 1 - } - }; + var order = new Mock(); + order.Setup(x => x.Quantity).Returns(1m); var security = TestsHelpers.GetSecurity(symbol: BTCUSDT.Value, market: BTCUSDT.ID.Market, quoteCurrency: "USDT"); @@ -157,10 +148,10 @@ public void CanSubmitStopMarketOrder_OrderSizeIsLargeEnough(decimal orderQuantit { Object = { - Quantity = orderQuantity, StopPrice = stopPrice } }; + order.Setup(x => x.Quantity).Returns(orderQuantity); var security = TestsHelpers.GetSecurity(symbol: BTCUSDT.Value, market: BTCUSDT.ID.Market, quoteCurrency: "USDT"); diff --git a/Tests/Common/Brokerages/FTXBrokerageModelTests.cs b/Tests/Common/Brokerages/FTXBrokerageModelTests.cs index 22bc57b1a7df..277e674f8228 100644 --- a/Tests/Common/Brokerages/FTXBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/FTXBrokerageModelTests.cs @@ -95,13 +95,8 @@ public void ShouldReturnProperMarket(SecurityType securityType) [TestCase(0.00005, false)] public void CanSubmitOrder_WhenQuantityIsLargeEnough(decimal orderQuantity, bool isValidOrderQuantity) { - var order = new Mock - { - Object = - { - Quantity = orderQuantity - } - }; + var order = new Mock(); + order.Setup(x => x.Quantity).Returns(orderQuantity); Assert.AreEqual(isValidOrderQuantity, _brokerageModel.CanSubmitOrder(TestsHelpers.GetSecurity(market: Market), order.Object, out _)); } diff --git a/Tests/Common/Brokerages/GDAXBrokerageModelTests.cs b/Tests/Common/Brokerages/GDAXBrokerageModelTests.cs index f0b38b11d048..acb4c16b5c6c 100644 --- a/Tests/Common/Brokerages/GDAXBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/GDAXBrokerageModelTests.cs @@ -63,7 +63,7 @@ public void CanSubmitOrder_WhenBrokerageIdIsCorrect(bool isUpdate) { BrokerageMessageEvent message; var order = new Mock(); - order.Object.Quantity = 10.0m; + order.Setup(x => x.Quantity).Returns(10.0m); if (isUpdate) { @@ -79,8 +79,7 @@ public void CanSubmitOrder_WhenQuantityIsLargeEnough(decimal orderQuantity, bool { BrokerageMessageEvent message; var order = new Mock(); - - order.Object.Quantity = orderQuantity; + order.Setup(x => x.Quantity).Returns(orderQuantity); Assert.AreEqual(isValidOrderQuantity, _gdaxBrokerageModel.CanSubmitOrder(TestsHelpers.GetSecurity(market: Market.GDAX), order.Object, out message)); } @@ -95,7 +94,7 @@ public void CanOnlySubmitCryptoOrders(SecurityType securityType, bool isValidSec { BrokerageMessageEvent message; var order = new Mock(); - order.Object.Quantity = 10.0m; + order.Setup(x => x.Quantity).Returns(10.0m); Assert.AreEqual(isValidSecurityType, _gdaxBrokerageModel.CanSubmitOrder(TestsHelpers.GetSecurity(1.0m, securityType), order.Object, out message)); } diff --git a/Tests/Common/Brokerages/KrakenBrokerageModelTests.cs b/Tests/Common/Brokerages/KrakenBrokerageModelTests.cs index 607254bd1523..bbd92cc466e5 100644 --- a/Tests/Common/Brokerages/KrakenBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/KrakenBrokerageModelTests.cs @@ -32,8 +32,7 @@ public void CanSubmitOrder_WhenQuantityIsLargeEnough(decimal orderQuantity, bool { BrokerageMessageEvent message; var order = new Mock(); - - order.Object.Quantity = orderQuantity; + order.Setup(x => x.Quantity).Returns(orderQuantity); Assert.AreEqual(isValidOrderQuantity, _krakenBrokerageModel.CanSubmitOrder(TestsHelpers.GetSecurity(market: Market.Kraken), order.Object, out message)); } diff --git a/Tests/Common/Brokerages/TradierBrokerageModelTests.cs b/Tests/Common/Brokerages/TradierBrokerageModelTests.cs index f8efc557a040..02b3338ab339 100644 --- a/Tests/Common/Brokerages/TradierBrokerageModelTests.cs +++ b/Tests/Common/Brokerages/TradierBrokerageModelTests.cs @@ -41,8 +41,8 @@ public void Init() public void CanSubmitOrderReturnsFalseWhenShortGTCOrder() { var order = GetOrder(); - order.Quantity = -101; - Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order, out var message)); + order.Setup(x => x.Quantity).Returns(-101); + Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order.Object, out var message)); var expectedMessage = new BrokerageMessageEvent(BrokerageMessageType.Warning, "ShortOrderIsGtc", "You cannot place short stock orders with GTC, only day orders are allowed"); Assert.AreEqual(expectedMessage.Message, message.Message); } @@ -51,9 +51,9 @@ public void CanSubmitOrderReturnsFalseWhenShortGTCOrder() public void CanSubmitOrderReturnsFalseWhenSellShortOrderLastPriceBelow5() { var order = GetOrder(); - order.Quantity = -101; - order.Properties.TimeInForce = TimeInForce.Day; - Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order, out var message)); + order.Setup(x => x.Quantity).Returns(-101); + order.Object.Properties.TimeInForce = TimeInForce.Day; + Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order.Object, out var message)); var expectedMessage = new BrokerageMessageEvent(BrokerageMessageType.Warning, "SellShortOrderLastPriceBelow5", "Sell Short order cannot be placed for stock priced below $5"); Assert.AreEqual(expectedMessage.Message, message.Message); } @@ -62,9 +62,9 @@ public void CanSubmitOrderReturnsFalseWhenSellShortOrderLastPriceBelow5() public void CanSubmitOrderReturnsFalseWhenTimeInForceIsGoodTilDate() { var order = GetOrder(); - order.Quantity = 101; - order.Properties.TimeInForce = TimeInForce.GoodTilDate(new DateTime()); - Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order, out var message)); + order.Setup(x => x.Quantity).Returns(101); + order.Object.Properties.TimeInForce = TimeInForce.GoodTilDate(new DateTime()); + Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order.Object, out var message)); var expectedMessage = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported", $"This model only supports orders with the following time in force types: {typeof(DayTimeInForce)} and {typeof(GoodTilCanceledTimeInForce)}"); Assert.AreEqual(expectedMessage.Message, message.Message); } @@ -74,9 +74,9 @@ public void CanSubmitOrderReturnsFalseWhenTimeInForceIsGoodTilDate() public void CanSubmitOrderReturnsFalseWhenIncorrectOrderQuantity(decimal quantity) { var order = GetOrder(); - order.Properties.TimeInForce = TimeInForce.Day; - order.Quantity = quantity; - Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order, out var message)); + order.Object.Properties.TimeInForce = TimeInForce.Day; + order.Setup(x => x.Quantity).Returns(quantity); + Assert.IsFalse(_tradierBrokerageModel.CanSubmitOrder(_security, order.Object, out var message)); var expectedMessage = new BrokerageMessageEvent(BrokerageMessageType.Warning, "IncorrectOrderQuantity", "Quantity should be between 1 and 10,000,000"); Assert.AreEqual(expectedMessage.Message, message.Message); } @@ -85,16 +85,16 @@ public void CanSubmitOrderReturnsFalseWhenIncorrectOrderQuantity(decimal quantit public void CanSubmitOrderReturnsTrueQuantityIsValidAndNotGTC() { var order = GetOrder(); - order.Quantity = -100; - order.Properties.TimeInForce = TimeInForce.Day; - Assert.IsTrue(_tradierBrokerageModel.CanSubmitOrder(_security, order, out var message)); + order.Setup(x => x.Quantity).Returns(-100); + order.Object.Properties.TimeInForce = TimeInForce.Day; + Assert.IsTrue(_tradierBrokerageModel.CanSubmitOrder(_security, order.Object, out var message)); } [Test] public void CanSubmitOrderReturnsTrueWhenQuantityIsValidAndNotGTCAndPriceAbove5() { var order = new Mock(); - order.Object.Quantity = -101; + order.Setup(x => x.Quantity).Returns(-100); order.Object.Properties.TimeInForce = TimeInForce.Day; var security = TestsHelpers.GetSecurity(securityType: SecurityType.Equity, symbol: "IBM", market: Market.USA); security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 100, 1000)); @@ -107,7 +107,7 @@ public void CanSubmitOrderReturnsTrueWhenQuantityIsValidAndNotGTCAndPriceAbove5( public void CanSubmitOrderReturnsTrueWhenQuantityIsValidIsMarketOrderAndPriceAbove5() { var order = new Mock(); - order.Object.Quantity = -100; + order.Setup(x => x.Quantity).Returns(-100); var security = TestsHelpers.GetSecurity(securityType: SecurityType.Equity, symbol: "IBM", market: Market.USA); security.SetMarketPrice(new Tick(DateTime.UtcNow, security.Symbol, 100, 1000)); security.Holdings.SetHoldings(6, 100); @@ -115,11 +115,11 @@ public void CanSubmitOrderReturnsTrueWhenQuantityIsValidIsMarketOrderAndPriceAbo Assert.IsTrue(_tradierBrokerageModel.CanSubmitOrder(security, order.Object, out var message)); } - private Order GetOrder() + private Mock GetOrder() { var order = new Mock(); order.Object.Symbol = _security.Symbol; - return order.Object; + return order; } } } diff --git a/Tests/Common/SeriesSamplerTests.cs b/Tests/Common/SeriesSamplerTests.cs index c9989d2ca127..3577cf3e85c4 100644 --- a/Tests/Common/SeriesSamplerTests.cs +++ b/Tests/Common/SeriesSamplerTests.cs @@ -253,7 +253,8 @@ public void SubSamplesDisabled() series.AddPoint(_reference, 1m); series.AddPoint(_reference.AddDays(1), 2m); series.AddPoint(new ChartPoint(_reference.AddDays(2), null)); - series.AddPoint(_reference.AddDays(3), 4m); + // even if the data doesn't fit exactly the expected bar span we expect it to pass through + series.AddPoint(_reference.AddDays(2.8), 4m); series.AddPoint(_reference.AddDays(4), 5m); series.AddPoint(_reference.AddDays(5), 6m); @@ -791,7 +792,8 @@ public void SamplesCandlestickSeriesWithHigherSamplingResolutionDisabled() var series = new CandlestickSeries { Name = "name" }; series.AddPoint(_reference, 1m, 2m, 1m, 2m); series.AddPoint(_reference.AddDays(1), 2m, 3m, 2m, 3m); - series.AddPoint(_reference.AddDays(2), 4m, 4m, 3m, 3m); + // even if the data doesn't fit exactly the expected bar span we expect it to pass through + series.AddPoint(_reference.AddDays(1.6), 4m, 4m, 3m, 3m); var sampler = new SeriesSampler(TimeSpan.FromDays(0.25)) { SubSample = false }; diff --git a/ToolBox/Program.cs b/ToolBox/Program.cs index c5b025376772..7d0b0e279cfa 100644 --- a/ToolBox/Program.cs +++ b/ToolBox/Program.cs @@ -27,7 +27,6 @@ using QuantConnect.ToolBox.KaikoDataConverter; using QuantConnect.ToolBox.KrakenDownloader; using QuantConnect.ToolBox.NseMarketDataConverter; -using QuantConnect.ToolBox.QuantQuoteConverter; using QuantConnect.ToolBox.RandomDataGenerator; using QuantConnect.ToolBox.YahooDownloader; using QuantConnect.Util; @@ -157,12 +156,6 @@ var factorFileProvider NseMarketDataConverterProgram.NseMarketDataConverter(GetParameterOrExit(optionsObject, "source-dir"), GetParameterOrExit(optionsObject, "destination-dir")); break; - case "qqc": - case "quantquoteconverter": - QuantQuoteConverterProgram.QuantQuoteConverter(GetParameterOrExit(optionsObject, "destination-dir"), - GetParameterOrExit(optionsObject, "source-dir"), - GetParameterOrExit(optionsObject, "resolution")); - break; case "cug": case "coarseuniversegenerator": CoarseUniverseGeneratorProgram.CoarseUniverseGenerator(); diff --git a/ToolBox/QuantConnect.ToolBox.csproj b/ToolBox/QuantConnect.ToolBox.csproj index d6621dbc916c..c3b473933348 100644 --- a/ToolBox/QuantConnect.ToolBox.csproj +++ b/ToolBox/QuantConnect.ToolBox.csproj @@ -63,12 +63,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - diff --git a/ToolBox/QuantQuoteConverter/QuantQuoteConverterProgram.cs b/ToolBox/QuantQuoteConverter/QuantQuoteConverterProgram.cs deleted file mode 100644 index 4fee02c28362..000000000000 --- a/ToolBox/QuantQuoteConverter/QuantQuoteConverterProgram.cs +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using QuantConnect.Util; -using static QuantConnect.StringExtensions; - -namespace QuantConnect.ToolBox.QuantQuoteConverter -{ - public static class QuantQuoteConverterProgram - { - /// - /// Primary entry point to the program - /// - public static void QuantQuoteConverter(string destinationDirectory, string sourceDirectory, string resolution) - { - //Document the process: - Console.WriteLine("QuantConnect.ToolBox: QuantQuote Converter: "); - Console.WriteLine("=============================================="); - Console.WriteLine("The QuantQuote converter transforms QuantQuote orders into the LEAN Algorithmic Trading Engine Data Format."); - Console.WriteLine("Parameters required: --source-dir= --destination-dir= --resolution="); - Console.WriteLine(" 1> Source Directory of Unzipped QuantQuote Data."); - Console.WriteLine(" 2> Destination Directory of LEAN Data Folder. (Typically located under Lean/Data)"); - Console.WriteLine(" 3> Resolution of your QuantQuote data. (either minute, second or tick)"); - Console.WriteLine(" "); - Console.WriteLine("NOTE: THIS WILL OVERWRITE ANY EXISTING FILES."); - if(sourceDirectory.IsNullOrEmpty() || destinationDirectory.IsNullOrEmpty() || resolution.IsNullOrEmpty()) - { - Console.WriteLine("1. Source QuantQuote source directory: "); - sourceDirectory = (Console.ReadLine() ?? ""); - Console.WriteLine("2. Destination LEAN Data directory: "); - destinationDirectory = (Console.ReadLine() ?? ""); - Console.WriteLine("3. Enter Resolution (minute/second/tick): "); - resolution = (Console.ReadLine() ?? ""); - resolution = resolution.ToLowerInvariant(); - } - - //Validate the user input: - Validate(sourceDirectory, destinationDirectory, resolution); - - //Remove the final slash to make the path building easier: - sourceDirectory = StripFinalSlash(sourceDirectory); - destinationDirectory = StripFinalSlash(destinationDirectory); - - //Count the total files to process: - Console.WriteLine("Counting Files..."); - var count = 0; - var totalCount = GetCount(sourceDirectory); - Console.WriteLine(Invariant($"Processing {totalCount} Files ...")); - - //Enumerate files - foreach (var directory in Directory.EnumerateDirectories(sourceDirectory)) - { - var date = GetDate(directory); - foreach (var file in Directory.EnumerateFiles(directory)) - { - var symbol = GetSymbol(file); - var fileContents = File.ReadAllText(file); - var data = new Dictionary { { Invariant($"{date:yyyyMMdd}_{symbol}_Trade_Second.csv"), fileContents } }; - - var fileDestination = Invariant($"{destinationDirectory}/equity/{resolution}/{symbol}/{date:yyyyMMdd}_trade.zip"); - - if (!Compression.ZipData(fileDestination, data)) - { - Error("Error: Could not convert to Lean zip file."); - } - else - { - Console.WriteLine(Invariant($"Successfully processed {count} of {totalCount} files: {fileDestination}")); - } - count++; - } - } - Console.ReadKey(); - } - - - /// - /// Application error: display error and then stop conversion - /// - /// Error string - private static void Error(string error) - { - Console.WriteLine(error); - Console.ReadKey(); - Environment.Exit(0); - } - - /// - /// Get the count of the files to process - /// - /// - /// - private static int GetCount(string sourceDirectory) - { - var count = 0; - foreach (var directory in Directory.EnumerateDirectories(sourceDirectory)) - { - count += Directory.EnumerateFiles(directory, "*.csv").Count(); - } - return count; - } - - /// - /// Remove the final slash to make path building easier - /// - private static string StripFinalSlash(string directory) - { - return directory.Trim('/', '\\'); - } - - /// - /// Get the date component of tie file path. - /// - /// - /// - private static DateTime GetDate(string date) - { - var splits = date.Split('/', '\\'); - var dateString = splits[splits.Length - 1].Replace("allstocks_", ""); - return Parse.DateTimeExact(dateString, "yyyyMMdd"); - } - - - /// - /// Extract the symbol from the path - /// - private static string GetSymbol(string filePath) - { - var splits = filePath.Split('/', '\\'); - var file = splits[splits.Length - 1]; - file = file.Trim( '.', '/', '\\'); - file = file.Replace("table_", ""); - return file.Replace(".csv", ""); - } - - /// - /// Validate the users input and throw error if not valid - /// - private static void Validate(string sourceDirectory, string destinationDirectory, string resolution) - { - if (string.IsNullOrWhiteSpace(sourceDirectory)) - { - Error("Error: Please enter a valid source directory."); - } - if (string.IsNullOrWhiteSpace(destinationDirectory)) - { - Error("Error: Please enter a valid destination directory."); - } - if (!Directory.Exists(sourceDirectory)) - { - Error("Error: Source directory does not exist."); - } - if (!Directory.Exists(destinationDirectory)) - { - Error("Error: Destination directory does not exist."); - } - if (resolution != "minute" && resolution != "second" && resolution != "tick") - { - Error("Error: Resolution specified is not supported. Please enter tick, second or minute"); - } - } - } -} diff --git a/ToolBox/Visualizer/QuantConnect.Visualizer.py b/ToolBox/Visualizer/QuantConnect.Visualizer.py deleted file mode 100644 index 917b8deaa677..000000000000 --- a/ToolBox/Visualizer/QuantConnect.Visualizer.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -Usage: - QuantConnect.Visualizer.py DATAFILE [--assembly assembly_path] [--output output_folder] [--size height,width] - -Arguments: - DATAFILE Absolute or relative path to a zipped data file to plot. - Optionally the zip entry file can be declared by using '#' as separator. - -Options: - -h --help show this. - -a --assembly assembly_path path to the folder with the assemblies dll/exe [default: ../.]. - -o --output output_folder path to the output folder, each new plot will be saved there with a random name [default: ./output_folder]. - -s, --size height,width plot size in pixels [default: 800,400]. - -Examples: - QuantConnect.Visualizer.py ../relative/path/to/file.zip - QuantConnect.Visualizer.py absolute/path/to/file.zip#zipEntry.csv - QuantConnect.Visualizer.py absolute/path/to/file.zip -o path/to/image.png -s 1024,800 -""" - -import json -import os -import sys -import uuid -from clr import AddReference -from pathlib import Path -from numpy import NaN - -import matplotlib as mpl - -mpl.use('Agg') - -from docopt import docopt -from matplotlib.dates import DateFormatter - - -class Visualizer: - """ - Python wrapper for the Lean ToolBox.Visualizer. - - This class is instantiated with the dictionary docopt generates from the CLI arguments. - - It contains the methods for set up and load the C# assemblies into Python. The QuantConnect.ToolBox assembly folder - can be declared in the module's CLI. - """ - def __init__(self, arguments): - self.arguments = arguments - zipped_data_file = Path(self.arguments['DATAFILE'].split('#')[0]) - if not zipped_data_file.exists(): - raise FileNotFoundError(f'File {zipped_data_file.resolve().absolute()} does not exist') - self.palette = ['#f5ae29', '#657584', '#b1b9c3', '#222222'] - # Loads the Toolbox to access Visualizer - self.setup_and_load_toolbox() - # Sets up the Composer - from QuantConnect.Data.Auxiliary import LocalDiskMapFileProvider - from QuantConnect.Util import Composer - from QuantConnect.Interfaces import IMapFileProvider - localDiskMapFileProvider = LocalDiskMapFileProvider() - Composer.Instance.AddPart[IMapFileProvider](localDiskMapFileProvider) - # Initizlize LeanDataReader and PandasConverter - from QuantConnect.ToolBox import LeanDataReader - from QuantConnect.Python import PandasConverter - self.lean_data_reader = LeanDataReader(self.arguments['DATAFILE']) - self.pandas_converter = PandasConverter() - # Generate random name for the plot. - self.plot_filename = self.generate_plot_filename() - - def setup_and_load_toolbox(self): - """ - Checks if the path given in the CLI (or its defaults values) contains the needed assemblies. - - :return: void. - :raise: NotImplementedError: if the needed assemblies dll are not available. - """ - # Check Lean assemblies are present in the composer-dll-directory key provided. - assemblies_folder_info = (Path(self.arguments['--assembly'])) - toolbox_assembly = assemblies_folder_info.joinpath('QuantConnect.ToolBox.exe') - common_assembly = assemblies_folder_info.joinpath('QuantConnect.Common.dll') - if not (toolbox_assembly.exists() and common_assembly.exists()): - raise KeyError("Please set up the '--assembly' option with the path to Lean assemblies.\n" + - f"Absolute path provided: {assemblies_folder_info.resolve().absolute()}") - - AddReference(str(toolbox_assembly.resolve().absolute())) - AddReference(str(common_assembly.resolve().absolute())) - os.chdir(str(assemblies_folder_info.resolve().absolute())) - return - - def generate_plot_filename(self): - """ - Generates a random name for the output plot image file in the default folder defined in the CLI. - - :return: an absolute path to the output plot image file. - """ - default_output_folder = (Path(self.arguments['--output'])) - if not default_output_folder.exists(): - os.makedirs(str(default_output_folder.resolve().absolute())) - file_name = f'{str(uuid.uuid4())[:8]}.png' - file_path = default_output_folder.joinpath(file_name) - return str(file_path.resolve().absolute()) - - def get_data(self): - """ - Makes use of the Lean's Toolbox LeanDataReader plus the PandasConverter to parse the data as pandas.DataFrame - from a given zip file and an optional internal filename for option and futures. - - :return: a pandas.DataFrame with the data from the file. - """ - - from QuantConnect.Data import BaseData - df = self.pandas_converter.GetDataFrame[BaseData](self.lean_data_reader.Parse()) - if df.empty: - raise Exception("Data frame is empty") - symbol = df.index.levels[0][0] - return df.loc[symbol] - - def filter_data(self, df): - """ - Applies the filters defined in the CLI arguments to the parsed data. - Not fully implemented yet, it only selects the close columns. - - :param df: pandas.DataFrame with all the data form the selected file. - :return: a filtered pandas.DataFrame. - - TODO: implement column and time filters. - """ - if 'tick' in self.arguments['DATAFILE']: - cols_to_plot = [col for col in df.columns if 'price' in col] - else: - cols_to_plot = [col for col in df.columns if 'close' in col] - if 'openinterest' in self.arguments['DATAFILE']: - cols_to_plot = ['openinterest'] - cols_to_plot = cols_to_plot[:2] if len(cols_to_plot) == 3 else cols_to_plot - df = df.loc[:, cols_to_plot] - return df - - def plot_and_save_image(self, data): - """ - Plots the data and saves the plot as a png image. - - :param data: a pandas.DataFrame with the data to plot. - :return: void - """ - is_future_tick = ('future' in self.arguments['DATAFILE'] and 'tick' in self.arguments['DATAFILE'] - and 'quote' in self.arguments['DATAFILE']) - if is_future_tick: - data = data.replace(0, NaN) - - plot = data.plot(grid=True, color=self.palette) - - is_low_resolution_data = 'hour' in self.arguments['DATAFILE'] or 'daily' in self.arguments['DATAFILE'] - if not is_low_resolution_data: - plot.xaxis.set_major_formatter(DateFormatter("%H:%M")) - plot.set_xlabel(self.lean_data_reader.GetDataTimeZone().Id) - - is_forex = 'forex' in self.arguments['DATAFILE'] - is_open_interest = 'openinterest' in self.arguments['DATAFILE'] - if is_forex: - plot.set_ylabel('exchange rate') - elif is_open_interest: - plot.set_ylabel('open contracts') - else: - plot.set_ylabel('price (USD)') - - fig = plot.get_figure() - size_px = [int(p) for p in self.arguments['--size'].split(',')] - fig.set_size_inches(size_px[0] / fig.dpi, size_px[1] / fig.dpi) - fig.savefig(self.plot_filename, transparent=True, dpi=fig.dpi) - return - - -if __name__ == "__main__": - arguments = docopt(__doc__) - visualizer = Visualizer(arguments) - # Gets the pandas.DataFrame from the data file - df = visualizer.get_data() - # Selects the columns you want to plot - df = visualizer.filter_data(df) - # Save the image - visualizer.plot_and_save_image(df) - print(visualizer.plot_filename) - sys.exit(0) diff --git a/ToolBox/Visualizer/__init__.py b/ToolBox/Visualizer/__init__.py deleted file mode 100644 index 8b137891791f..000000000000 --- a/ToolBox/Visualizer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -