diff --git a/Algorithm.CSharp/Collective2PortfolioSignalExportDemonstrationAlgorithm.cs b/Algorithm.CSharp/Collective2PortfolioSignalExportDemonstrationAlgorithm.cs index dfe6b2919814..9879374ca80f 100644 --- a/Algorithm.CSharp/Collective2PortfolioSignalExportDemonstrationAlgorithm.cs +++ b/Algorithm.CSharp/Collective2PortfolioSignalExportDemonstrationAlgorithm.cs @@ -80,8 +80,10 @@ public override void Initialize() // Initialize this flag, to check when the ema indicators crosses between themselves _emaFastIsNotSet = true; + // Disable automatic exports as we manually set them + SignalExport.AutomaticExportTimeSpan = null; // Set Collective2 signal export provider - SignalExport.AddSignalExportProviders(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId)); + SignalExport.AddSignalExportProvider(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId)); SetWarmUp(100); } diff --git a/Algorithm.CSharp/Collective2SignalExportDemonstrationAlgorithm.cs b/Algorithm.CSharp/Collective2SignalExportDemonstrationAlgorithm.cs index 9b59b1072ae0..0f394777b7fa 100644 --- a/Algorithm.CSharp/Collective2SignalExportDemonstrationAlgorithm.cs +++ b/Algorithm.CSharp/Collective2SignalExportDemonstrationAlgorithm.cs @@ -95,12 +95,15 @@ public override void Initialize() // Initialize this flag, to check when the ema indicators crosses between themselves _emaFastIsNotSet = true; + // Disable automatic exports as we manually set them + SignalExport.AutomaticExportTimeSpan = null; + // Set Collective2 signal export provider. // If using the Collective2 white-label API, you can specify it in the constructor with the optional parameter `useWhiteLabelApi`: // e.g. new Collective2SignalExport(_collective2ApiKey, _collective2SystemId, useWhiteLabelApi: true) // The API url can also be overridden by setting the Destination property: // e.g. new Collective2SignalExport(_collective2ApiKey, _collective2SystemId) { Destination = new Uri("your url") } - SignalExport.AddSignalExportProviders(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId)); + SignalExport.AddSignalExportProvider(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId)); SetWarmUp(100); } diff --git a/Algorithm.CSharp/CustomSignalExportDemonstrationAlgorithm.cs b/Algorithm.CSharp/CustomSignalExportDemonstrationAlgorithm.cs new file mode 100644 index 000000000000..f302f8bce321 --- /dev/null +++ b/Algorithm.CSharp/CustomSignalExportDemonstrationAlgorithm.cs @@ -0,0 +1,113 @@ +/* + * 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 Newtonsoft.Json; +using QuantConnect.Algorithm.Framework.Portfolio; +using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; +using QuantConnect.Api; +using QuantConnect.Data; +using QuantConnect.Interfaces; +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; + +namespace QuantConnect.Algorithm.CSharp +{ + /// + /// This algorithm sends a list of portfolio targets to custom endpoint + /// + /// + /// + /// + public class CustomSignalExportDemonstrationAlgorithm : QCAlgorithm + { + /// + /// Initialize the date and add all equity symbols present + /// + public override void Initialize() + { + SetStartDate(2013, 10, 07); + SetEndDate(2013, 10, 11); + + /// Our custom signal export accepts all asset types + AddEquity("SPY", Resolution.Second); + AddCrypto("BTCUSD", Resolution.Second); + AddForex("EURUSD", Resolution.Second); + AddFutureContract(QuantConnect.Symbol.CreateFuture("ES", Market.CME, new DateTime(2023, 12, 15), null)); + AddOptionContract(QuantConnect.Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, OptionRight.Call, 130, new DateTime(2023, 9, 1))); + + // Set CustomSignalExport signal export provider. + SignalExport.AddSignalExportProvider(new CustomSignalExport()); + } + + /// + /// Buy and hold EURUSD and SPY + /// + /// + public override void OnData(Slice slice) + { + foreach (var ticker in new[] { "SPY", "EURUSD", "BTCUSD" }) + { + if (!Portfolio[ticker].Invested && Securities[ticker].HasData) + { + SetHoldings(ticker, 0.5m); + } + } + } + } + + internal class CustomSignalExport : ISignalExportTarget + { + private readonly Uri _requestUri = new ("http://localhost:5000/"); + private readonly HttpClient _httpClient = new(); + + public bool Send(SignalExportTargetParameters parameters) + { + object SimplePayload(PortfolioTarget target) + { + var newTarget = PortfolioTarget.Percent(parameters.Algorithm, target.Symbol, target.Quantity); + return new { symbol = newTarget.Symbol.Value, quantity = newTarget.Quantity }; + }; + + var message = JsonConvert.SerializeObject(parameters.Targets.Select(SimplePayload)); + using var httpMessage = new StringContent(message, Encoding.UTF8, "application/json"); + using HttpResponseMessage response = _httpClient.PostAsync(_requestUri, httpMessage).Result; + var result = response.Content.ReadFromJsonAsync().Result; + parameters.Algorithm.Log($"Send #{parameters.Targets.Count} targets. Success: {result.Success}"); + return result.Success; + } + + public void Dispose() => _httpClient.Dispose(); + } +} + +/* +# To test the algorithm, you can create a simple Python Flask application (app.py) and run flask +# $ flask --app app run + +# app.py: +from flask import Flask, request, jsonify +from json import loads +app = Flask(__name__) +@app.post('/') +def handle_positions(): + result = loads(request.data) + print(result) + return jsonify({'success': True,'message': f'{len(result)} positions received'}) +if __name__ == '__main__': + app.run(debug=True) +*/ diff --git a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs index 67cd539d57e6..1744bb33dd4b 100644 --- a/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs +++ b/Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs @@ -43,6 +43,7 @@ public override void Initialize() _index = AddIndex("SPX").Symbol; _equity = AddEquity("SPY").Symbol; + SignalExport.AutomaticExportTimeSpan = null; _signalExportManagerTest = new SignalExportManagerTest(this); Securities[_index].IsTradable = IsTradable; } diff --git a/Algorithm.CSharp/NumeraiSignalExportDemonstrationAlgorithm.cs b/Algorithm.CSharp/NumeraiSignalExportDemonstrationAlgorithm.cs index 8242edb13f6c..0524092c3808 100644 --- a/Algorithm.CSharp/NumeraiSignalExportDemonstrationAlgorithm.cs +++ b/Algorithm.CSharp/NumeraiSignalExportDemonstrationAlgorithm.cs @@ -64,7 +64,12 @@ public override void Initialize() var numeraiModelId = ""; var numeraiFilename = ""; // (Optional) Replace this value with your submission filename - SignalExport.AddSignalExportProviders(new NumeraiSignalExport(numeraiPublicId, numeraiSecretId, numeraiModelId, numeraiFilename)); + + // Disable automatic exports as we manually set them + SignalExport.AutomaticExportTimeSpan = null; + + // Set Numerai signal export provider + SignalExport.AddSignalExportProvider(new NumeraiSignalExport(numeraiPublicId, numeraiSecretId, numeraiModelId, numeraiFilename)); } public void SubmitSignals() diff --git a/Algorithm.CSharp/RegressionTests/Collective2IndexOptionAlgorithm.cs b/Algorithm.CSharp/RegressionTests/Collective2IndexOptionAlgorithm.cs index 9bcb3f8d2f8f..b0232dd33e24 100644 --- a/Algorithm.CSharp/RegressionTests/Collective2IndexOptionAlgorithm.cs +++ b/Algorithm.CSharp/RegressionTests/Collective2IndexOptionAlgorithm.cs @@ -63,8 +63,10 @@ public override void Initialize() _fast = EMA(underlying, 10, Resolution.Minute); _slow = EMA(underlying, 50, Resolution.Minute); + // Disable automatic exports as we manually set them + SignalExport.AutomaticExportTimeSpan = null; // Set up the Collective2 Signal Export with the provided API key and system ID - SignalExport.AddSignalExportProviders(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId)); + SignalExport.AddSignalExportProvider(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId)); // Set warm-up period for the indicators SetWarmUp(50); diff --git a/Algorithm.Python/Collective2PortfolioSignalExportDemonstrationAlgorithm.py b/Algorithm.Python/Collective2PortfolioSignalExportDemonstrationAlgorithm.py index 2682712fa47d..543c124a84d5 100644 --- a/Algorithm.Python/Collective2PortfolioSignalExportDemonstrationAlgorithm.py +++ b/Algorithm.Python/Collective2PortfolioSignalExportDemonstrationAlgorithm.py @@ -54,7 +54,11 @@ def initialize(self): # Collective2 System ID: This value is found beside the system's name (strategy's name) on the main system page self.collective2_system_id = 0 - self.signal_export.add_signal_export_providers(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id)) + # Disable automatic exports as we manually set them + self.signal_export.automatic_export_time_span = None + + # Set Collective2 signal export provider + self.signal_export.add_signal_export_provider(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id)) self.first_call = True diff --git a/Algorithm.Python/Collective2SignalExportDemonstrationAlgorithm.py b/Algorithm.Python/Collective2SignalExportDemonstrationAlgorithm.py index 4a670cecb50d..8b793649c08e 100644 --- a/Algorithm.Python/Collective2SignalExportDemonstrationAlgorithm.py +++ b/Algorithm.Python/Collective2SignalExportDemonstrationAlgorithm.py @@ -61,11 +61,14 @@ def initialize(self): # Collective2 System ID: This value is found beside the system's name (strategy's name) on the main system page self.collective2_system_id = 0 + # Disable automatic exports as we manually set them + self.signal_export.automatic_export_time_span = None + # If using the Collective2 white-label API, you can specify it in the constructor with the optional parameter `use_white_label_api`: # e.g. Collective2SignalExport(self.collective2_apikey, self.collective2_system_id, use_white_label_api=True) # The API url can also be overridden by setting the Destination property: # e.g. Collective2SignalExport(self.collective2_apikey, self.collective2_system_id) { Destination = new Uri("your url") } - self.signal_export.add_signal_export_providers(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id)) + self.signal_export.add_signal_export_provider(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id)) self.first_call = True diff --git a/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py b/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py index 4c140d8d7020..e6d7e1622dc8 100644 --- a/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py +++ b/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py @@ -28,15 +28,18 @@ def initialize(self): self.set_end_date(2023, 5, 26) self.set_cash(1_000_000) + # Disable automatic exports as we manually set them + self.signal_export.automatic_export_time_span = None + # Connect to CrunchDAO api_key = "" # Your CrunchDAO API key model = "" # The Id of your CrunchDAO model submission_name = "" # A name for the submission to distinguish it from your other submissions comment = "" # A comment for the submission - self.signal_export.add_signal_export_providers(CrunchDAOSignalExport(api_key, model, submission_name, comment)) + self.signal_export.add_signal_export_provider(CrunchDAOSignalExport(api_key, model, submission_name, comment)) self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) - + # Add a custom data universe to read the CrunchDAO skeleton self.add_universe(CrunchDaoSkeleton, "CrunchDaoSkeleton", Resolution.DAILY, self.select_symbols) diff --git a/Algorithm.Python/CustomSignalExportDemonstrationAlgorithm.py b/Algorithm.Python/CustomSignalExportDemonstrationAlgorithm.py new file mode 100644 index 000000000000..1779b17fa747 --- /dev/null +++ b/Algorithm.Python/CustomSignalExportDemonstrationAlgorithm.py @@ -0,0 +1,77 @@ +# 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. + +from AlgorithmImports import * + +### +### his algorithm sends a list of portfolio targets to custom endpoint +### +### +### +### +class CustomSignalExportDemonstrationAlgorithm(QCAlgorithm): + + def initialize(self) -> None: + ''' Initialize the date and add all equity symbols present in list _symbols ''' + + self.set_start_date(2013, 10, 7) #Set Start Date + self.set_end_date(2013, 10, 11) #Set End Date + self.set_cash(100000) #Set Strategy Cash + + # Our custom signal export accepts all asset types + self.add_equity("SPY", Resolution.SECOND) + self.add_crypto("BTCUSD", Resolution.SECOND) + self.add_forex("EURUSD", Resolution.SECOND) + self.add_future_contract(Symbol.create_future("ES", Market.CME, datetime(2023, 12, 15), None)) + self.add_option_contract(Symbol.create_option("SPY", Market.USA, OptionStyle.American, OptionRight.Call, 130, datetime(2023, 9, 1))) + + # Set CustomSignalExport signal export provider. + self.signal_export.add_signal_export_provider(CustomSignalExport()) + + def on_data(self, data: Slice) -> None: + '''Buy and hold EURUSD and SPY''' + for ticker in [ "SPY", "EURUSD", "BTCUSD" ]: + if not self.portfolio[ticker].invested and self.securities[ticker].has_data: + self.set_holdings(ticker, 0.5) + +from requests import post +class CustomSignalExport: + def send(self, parameters: SignalExportTargetParameters) -> bool: + targets = [PortfolioTarget.percent(parameters.algorithm, x.symbol, x.quantity) + for x in parameters.targets] ; + data = [ {'symbol' : x.symbol.value, 'quantity': x.quantity} for x in targets ] + response = post("http://localhost:5000/", json = data) + result = response.json() + success = result.get('success', False) + parameters.algorithm.log(f"Send #{len(parameters.targets)} targets. Success: {success}"); + return success + + def dispose(self): + pass + +''' +# To test the algorithm, you can create a simple Python Flask application (app.py) and run flask +# $ flask --app app run + +# app.py: +from flask import Flask, request, jsonify +from json import loads +app = Flask(__name__) +@app.post('/') +def handle_positions(): + result = loads(request.data) + print(result) + return jsonify({'success': True,'message': f'{len(result)} positions received'}) +if __name__ == '__main__': + app.run(debug=True) +''' diff --git a/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py b/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py index 000b258c1088..7f95dd3b7d3e 100644 --- a/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py +++ b/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py @@ -55,7 +55,12 @@ def initialize(self): numerai_model_id = "" numerai_filename = "" # (Optional) Replace this value with your submission filename - self.signal_export.add_signal_export_providers(NumeraiSignalExport(numerai_public_id, numerai_secret_id, numerai_model_id, numerai_filename)) + + # Disable automatic exports as we manually set them + self.signal_export.automatic_export_time_span = None + + # Set Numerai signal export provider + self.signal_export.add_signal_export_provider(NumeraiSignalExport(numerai_public_id, numerai_secret_id, numerai_model_id, numerai_filename)) def submit_signals(self): diff --git a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs index 0ab8d255923f..9c5b7896026d 100644 --- a/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs +++ b/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs @@ -38,6 +38,7 @@ using QuantConnect.Data.Market; using QuantConnect.Algorithm.Framework.Alphas.Analysis; using QuantConnect.Commands; +using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; namespace QuantConnect.AlgorithmFactory.Python.Wrappers { @@ -564,6 +565,12 @@ public int ProjectId /// public StatisticsResults Statistics => _baseAlgorithm.Statistics; + /// + /// SignalExport - Allows sending export signals to different 3rd party API's. For example, it allows to send signals + /// to Collective2, CrunchDAO and Numerai API's + /// + public SignalExportManager SignalExport => ((QCAlgorithm)_baseAlgorithm).SignalExport; + /// /// Set a required SecurityType-symbol and resolution for algorithm /// diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs index e9e8afac6e08..b1b076f71e7d 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs @@ -13,11 +13,15 @@ * limitations under the License. */ +using Python.Runtime; using QuantConnect.Interfaces; +using QuantConnect.Orders; +using QuantConnect.Python; using QuantConnect.Securities; using System.Collections.Generic; using System; using System.Linq; +using QuantConnect.Util; namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports { @@ -27,6 +31,11 @@ namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports /// public class SignalExportManager { + /// + /// Records the time of the first order event of a group of events + /// + private ReferenceWrapper _initialOrderEventTimeUtc = new(Time.EndOfTime); + /// /// List of signal export providers /// @@ -42,6 +51,12 @@ public class SignalExportManager /// private bool _isLiveWarningModeLog; + /// + /// Gets the maximim time span elapsed to export signals after an order event + /// If null, disable automatic export. + /// + public TimeSpan? AutomaticExportTimeSpan { get; set; } = TimeSpan.FromSeconds(5); + /// /// SignalExportManager Constructor, obtains the entry information needed to send signals /// and initializes the fields to be used @@ -53,15 +68,51 @@ public SignalExportManager(IAlgorithm algorithm) _isLiveWarningModeLog = false; } + /// + /// Adds a new signal exports provider + /// + /// Signal export provider + public void AddSignalExportProvider(ISignalExportTarget signalExport) + { + _signalExports ??= []; + _signalExports.Add(signalExport); + } + + /// + /// Adds a new signal exports provider + /// + /// Signal export provider + public void AddSignalExportProvider(PyObject signalExport) + { + if (!signalExport.TryConvert(out var managedSignalExport)) + { + managedSignalExport = new SignalExportTargetPythonWrapper(signalExport); + } + AddSignalExportProvider(managedSignalExport); + } + /// /// Adds one or more new signal exports providers /// /// One or more signal export provider public void AddSignalExportProviders(params ISignalExportTarget[] signalExports) { - _signalExports ??= new List(); + signalExports.DoForEach(AddSignalExportProvider); + } - _signalExports.AddRange(signalExports); + /// + /// Adds one or more new signal exports providers + /// + /// One or more signal export provider + public void AddSignalExportProviders(PyObject signalExports) + { + using var _ = Py.GIL(); + if (!signalExports.IsIterable()) + { + AddSignalExportProvider(signalExports); + return; + } + PyList.AsList(signalExports).DoForEach(AddSignalExportProvider); } /// @@ -174,5 +225,47 @@ private IEnumerable GetPortfolioTargets(decimal totalPortfolioV yield return new PortfolioTarget(holding.Symbol, adjustedHoldingPercent); } } + + /// + /// New order event handler: on order status changes (filled, partially filled, cancelled etc). + /// + /// Event information + public void OnOrderEvent(OrderEvent orderEvent) + { + if (_initialOrderEventTimeUtc.Value == Time.EndOfTime && orderEvent.Status.IsFill()) + { + _initialOrderEventTimeUtc = new(orderEvent.UtcTime); + } + } + + /// + /// Set the target portfolio after order events. + /// + /// The current time of synchronous events + public void Flush(DateTime currentTimeUtc) + { + var initialOrderEventTimeUtc = _initialOrderEventTimeUtc.Value; + if (initialOrderEventTimeUtc == Time.EndOfTime || !AutomaticExportTimeSpan.HasValue) + { + return; + } + + if (currentTimeUtc - initialOrderEventTimeUtc < AutomaticExportTimeSpan) + { + return; + } + + try + { + SetTargetPortfolioFromPortfolio(); + } + catch (Exception exception) + { + // SetTargetPortfolioFromPortfolio logs all known error on LEAN side. + // Exceptions occurs in the ISignalExportTarget.Send method (user-defined). + _algorithm.Error($"Failed to send portfolio target(s). Reason: {exception.Message}.{Environment.NewLine}{exception.StackTrace}"); + } + _initialOrderEventTimeUtc = new(Time.EndOfTime); + } } } diff --git a/Common/Python/SignalExportTargetPythonWrapper.cs b/Common/Python/SignalExportTargetPythonWrapper.cs new file mode 100644 index 000000000000..6e30270514d4 --- /dev/null +++ b/Common/Python/SignalExportTargetPythonWrapper.cs @@ -0,0 +1,49 @@ +/* + * 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.Algorithm.Framework.Portfolio.SignalExports; +using QuantConnect.Interfaces; + +namespace QuantConnect.Python +{ + /// + /// Provides an implementation of that wraps a object + /// + public class SignalExportTargetPythonWrapper : BasePythonWrapper, ISignalExportTarget + { + /// + /// Constructor for initialising the class with wrapped object + /// + /// The underlying python instance + public SignalExportTargetPythonWrapper(PyObject instance) : base(instance) { } + + /// + /// Interface to send positions holdings to different 3rd party API's + /// + public bool Send(SignalExportTargetParameters parameters) + { + return InvokeMethod(nameof(Send), parameters); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + InvokeMethod(nameof(Dispose)); + } + } +} diff --git a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs index cb9b9274e646..22abb1d79550 100644 --- a/Engine/TransactionHandlers/BrokerageTransactionHandler.cs +++ b/Engine/TransactionHandlers/BrokerageTransactionHandler.cs @@ -19,6 +19,9 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; +using QuantConnect.Algorithm; +using QuantConnect.Algorithm.Framework.Portfolio.SignalExports; +using QuantConnect.AlgorithmFactory.Python.Wrappers; using QuantConnect.Brokerages; using QuantConnect.Brokerages.Backtesting; using QuantConnect.Interfaces; @@ -38,6 +41,7 @@ namespace QuantConnect.Lean.Engine.TransactionHandlers public class BrokerageTransactionHandler : ITransactionHandler { private IAlgorithm _algorithm; + private SignalExportManager _signalExport; private IBrokerage _brokerage; private bool _brokerageIsBacktesting; private bool _loggedFeeAdjustmentWarning; @@ -205,6 +209,12 @@ public virtual void Initialize(IAlgorithm algorithm, IBrokerage brokerage, IResu IsActive = true; _algorithm = algorithm; + + _signalExport = _algorithm is QCAlgorithm + ? (_algorithm as QCAlgorithm).SignalExport + : (_algorithm as AlgorithmPythonWrapper).SignalExport; + + NewOrderEvent += (s, e) => _signalExport.OnOrderEvent(e); InitializeTransactionThread(); } @@ -659,6 +669,8 @@ public virtual void ProcessSynchronousEvents() return; } + _signalExport.Flush(CurrentTimeUtc); + // check if the brokerage should perform cash sync now if (!_algorithm.IsWarmingUp && _brokerage.ShouldPerformCashSync(CurrentTimeUtc)) {