/* * 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 Python.Runtime; using QuantConnect.Orders; using QuantConnect.Securities; using System; using System.Collections.Generic; namespace QuantConnect.Python { /// /// Provides a margin call model that wraps a object that represents the model responsible for picking which orders should be executed during a margin call /// public class MarginCallModelPythonWrapper : IMarginCallModel { private readonly dynamic _model; /// /// Constructor for initialising the class with wrapped object /// /// Represents the model responsible for picking which orders should be executed during a margin call public MarginCallModelPythonWrapper(PyObject model) { using (Py.GIL()) { foreach (var attributeName in new[] { "ExecuteMarginCall", "GetMarginCallOrders" }) { if (!model.HasAttr(attributeName)) { throw new NotImplementedException($"IMarginCallModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}"); } } } _model = model; } /// /// Executes synchronous orders to bring the account within margin requirements. /// /// These are the margin call orders that were generated /// by individual security margin models. /// The list of orders that were actually executed public List ExecuteMarginCall(IEnumerable generatedMarginCallOrders) { using (Py.GIL()) { var marginCalls = _model.ExecuteMarginCall(generatedMarginCallOrders) as PyObject; // Since ExecuteMarginCall may return a python list // Need to convert to C# list var tickets = new List(); var iterator = marginCalls.GetIterator(); foreach (PyObject pyObject in iterator) { OrderTicket ticket; if (pyObject.TryConvert(out ticket)) { tickets.Add(ticket); } pyObject.Dispose(); } iterator.Dispose(); marginCalls.Dispose(); return tickets; } } /// /// Scan the portfolio and the updated data for a potential margin call situation which may get the holdings below zero! /// If there is a margin call, liquidate the portfolio immediately before the portfolio gets sub zero. /// /// Set to true if a warning should be issued to the algorithm /// True for a margin call on the holdings. public List GetMarginCallOrders(out bool issueMarginCallWarning) { using (Py.GIL()) { var value = _model.GetMarginCallOrders(out issueMarginCallWarning); // Since pythonnet does not support out parameters, the methods return // a tuple where the out parameter comes after the other returned values if (!PyTuple.IsTupleType(value)) { throw new ArgumentException($"{_model.__class__.__name__}.GetMarginCallOrders: Must return a tuple, where the first item is a list and the second a boolean"); } // In this case, the first item holds the list of margin calls // and the second the out parameter 'issueMarginCallWarning' var marginCallOrders = value[0] as PyObject; issueMarginCallWarning = (value[1] as PyObject).GetAndDispose(); // Since GetMarginCallOrders may return a python list // Need to convert to C# list var requests = new List(); foreach (PyObject pyObject in marginCallOrders) { SubmitOrderRequest request; if (pyObject.TryConvert(out request)) { requests.Add(request); } } issueMarginCallWarning |= requests.Count > 0; marginCallOrders.Dispose(); (value as PyObject).Dispose(); return requests; } } } }