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))
{