Skip to content

Commit 1cb0405

Browse files
authored
Adds Custom Signal Export Example (#8674)
* Adds example of custom signal exports. - SignalExportManager send signals automatically after 5 seconds. - Missing example and Python support. * Updates Examples to Disable Automatic Export * Addresses Peer-Review
1 parent 1e4ef83 commit 1cb0405

15 files changed

+390
-11
lines changed

Algorithm.CSharp/Collective2PortfolioSignalExportDemonstrationAlgorithm.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ public override void Initialize()
8080
// Initialize this flag, to check when the ema indicators crosses between themselves
8181
_emaFastIsNotSet = true;
8282

83+
// Disable automatic exports as we manually set them
84+
SignalExport.AutomaticExportTimeSpan = null;
8385
// Set Collective2 signal export provider
84-
SignalExport.AddSignalExportProviders(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId));
86+
SignalExport.AddSignalExportProvider(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId));
8587

8688
SetWarmUp(100);
8789
}

Algorithm.CSharp/Collective2SignalExportDemonstrationAlgorithm.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,15 @@ public override void Initialize()
9595
// Initialize this flag, to check when the ema indicators crosses between themselves
9696
_emaFastIsNotSet = true;
9797

98+
// Disable automatic exports as we manually set them
99+
SignalExport.AutomaticExportTimeSpan = null;
100+
98101
// Set Collective2 signal export provider.
99102
// If using the Collective2 white-label API, you can specify it in the constructor with the optional parameter `useWhiteLabelApi`:
100103
// e.g. new Collective2SignalExport(_collective2ApiKey, _collective2SystemId, useWhiteLabelApi: true)
101104
// The API url can also be overridden by setting the Destination property:
102105
// e.g. new Collective2SignalExport(_collective2ApiKey, _collective2SystemId) { Destination = new Uri("your url") }
103-
SignalExport.AddSignalExportProviders(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId));
106+
SignalExport.AddSignalExportProvider(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId));
104107

105108
SetWarmUp(100);
106109
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3+
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using Newtonsoft.Json;
17+
using QuantConnect.Algorithm.Framework.Portfolio;
18+
using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
19+
using QuantConnect.Api;
20+
using QuantConnect.Data;
21+
using QuantConnect.Interfaces;
22+
using System;
23+
using System.Linq;
24+
using System.Net.Http;
25+
using System.Net.Http.Json;
26+
using System.Text;
27+
28+
namespace QuantConnect.Algorithm.CSharp
29+
{
30+
/// <summary>
31+
/// This algorithm sends a list of portfolio targets to custom endpoint
32+
/// </summary>
33+
/// <meta name="tag" content="using data" />
34+
/// <meta name="tag" content="using quantconnect" />
35+
/// <meta name="tag" content="securities and portfolio" />
36+
public class CustomSignalExportDemonstrationAlgorithm : QCAlgorithm
37+
{
38+
/// <summary>
39+
/// Initialize the date and add all equity symbols present
40+
/// </summary>
41+
public override void Initialize()
42+
{
43+
SetStartDate(2013, 10, 07);
44+
SetEndDate(2013, 10, 11);
45+
46+
/// Our custom signal export accepts all asset types
47+
AddEquity("SPY", Resolution.Second);
48+
AddCrypto("BTCUSD", Resolution.Second);
49+
AddForex("EURUSD", Resolution.Second);
50+
AddFutureContract(QuantConnect.Symbol.CreateFuture("ES", Market.CME, new DateTime(2023, 12, 15), null));
51+
AddOptionContract(QuantConnect.Symbol.CreateOption("SPY", Market.USA, OptionStyle.American, OptionRight.Call, 130, new DateTime(2023, 9, 1)));
52+
53+
// Set CustomSignalExport signal export provider.
54+
SignalExport.AddSignalExportProvider(new CustomSignalExport());
55+
}
56+
57+
/// <summary>
58+
/// Buy and hold EURUSD and SPY
59+
/// </summary>
60+
/// <param name="slice"></param>
61+
public override void OnData(Slice slice)
62+
{
63+
foreach (var ticker in new[] { "SPY", "EURUSD", "BTCUSD" })
64+
{
65+
if (!Portfolio[ticker].Invested && Securities[ticker].HasData)
66+
{
67+
SetHoldings(ticker, 0.5m);
68+
}
69+
}
70+
}
71+
}
72+
73+
internal class CustomSignalExport : ISignalExportTarget
74+
{
75+
private readonly Uri _requestUri = new ("http://localhost:5000/");
76+
private readonly HttpClient _httpClient = new();
77+
78+
public bool Send(SignalExportTargetParameters parameters)
79+
{
80+
object SimplePayload(PortfolioTarget target)
81+
{
82+
var newTarget = PortfolioTarget.Percent(parameters.Algorithm, target.Symbol, target.Quantity);
83+
return new { symbol = newTarget.Symbol.Value, quantity = newTarget.Quantity };
84+
};
85+
86+
var message = JsonConvert.SerializeObject(parameters.Targets.Select(SimplePayload));
87+
using var httpMessage = new StringContent(message, Encoding.UTF8, "application/json");
88+
using HttpResponseMessage response = _httpClient.PostAsync(_requestUri, httpMessage).Result;
89+
var result = response.Content.ReadFromJsonAsync<RestResponse>().Result;
90+
parameters.Algorithm.Log($"Send #{parameters.Targets.Count} targets. Success: {result.Success}");
91+
return result.Success;
92+
}
93+
94+
public void Dispose() => _httpClient.Dispose();
95+
}
96+
}
97+
98+
/*
99+
# To test the algorithm, you can create a simple Python Flask application (app.py) and run flask
100+
# $ flask --app app run
101+
102+
# app.py:
103+
from flask import Flask, request, jsonify
104+
from json import loads
105+
app = Flask(__name__)
106+
@app.post('/')
107+
def handle_positions():
108+
result = loads(request.data)
109+
print(result)
110+
return jsonify({'success': True,'message': f'{len(result)} positions received'})
111+
if __name__ == '__main__':
112+
app.run(debug=True)
113+
*/

Algorithm.CSharp/IndexSecurityCanBeTradableRegressionAlgorithm.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public override void Initialize()
4343

4444
_index = AddIndex("SPX").Symbol;
4545
_equity = AddEquity("SPY").Symbol;
46+
SignalExport.AutomaticExportTimeSpan = null;
4647
_signalExportManagerTest = new SignalExportManagerTest(this);
4748
Securities[_index].IsTradable = IsTradable;
4849
}

Algorithm.CSharp/NumeraiSignalExportDemonstrationAlgorithm.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@ public override void Initialize()
6464
var numeraiModelId = "";
6565

6666
var numeraiFilename = ""; // (Optional) Replace this value with your submission filename
67-
SignalExport.AddSignalExportProviders(new NumeraiSignalExport(numeraiPublicId, numeraiSecretId, numeraiModelId, numeraiFilename));
67+
68+
// Disable automatic exports as we manually set them
69+
SignalExport.AutomaticExportTimeSpan = null;
70+
71+
// Set Numerai signal export provider
72+
SignalExport.AddSignalExportProvider(new NumeraiSignalExport(numeraiPublicId, numeraiSecretId, numeraiModelId, numeraiFilename));
6873
}
6974

7075
public void SubmitSignals()

Algorithm.CSharp/RegressionTests/Collective2IndexOptionAlgorithm.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ public override void Initialize()
6363
_fast = EMA(underlying, 10, Resolution.Minute);
6464
_slow = EMA(underlying, 50, Resolution.Minute);
6565

66+
// Disable automatic exports as we manually set them
67+
SignalExport.AutomaticExportTimeSpan = null;
6668
// Set up the Collective2 Signal Export with the provided API key and system ID
67-
SignalExport.AddSignalExportProviders(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId));
69+
SignalExport.AddSignalExportProvider(new Collective2SignalExport(_collective2ApiKey, _collective2SystemId));
6870

6971
// Set warm-up period for the indicators
7072
SetWarmUp(50);

Algorithm.Python/Collective2PortfolioSignalExportDemonstrationAlgorithm.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ def initialize(self):
5454
# Collective2 System ID: This value is found beside the system's name (strategy's name) on the main system page
5555
self.collective2_system_id = 0
5656

57-
self.signal_export.add_signal_export_providers(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id))
57+
# Disable automatic exports as we manually set them
58+
self.signal_export.automatic_export_time_span = None
59+
60+
# Set Collective2 signal export provider
61+
self.signal_export.add_signal_export_provider(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id))
5862

5963
self.first_call = True
6064

Algorithm.Python/Collective2SignalExportDemonstrationAlgorithm.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,14 @@ def initialize(self):
6161
# Collective2 System ID: This value is found beside the system's name (strategy's name) on the main system page
6262
self.collective2_system_id = 0
6363

64+
# Disable automatic exports as we manually set them
65+
self.signal_export.automatic_export_time_span = None
66+
6467
# If using the Collective2 white-label API, you can specify it in the constructor with the optional parameter `use_white_label_api`:
6568
# e.g. Collective2SignalExport(self.collective2_apikey, self.collective2_system_id, use_white_label_api=True)
6669
# The API url can also be overridden by setting the Destination property:
6770
# e.g. Collective2SignalExport(self.collective2_apikey, self.collective2_system_id) { Destination = new Uri("your url") }
68-
self.signal_export.add_signal_export_providers(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id))
71+
self.signal_export.add_signal_export_provider(Collective2SignalExport(self.collective2_apikey, self.collective2_system_id))
6972

7073
self.first_call = True
7174

Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@ def initialize(self):
2828
self.set_end_date(2023, 5, 26)
2929
self.set_cash(1_000_000)
3030

31+
# Disable automatic exports as we manually set them
32+
self.signal_export.automatic_export_time_span = None
33+
3134
# Connect to CrunchDAO
3235
api_key = "" # Your CrunchDAO API key
3336
model = "" # The Id of your CrunchDAO model
3437
submission_name = "" # A name for the submission to distinguish it from your other submissions
3538
comment = "" # A comment for the submission
36-
self.signal_export.add_signal_export_providers(CrunchDAOSignalExport(api_key, model, submission_name, comment))
39+
self.signal_export.add_signal_export_provider(CrunchDAOSignalExport(api_key, model, submission_name, comment))
3740

3841
self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
39-
42+
4043
# Add a custom data universe to read the CrunchDAO skeleton
4144
self.add_universe(CrunchDaoSkeleton, "CrunchDaoSkeleton", Resolution.DAILY, self.select_symbols)
4245

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
2+
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
from AlgorithmImports import *
15+
16+
### <summary>
17+
### his algorithm sends a list of portfolio targets to custom endpoint
18+
### </summary>
19+
### <meta name="tag" content="using data" />
20+
### <meta name="tag" content="using quantconnect" />
21+
### <meta name="tag" content="securities and portfolio" />
22+
class CustomSignalExportDemonstrationAlgorithm(QCAlgorithm):
23+
24+
def initialize(self) -> None:
25+
''' Initialize the date and add all equity symbols present in list _symbols '''
26+
27+
self.set_start_date(2013, 10, 7) #Set Start Date
28+
self.set_end_date(2013, 10, 11) #Set End Date
29+
self.set_cash(100000) #Set Strategy Cash
30+
31+
# Our custom signal export accepts all asset types
32+
self.add_equity("SPY", Resolution.SECOND)
33+
self.add_crypto("BTCUSD", Resolution.SECOND)
34+
self.add_forex("EURUSD", Resolution.SECOND)
35+
self.add_future_contract(Symbol.create_future("ES", Market.CME, datetime(2023, 12, 15), None))
36+
self.add_option_contract(Symbol.create_option("SPY", Market.USA, OptionStyle.American, OptionRight.Call, 130, datetime(2023, 9, 1)))
37+
38+
# Set CustomSignalExport signal export provider.
39+
self.signal_export.add_signal_export_provider(CustomSignalExport())
40+
41+
def on_data(self, data: Slice) -> None:
42+
'''Buy and hold EURUSD and SPY'''
43+
for ticker in [ "SPY", "EURUSD", "BTCUSD" ]:
44+
if not self.portfolio[ticker].invested and self.securities[ticker].has_data:
45+
self.set_holdings(ticker, 0.5)
46+
47+
from requests import post
48+
class CustomSignalExport:
49+
def send(self, parameters: SignalExportTargetParameters) -> bool:
50+
targets = [PortfolioTarget.percent(parameters.algorithm, x.symbol, x.quantity)
51+
for x in parameters.targets] ;
52+
data = [ {'symbol' : x.symbol.value, 'quantity': x.quantity} for x in targets ]
53+
response = post("http://localhost:5000/", json = data)
54+
result = response.json()
55+
success = result.get('success', False)
56+
parameters.algorithm.log(f"Send #{len(parameters.targets)} targets. Success: {success}");
57+
return success
58+
59+
def dispose(self):
60+
pass
61+
62+
'''
63+
# To test the algorithm, you can create a simple Python Flask application (app.py) and run flask
64+
# $ flask --app app run
65+
66+
# app.py:
67+
from flask import Flask, request, jsonify
68+
from json import loads
69+
app = Flask(__name__)
70+
@app.post('/')
71+
def handle_positions():
72+
result = loads(request.data)
73+
print(result)
74+
return jsonify({'success': True,'message': f'{len(result)} positions received'})
75+
if __name__ == '__main__':
76+
app.run(debug=True)
77+
'''

0 commit comments

Comments
 (0)