Skip to content

Commit 9a87389

Browse files
committed
Adds Custom Signal Export Example
1 parent f963e43 commit 9a87389

4 files changed

Lines changed: 363 additions & 0 deletions

File tree

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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 System;
17+
using QuantConnect.Algorithm.Framework.Portfolio;
18+
using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
19+
using QuantConnect.Data;
20+
using QuantConnect.Indicators;
21+
using QuantConnect.Interfaces;
22+
using System.Collections.Generic;
23+
using Newtonsoft.Json;
24+
using System.Net.Http;
25+
using System.Text;
26+
using System.Net.Http.Json;
27+
using QuantConnect.Api;
28+
using QuantConnect.Util;
29+
30+
namespace QuantConnect.Algorithm.CSharp
31+
{
32+
/// <summary>
33+
/// This algorithm sends a list of portfolio targets to custom endpoint every time the ema indicators crosses between themselves
34+
/// </summary>
35+
/// <meta name="tag" content="using data" />
36+
/// <meta name="tag" content="using quantconnect" />
37+
/// <meta name="tag" content="securities and portfolio" />
38+
public class CustomSignalExportDemonstrationAlgorithm : QCAlgorithm
39+
{
40+
private ExponentialMovingAverage _fast;
41+
private ExponentialMovingAverage _slow;
42+
private bool _emaFastWasAbove;
43+
private bool _emaFastIsNotSet;
44+
private bool _firstCall = true;
45+
46+
private PortfolioTarget[] _targets = new PortfolioTarget[4];
47+
48+
/// <summary>
49+
/// Our custom signal export accepts all asset types
50+
/// </summary>
51+
private List<Symbol> _symbols =
52+
[
53+
QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA, null, null),
54+
QuantConnect.Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda, null, null),
55+
QuantConnect.Symbol.CreateFuture("ES", Market.CME, new DateTime(2023, 12, 15), null),
56+
QuantConnect.Symbol.CreateOption("GOOG", Market.USA, OptionStyle.American, OptionRight.Call, 130, new DateTime(2023, 9, 1)),
57+
];
58+
59+
/// <summary>
60+
/// Initialize the date and add all equity symbols present in _symbols list.
61+
/// Besides, make a new PortfolioTarget for each symbol in _symbols, assign it
62+
/// an initial quantity and save it in _targets array
63+
/// </summary>
64+
public override void Initialize()
65+
{
66+
SetStartDate(2013, 10, 07);
67+
SetEndDate(2013, 10, 11);
68+
SetCash(100 * 1000);
69+
70+
var index = 0;
71+
foreach (var item in _symbols)
72+
{
73+
var symbol = AddSecurity(item).Symbol;
74+
if (symbol.SecurityType == SecurityType.Equity
75+
|| symbol.SecurityType == SecurityType.Forex)
76+
{
77+
_targets[index] = new PortfolioTarget(symbol, (decimal)0.05);
78+
}
79+
else
80+
{
81+
_targets[index] = new PortfolioTarget(symbol, 1);
82+
}
83+
index++;
84+
}
85+
86+
_fast = EMA("SPY", 10);
87+
_slow = EMA("SPY", 100);
88+
89+
// Initialize this flag, to check when the ema indicators crosses between themselves
90+
_emaFastIsNotSet = true;
91+
92+
// Set CustomSignalExport signal export provider.
93+
SignalExport.AddSignalExportProviders(new CustomSignalExport());
94+
}
95+
96+
/// <summary>
97+
/// Reduce the quantity of holdings for SPY or increase it, depending the case,
98+
/// when the EMA's indicators crosses between themselves, then send a signal to API
99+
/// </summary>
100+
/// <param name="slice"></param>
101+
public override void OnData(Slice slice)
102+
{
103+
if (IsWarmingUp) return;
104+
105+
// Place an order as soon as possible to send a signal.
106+
if (_firstCall)
107+
{
108+
SetHoldings("SPY", 0.1);
109+
_targets[0] = new PortfolioTarget(Portfolio["SPY"].Symbol, (decimal)0.1);
110+
SignalExport.SetTargetPortfolio(_targets);
111+
_firstCall = false;
112+
}
113+
114+
// Set the value of flag _emaFastWasAbove, to know when the ema indicators crosses between themselves
115+
if (_emaFastIsNotSet)
116+
{
117+
if (_fast > _slow * 1.001m)
118+
{
119+
_emaFastWasAbove = true;
120+
}
121+
else
122+
{
123+
_emaFastWasAbove = false;
124+
}
125+
_emaFastIsNotSet = false;
126+
}
127+
128+
// Check whether ema fast and ema slow crosses. If they do, set holdings to SPY
129+
// or reduce its holdings, change its value in _targets array and send signals
130+
// to the API from _targets array
131+
if ((_fast > _slow * 1.001m) && (!_emaFastWasAbove))
132+
{
133+
SetHoldings("SPY", 0.1);
134+
_targets[0] = new PortfolioTarget(Portfolio["SPY"].Symbol, (decimal)0.1);
135+
SignalExport.SetTargetPortfolio(_targets);
136+
}
137+
else if ((_fast < _slow * 0.999m) && (_emaFastWasAbove))
138+
{
139+
SetHoldings("SPY", 0.01);
140+
_targets[0] = new PortfolioTarget(Portfolio["SPY"].Symbol, (decimal)0.01);
141+
SignalExport.SetTargetPortfolio(_targets);
142+
}
143+
}
144+
}
145+
146+
internal class CustomSignalExport : ISignalExportTarget
147+
{
148+
private readonly Uri _requestUri = new ("http://localhost:5000/");
149+
private Lazy<HttpClient> _lazyClient = new();
150+
protected HttpClient HttpClient => _lazyClient.Value;
151+
152+
public bool Send(SignalExportTargetParameters parameters)
153+
{
154+
var message = JsonConvert.SerializeObject(parameters.Targets);
155+
using var httpMessage = new StringContent(message, Encoding.UTF8, "application/json");
156+
using HttpResponseMessage response = HttpClient.PostAsync(_requestUri, httpMessage).Result;
157+
var result = response.Content.ReadFromJsonAsync<RestResponse>().Result;
158+
return result.Success;
159+
}
160+
161+
public void Dispose()
162+
{
163+
if (_lazyClient.IsValueCreated)
164+
{
165+
_lazyClient.Value.DisposeSafely();
166+
}
167+
}
168+
}
169+
}
170+
171+
/*
172+
# $ flask --app app run
173+
174+
# app.py:
175+
from flask import Flask, request, jsonify
176+
from json import loads
177+
app = Flask(__name__)
178+
@app.post('/')
179+
def handle_positions():
180+
result = loads(request.data)
181+
return jsonify({'success': True,'message': f'{len(result)} positions received'})
182+
if __name__ == '__main__':
183+
app.run(debug=True)
184+
*/
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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+
### This algorithm sends a list of portfolio targets to custom endpoint every time the ema indicators crosses between themselves
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):
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("GOOG")
33+
self._symbols = [
34+
Symbol.create("SPY", SecurityType.EQUITY, Market.USA, None, None),
35+
Symbol.create("EURUSD", SecurityType.FOREX, Market.OANDA, None, None),
36+
Symbol.create_future("ES", Market.CME, datetime(2023, 12, 15), None),
37+
Symbol.create_option("GOOG", Market.USA, OptionStyle.AMERICAN, OptionRight.CALL, 130, datetime(2023, 9, 1))
38+
]
39+
self.targets = []
40+
41+
# Create a new PortfolioTarget for each symbol, assign it an initial amount of 0.05 and save it in self.targets list
42+
for item in self._symbols:
43+
symbol = self.add_security(item).symbol
44+
if symbol.security_type == SecurityType.EQUITY or symbol.security_type == SecurityType.FOREX:
45+
self.targets.append(PortfolioTarget(symbol, 0.05))
46+
else:
47+
self.targets.append(PortfolioTarget(symbol, 1))
48+
49+
self.fast = self.ema("SPY", 10)
50+
self.slow = self.ema("SPY", 100)
51+
52+
# Initialize these flags, to check when the ema indicators crosses between themselves
53+
self.ema_fast_is_not_set = True
54+
self.ema_fast_was_above = False
55+
self.first_call = True
56+
57+
# Set CustomSignalExport signal export provider.
58+
self.signal_export.add_signal_export_providers(CustomSignalExport())
59+
60+
def on_data(self, data):
61+
''' Reduce the quantity of holdings for one security and increase the holdings to the another
62+
one when the EMA's indicators crosses between themselves, then send a signal to API '''
63+
if self.is_warming_up: return
64+
65+
# Place an order as soon as possible to send a signal.
66+
if self.first_call:
67+
self.set_holdings("SPY", 0.1)
68+
self.targets[0] = PortfolioTarget(self.portfolio["SPY"].symbol, 0.1)
69+
self.signal_export.set_target_portfolio(self.targets)
70+
self.first_call = False
71+
else:
72+
self.quit()
73+
74+
fast = self.fast.current.value
75+
slow = self.slow.current.value
76+
77+
# Set the value of flag _ema_fast_was_above, to know when the ema indicators crosses between themselves
78+
if self.ema_fast_is_not_set == True:
79+
if fast > slow *1.001:
80+
self.ema_fast_was_above = True
81+
else:
82+
self.ema_fast_was_above = False
83+
self.ema_fast_is_not_set = False
84+
85+
# Check whether ema fast and ema slow crosses. If they do, set holdings to SPY
86+
# or reduce its holdings, change its value in self.targets list and send signals
87+
# to API from self.targets
88+
if fast > slow * 1.001 and (not self.ema_fast_was_above):
89+
self.set_holdings("SPY", 0.1)
90+
self.targets[0] = PortfolioTarget(self.portfolio["SPY"].symbol, 0.1)
91+
self.signal_export.set_target_portfolio(self.targets)
92+
elif fast < slow * 0.999 and (self.ema_fast_was_above):
93+
self.set_holdings("SPY", 0.01)
94+
self.targets[0] = PortfolioTarget(self.portfolio["SPY"].symbol, 0.01)
95+
self.signal_export.set_target_portfolio(self.targets)
96+
97+
from requests import post
98+
class CustomSignalExport:
99+
def send(self, parameters: SignalExportTargetParameters) -> bool:
100+
data = { x.symbol.value: x.quantity for x in parameters.targets }
101+
response = post("http://localhost:5000/", json = data)
102+
result = response.json()
103+
return result.get('success', False)
104+
105+
def dispose(self):
106+
pass
107+
108+
'''
109+
# $ flask --app app run
110+
111+
# app.py:
112+
from flask import Flask, request, jsonify
113+
from json import loads
114+
app = Flask(__name__)
115+
@app.post('/')
116+
def handle_positions():
117+
result = loads(request.data)
118+
return jsonify({'success': True,'message': f'{len(result)} positions received'})
119+
if __name__ == '__main__':
120+
app.run(debug=True)
121+
'''

Common/Algorithm/Framework/Portfolio/SignalExports/SignalExportManager.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
* limitations under the License.
1414
*/
1515

16+
using Python.Runtime;
1617
using QuantConnect.Interfaces;
18+
using QuantConnect.Python;
1719
using QuantConnect.Securities;
1820
using System.Collections.Generic;
1921
using System;
@@ -64,6 +66,15 @@ public void AddSignalExportProviders(params ISignalExportTarget[] signalExports)
6466
_signalExports.AddRange(signalExports);
6567
}
6668

69+
/// <summary>
70+
/// Adds one new signal exports providers
71+
/// </summary>
72+
/// <param name="signalExport">Signal export provider</param>
73+
public void AddSignalExportProviders(PyObject signalExport)
74+
{
75+
AddSignalExportProviders(new SignalExportTargetPythonWrapper(signalExport));
76+
}
77+
6778
/// <summary>
6879
/// Sets the portfolio targets from the algorihtm's Portfolio and sends them with the
6980
/// algorithm being ran to the signal exports providers already set
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 Python.Runtime;
17+
using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
18+
using QuantConnect.Interfaces;
19+
20+
namespace QuantConnect.Python
21+
{
22+
/// <summary>
23+
/// Provides an implementation of <see cref="ISignalExportTarget"/> that wraps a <see cref="PyObject"/> object
24+
/// </summary>
25+
/// <remarks>
26+
/// Constructor for initialising the <see cref="SignalExportTargetPythonWrapper"/> class with wrapped <see cref="PyObject"/> object
27+
/// </remarks>
28+
/// <param name="model">Python benchmark model</param>
29+
public class SignalExportTargetPythonWrapper(PyObject model) : BasePythonWrapper<ISignalExportTarget>(model), ISignalExportTarget
30+
{
31+
/// <summary>
32+
/// Interface to send positions holdings to different 3rd party API's
33+
/// </summary>
34+
public bool Send(SignalExportTargetParameters parameters)
35+
{
36+
return InvokeMethod<bool>(nameof(Send), parameters);
37+
}
38+
39+
/// <summary>
40+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
41+
/// </summary>
42+
public void Dispose()
43+
{
44+
InvokeMethod(nameof(Dispose));
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)