/* * 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.Linq; using QuantConnect.Interfaces; using QuantConnect.Orders; using QuantConnect.Securities; namespace QuantConnect.Algorithm.CSharp { /// /// Base algorithm to assert that the margin call events are fired when trading options /// public abstract class OptionsMarginCallEventsAlgorithmBase : QCAlgorithm, IRegressionAlgorithmDefinition { private int _onMarginCallWarningCount; private int _onMarginCallCount; private bool _firstOrderEventReceived; protected abstract int OriginalQuantity { get; } protected abstract int ExpectedOrdersCount { get; } public override void OnMarginCall(List requests) { Debug($"OnMarginCall at {Time}"); _onMarginCallCount++; } public override void OnMarginCallWarning() { Debug($"OnMarginCallWarning at {Time}"); _onMarginCallWarningCount++; } public override void OnOrderEvent(OrderEvent orderEvent) { if (orderEvent.Status == OrderStatus.Filled && !_firstOrderEventReceived) { _firstOrderEventReceived = true; // Make sure the algorithms implementing this class place orders with the expected quantity for // the check in the OnEndOfAlgorithm method to be accurate. if (orderEvent.Quantity != OriginalQuantity) { throw new RegressionTestException($"Expected order quantity to be {OriginalQuantity} but was {orderEvent.Quantity}"); } } } public override void OnEndOfAlgorithm() { if (!Portfolio.Invested) { throw new RegressionTestException("Portfolio should be invested"); } if (_onMarginCallCount != 1) { throw new RegressionTestException($"OnMarginCall was called {_onMarginCallCount} times, expected 1"); } if (_onMarginCallWarningCount == 0) { throw new RegressionTestException("OnMarginCallWarning was not called"); } var orders = Transactions.GetOrders().ToList(); if (orders.Count != ExpectedOrdersCount) { throw new RegressionTestException($"Expected {ExpectedOrdersCount} orders, found {orders.Count}"); } if (orders.Any(order => !order.Status.IsFill())) { throw new RegressionTestException("All orders should be filled"); } var finalStrategyQuantity = Portfolio.Positions.Groups.First().Quantity; if (Math.Abs(OriginalQuantity) <= Math.Abs(finalStrategyQuantity)) { throw new RegressionTestException($@"Strategy position group quantity should have been decreased from the original quantity {OriginalQuantity }, but was {finalStrategyQuantity}"); } } protected class CustomMarginCallModel : DefaultMarginCallModel { // Setting margin buffer to 0 so we make sure the margin call orders are generated. Otherwise, they will only // be generated if the used margin is > 110%TVP, which is unlikely for this case public CustomMarginCallModel(SecurityPortfolioManager portfolio, IOrderProperties defaultOrderProperties) : base(portfolio, defaultOrderProperties, 0m) { } } public abstract bool CanRunLocally { get; } public abstract List Languages { get; } public abstract long DataPoints { get; } public abstract int AlgorithmHistoryDataPoints { get; } public abstract AlgorithmStatus AlgorithmStatus { get; } public abstract Dictionary ExpectedStatistics { get; } } }