Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e98b31fc4c | ||
|
|
d2eae2f652 | ||
|
|
0f0a2bc9a8 | ||
|
|
8ad81dca71 | ||
|
|
f965f34a3f | ||
|
|
06a7d54c38 | ||
|
|
7174fcb9d7 | ||
|
|
29e9d678f2 | ||
|
|
098ac7d0a9 | ||
|
|
6cb4411f6e | ||
|
|
46ef9a9dbb | ||
|
|
d8d8134437 | ||
|
|
9a30c9bd5f | ||
|
|
f76a0efb0e | ||
|
|
7238fcd0f3 | ||
|
|
91e8393aac | ||
|
|
5771635265 | ||
|
|
914486fdb6 | ||
|
|
61fda8b62c | ||
|
|
124ac3b98e | ||
|
|
9dca43bccb | ||
|
|
6efeee07dc | ||
|
|
c452dd3726 | ||
|
|
c602fd0a3f | ||
|
|
104071cda5 | ||
|
|
a5d9526d65 | ||
|
|
8e525c63fc | ||
|
|
d0e9134cc9 | ||
|
|
883d354a98 | ||
|
|
76a53eb096 | ||
|
|
c02ee1b0d8 | ||
|
|
9167882ab2 | ||
|
|
854b987cd0 | ||
|
|
a26414d273 | ||
|
|
84264ca7ef | ||
|
|
b2ed398687 | ||
|
|
e8c316cbcf | ||
|
|
724e52c0b3 | ||
|
|
4252c79e45 | ||
|
|
cbb40dfa43 | ||
|
|
8792fa2600 | ||
|
|
20e9fd7899 | ||
|
|
e05a6bffd0 | ||
|
|
c2f0fdc47a | ||
|
|
b1b8da1e17 | ||
|
|
01a0454c57 | ||
|
|
90e2c48404 | ||
|
|
888c443264 | ||
|
|
03efc1b735 | ||
|
|
6ef2ead929 | ||
|
|
bfd319c91e | ||
|
|
5f61456df8 | ||
|
|
a46a551c03 | ||
|
|
cf9b547e2e | ||
|
|
faa4e91e04 | ||
|
|
cc83f19528 | ||
|
|
54af12b06a | ||
|
|
1d1c8f5f82 | ||
|
|
3966c0e91f | ||
|
|
28160e1301 | ||
|
|
f4679785a5 | ||
|
|
79b9009452 | ||
|
|
e5b5f80d9d | ||
|
|
027fde8f09 | ||
|
|
c7ccd60bf2 | ||
|
|
b30bb3fcf5 | ||
|
|
40a87eb056 | ||
|
|
035b29fdf5 | ||
|
|
f2fc1aae9e | ||
|
|
ff5fc5db5d | ||
|
|
40c3062348 | ||
|
|
371f2cd469 | ||
|
|
090ceb131e | ||
|
|
6885fd130b | ||
|
|
a450cea8d0 | ||
|
|
934128cfa0 | ||
|
|
c7a74306fb | ||
|
|
cfacc755fa |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -271,3 +271,6 @@ QuantConnect.Lean.sln.DotSettings*
|
||||
|
||||
#User notebook files
|
||||
Research/Notebooks
|
||||
|
||||
#Docker result files
|
||||
Results/
|
||||
14
.idea/readme.md
generated
14
.idea/readme.md
generated
@@ -91,14 +91,14 @@ From a terminal; Pycharm has a built in terminal on the bottom taskbar labeled *
|
||||
|
||||
2. Using the **run_docker.cfg** to store args for repeated use; any blank entries will resort to default values! example: **_./run_docker.bat run_docker.cfg_**
|
||||
|
||||
image=quantconnect/lean:latest
|
||||
config_file=
|
||||
data_dir=
|
||||
results_dir=
|
||||
debugging=
|
||||
python_dir=
|
||||
IMAGE=quantconnect/lean:latest
|
||||
CONFIG_FILE=
|
||||
DATA_DIR=
|
||||
RESULTS_DIR=
|
||||
DEBUGGING=
|
||||
PYTHON_DIR=
|
||||
|
||||
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat debugging=y_**
|
||||
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat DEBUGGING=y_**
|
||||
* Accepted args for inline include all listed in the file in #2; must follow the **key=value** format
|
||||
|
||||
<br />
|
||||
|
||||
@@ -23,4 +23,4 @@ script:
|
||||
- msbuild /p:Configuration=Release /p:VbcToolExe=vbnc.exe QuantConnect.Lean.sln
|
||||
- mono ./testrunner/NUnit.ConsoleRunner.3.11.1/tools/nunit3-console.exe ./Tests/bin/Release/QuantConnect.Tests.dll --where "cat != TravisExclude" --labels=Off
|
||||
- chmod +x ci_build_stubs.sh
|
||||
- sudo -E ./ci_build_stubs.sh -ipy -g -p
|
||||
- sudo -E ./ci_build_stubs.sh -d -t -g -p
|
||||
|
||||
@@ -106,14 +106,14 @@ From a terminal launch the run_docker.bat/.sh script; there are a few choices on
|
||||
|
||||
2. Using the **run_docker.cfg** to store args for repeated use; any blank entries will resort to default values! example: **_./run_docker.bat run_docker.cfg_**
|
||||
|
||||
image=quantconnect/lean:latest
|
||||
config_file=
|
||||
data_dir=
|
||||
results_dir=
|
||||
debugging=
|
||||
python_dir=
|
||||
IMAGE=quantconnect/lean:latest
|
||||
CONFIG_FILE=
|
||||
DATA_DIR=
|
||||
RESULTS_DIR=
|
||||
DEBUGGING=
|
||||
PYTHON_DIR=
|
||||
|
||||
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat debugging=y_**
|
||||
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat DEBUGGING=y_**
|
||||
* Accepted args for inline include all listed in the file in #2
|
||||
|
||||
<br />
|
||||
|
||||
34
.vscode/readme.md
vendored
34
.vscode/readme.md
vendored
@@ -101,14 +101,14 @@ This section will cover how to actually launch Lean in the container with your d
|
||||
|
||||
<h3>Option 1 (Recommended)</h3>
|
||||
|
||||
In VS Code click on the debug/run icon on the left toolbar, at the top you should see a drop down menu with launch options, be sure to select **Debug in Container**. This option will kick off a launch script that will start the docker. With this specific launch option the parameters are already configured in VS Codes **tasks.json** under the **run-docker** task args. These set arguements are:
|
||||
In VS Code click on the debug/run icon on the left toolbar, at the top you should see a drop down menu with launch options, be sure to select **Debug in Container**. This option will kick off a launch script that will start the docker. With this specific launch option the parameters are already configured in VS Codes **tasks.json** under the **run-docker** task args. These set arguments are:
|
||||
|
||||
"image=quantconnect/lean:latest",
|
||||
"config_file=${workspaceFolder}/Launcher/config.json",
|
||||
"data_dir=${workspaceFolder}/Data",
|
||||
"results_dir=${workspaceFolder}/",
|
||||
"debugging=Y",
|
||||
"python_location=${workspaceFolder}/Algorithm.Python"
|
||||
"IMAGE=quantconnect/lean:latest",
|
||||
"CONFIG_FILE=${workspaceFolder}/Launcher/config.json",
|
||||
"DATA_DIR=${workspaceFolder}/Data",
|
||||
"RESULTS_DIR=${workspaceFolder}/Results",
|
||||
"DEBUGGING=Y",
|
||||
"PYHTON_DIR=${workspaceFolder}/Algorithm.Python"
|
||||
|
||||
As defaults these are all great! Feel free to change them as needed for your setup.
|
||||
|
||||
@@ -120,21 +120,21 @@ From a terminal launch the run_docker.bat/.sh script; there are a few choices on
|
||||
1. Launch with no parameters and answer the questions regarding configuration (Press enter for defaults)
|
||||
|
||||
* Enter docker image [default: quantconnect/lean:latest]:
|
||||
* Enter absolute path to Lean config file [default: _~currentDir_\Launcher\config.json]:
|
||||
* Enter absolute path to Data folder [default: ~_currentDir_\Data\]:
|
||||
* Enter absolute path to store results [default: ~_currentDir_\]:
|
||||
* Enter absolute path to Lean config file [default: .\Launcher\config.json]:
|
||||
* Enter absolute path to Data folder [default: .\Data\]:
|
||||
* Enter absolute path to store results [default: .\Results]:
|
||||
* Would you like to debug C#? (Requires mono debugger attachment) [default: N]:
|
||||
|
||||
2. Using the **run_docker.cfg** to store args for repeated use; any blank entries will resort to default values! example: **_./run_docker.bat run_docker.cfg_**
|
||||
|
||||
image=quantconnect/lean:latest
|
||||
config_file=
|
||||
data_dir=
|
||||
results_dir=
|
||||
debugging=
|
||||
python_dir=
|
||||
IMAGE=quantconnect/lean:latest
|
||||
CONFIG_FILE=
|
||||
DATA_DIR=
|
||||
RESULTS_DIR=
|
||||
DEBUGGING=
|
||||
PYTHON_DIR=
|
||||
|
||||
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat debugging=y_**
|
||||
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat DEBUGGING=y_**
|
||||
* Accepted args for inline include all listed in the file in #2
|
||||
|
||||
<br />
|
||||
|
||||
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
@@ -51,13 +51,13 @@
|
||||
"command": "${workspaceFolder}/run_docker.sh"
|
||||
},
|
||||
"args": [
|
||||
"image=quantconnect/lean:latest",
|
||||
"config_file=${workspaceFolder}/Launcher/config.json",
|
||||
"data_dir=${workspaceFolder}/Data",
|
||||
"results_dir=${workspaceFolder}/",
|
||||
"debugging=Y",
|
||||
"python_dir=${workspaceFolder}/Algorithm.Python",
|
||||
"exit=Y"
|
||||
"IMAGE=quantconnect/lean:latest",
|
||||
"CONFIG_FILE=${workspaceFolder}/Launcher/config.json",
|
||||
"DATA_DIR=${workspaceFolder}/Data",
|
||||
"RESULTS_DIR=${workspaceFolder}/Results",
|
||||
"DEBUGGING=Y",
|
||||
"PYTHON_DIR=${workspaceFolder}/Algorithm.Python",
|
||||
"EXIT=Y"
|
||||
],
|
||||
"problemMatcher": [
|
||||
{
|
||||
|
||||
164
Algorithm.CSharp/AddOptionContractExpiresRegressionAlgorithm.cs
Normal file
164
Algorithm.CSharp/AddOptionContractExpiresRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// We add an option contract using <see cref="QCAlgorithm.AddOptionContract"/> and place a trade and wait till it expires
|
||||
/// later will liquidate the resulting equity position and assert both option and underlying get removed
|
||||
/// </summary>
|
||||
public class AddOptionContractExpiresRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private DateTime _expiration = new DateTime(2014, 06, 21);
|
||||
private Symbol _option;
|
||||
private Symbol _twx;
|
||||
private bool _traded;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2014, 06, 05);
|
||||
SetEndDate(2014, 06, 30);
|
||||
|
||||
_twx = QuantConnect.Symbol.Create("TWX", SecurityType.Equity, Market.USA);
|
||||
|
||||
AddUniverse("my-daily-universe-name", time => new List<string> { "AAPL" });
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (_option == null)
|
||||
{
|
||||
var option = OptionChainProvider.GetOptionContractList(_twx, Time)
|
||||
.OrderBy(symbol => symbol.ID.Symbol)
|
||||
.FirstOrDefault(optionContract => optionContract.ID.Date == _expiration
|
||||
&& optionContract.ID.OptionRight == OptionRight.Call
|
||||
&& optionContract.ID.OptionStyle == OptionStyle.American);
|
||||
if (option != null)
|
||||
{
|
||||
_option = AddOptionContract(option).Symbol;
|
||||
}
|
||||
}
|
||||
|
||||
if (_option != null && Securities[_option].Price != 0 && !_traded)
|
||||
{
|
||||
_traded = true;
|
||||
Buy(_option, 1);
|
||||
|
||||
foreach (var symbol in new [] { _option, _option.Underlying })
|
||||
{
|
||||
var config = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol).ToList();
|
||||
|
||||
if (!config.Any())
|
||||
{
|
||||
throw new Exception($"Was expecting configurations for {symbol}");
|
||||
}
|
||||
if (config.Any(dataConfig => dataConfig.DataNormalizationMode != DataNormalizationMode.Raw))
|
||||
{
|
||||
throw new Exception($"Was expecting DataNormalizationMode.Raw configurations for {symbol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Time.Date > _expiration)
|
||||
{
|
||||
if (SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_option).Any())
|
||||
{
|
||||
throw new Exception($"Unexpected configurations for {_option} after it has been delisted");
|
||||
}
|
||||
|
||||
if (Securities[_twx].Invested)
|
||||
{
|
||||
if (!SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_twx).Any())
|
||||
{
|
||||
throw new Exception($"Was expecting configurations for {_twx}");
|
||||
}
|
||||
|
||||
// first we liquidate the option exercised position
|
||||
Liquidate(_twx);
|
||||
}
|
||||
}
|
||||
else if (Time.Date > _expiration && !Securities[_twx].Invested)
|
||||
{
|
||||
if (SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_twx).Any())
|
||||
{
|
||||
throw new Exception($"Unexpected configurations for {_twx} after it has been liquidated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "3"},
|
||||
{"Average Win", "2.73%"},
|
||||
{"Average Loss", "-2.98%"},
|
||||
{"Compounding Annual Return", "-4.619%"},
|
||||
{"Drawdown", "0.300%"},
|
||||
{"Expectancy", "-0.042"},
|
||||
{"Net Profit", "-0.332%"},
|
||||
{"Sharpe Ratio", "-3.7"},
|
||||
{"Probabilistic Sharpe Ratio", "0.563%"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "0.92"},
|
||||
{"Alpha", "-0.023"},
|
||||
{"Beta", "0.005"},
|
||||
{"Annual Standard Deviation", "0.006"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "-3.424"},
|
||||
{"Tracking Error", "0.057"},
|
||||
{"Treynor Ratio", "-4.775"},
|
||||
{"Total Fees", "$2.00"},
|
||||
{"Fitness Score", "0"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-43.418"},
|
||||
{"Return Over Maximum Drawdown", "-14.274"},
|
||||
{"Portfolio Turnover", "0.007"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1185639451"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// We add an option contract using <see cref="QCAlgorithm.AddOptionContract"/> and place a trade, the underlying
|
||||
/// gets deselected from the universe selection but should still be present since we manually added the option contract.
|
||||
/// Later we call <see cref="QCAlgorithm.RemoveOptionContract"/> and expect both option and underlying to be removed.
|
||||
/// </summary>
|
||||
public class AddOptionContractFromUniverseRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private DateTime _expiration = new DateTime(2014, 06, 21);
|
||||
private SecurityChanges _securityChanges = SecurityChanges.None;
|
||||
private Symbol _option;
|
||||
private Symbol _aapl;
|
||||
private Symbol _twx;
|
||||
private bool _traded;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_twx = QuantConnect.Symbol.Create("TWX", SecurityType.Equity, Market.USA);
|
||||
_aapl = QuantConnect.Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
|
||||
UniverseSettings.Resolution = Resolution.Minute;
|
||||
UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
|
||||
|
||||
SetStartDate(2014, 06, 05);
|
||||
SetEndDate(2014, 06, 09);
|
||||
|
||||
AddUniverse(enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl },
|
||||
enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl });
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (_option != null && Securities[_option].Price != 0 && !_traded)
|
||||
{
|
||||
_traded = true;
|
||||
Buy(_option, 1);
|
||||
}
|
||||
|
||||
if (Time.Date > new DateTime(2014, 6, 5))
|
||||
{
|
||||
if (Time < new DateTime(2014, 6, 6, 14, 0, 0))
|
||||
{
|
||||
var configs = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(_twx);
|
||||
// assert underlying still there after the universe selection removed it, still used by the manually added option contract
|
||||
if (!configs.Any())
|
||||
{
|
||||
throw new Exception($"Was expecting configurations for {_twx}" +
|
||||
$" even after it has been deselected from coarse universe because we still have the option contract.");
|
||||
}
|
||||
}
|
||||
else if (Time == new DateTime(2014, 6, 6, 14, 0, 0))
|
||||
{
|
||||
// liquidate & remove the option
|
||||
RemoveOptionContract(_option);
|
||||
}
|
||||
// assert underlying was finally removed
|
||||
else if(Time > new DateTime(2014, 6, 6, 14, 0, 0))
|
||||
{
|
||||
foreach (var symbol in new[] { _option, _option.Underlying })
|
||||
{
|
||||
var configs = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol);
|
||||
if (configs.Any())
|
||||
{
|
||||
throw new Exception($"Unexpected configuration for {symbol} after it has been deselected from coarse universe and option contract is removed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(SecurityChanges changes)
|
||||
{
|
||||
if (_securityChanges.RemovedSecurities.Intersect(changes.RemovedSecurities).Any())
|
||||
{
|
||||
throw new Exception($"SecurityChanges.RemovedSecurities intersect {changes.RemovedSecurities}. We expect no duplicate!");
|
||||
}
|
||||
if (_securityChanges.AddedSecurities.Intersect(changes.AddedSecurities).Any())
|
||||
{
|
||||
throw new Exception($"SecurityChanges.AddedSecurities intersect {changes.RemovedSecurities}. We expect no duplicate!");
|
||||
}
|
||||
// keep track of all removed and added securities
|
||||
_securityChanges += changes;
|
||||
|
||||
if (changes.AddedSecurities.Any(security => security.Symbol.SecurityType == SecurityType.Option))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var addedSecurity in changes.AddedSecurities)
|
||||
{
|
||||
var option = OptionChainProvider.GetOptionContractList(addedSecurity.Symbol, Time)
|
||||
.OrderBy(symbol => symbol.ID.Symbol)
|
||||
.First(optionContract => optionContract.ID.Date == _expiration
|
||||
&& optionContract.ID.OptionRight == OptionRight.Call
|
||||
&& optionContract.ID.OptionStyle == OptionStyle.American);
|
||||
AddOptionContract(option);
|
||||
|
||||
foreach (var symbol in new[] { option, option.Underlying })
|
||||
{
|
||||
var config = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(symbol).ToList();
|
||||
|
||||
if (!config.Any())
|
||||
{
|
||||
throw new Exception($"Was expecting configurations for {symbol}");
|
||||
}
|
||||
if (config.Any(dataConfig => dataConfig.DataNormalizationMode != DataNormalizationMode.Raw))
|
||||
{
|
||||
throw new Exception($"Was expecting DataNormalizationMode.Raw configurations for {symbol}");
|
||||
}
|
||||
}
|
||||
|
||||
// just keep the first we got
|
||||
if (_option == null)
|
||||
{
|
||||
_option = option;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (SubscriptionManager.Subscriptions.Any(dataConfig => dataConfig.Symbol == _twx || dataConfig.Symbol.Underlying == _twx))
|
||||
{
|
||||
throw new Exception($"Was NOT expecting any configurations for {_twx} or it's options, since we removed the contract");
|
||||
}
|
||||
|
||||
if (SubscriptionManager.Subscriptions.All(dataConfig => dataConfig.Symbol != _aapl))
|
||||
{
|
||||
throw new Exception($"Was expecting configurations for {_aapl}");
|
||||
}
|
||||
if (SubscriptionManager.Subscriptions.All(dataConfig => dataConfig.Symbol.Underlying != _aapl))
|
||||
{
|
||||
throw new Exception($"Was expecting options configurations for {_aapl}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "-0.23%"},
|
||||
{"Compounding Annual Return", "-15.596%"},
|
||||
{"Drawdown", "0.200%"},
|
||||
{"Expectancy", "-1"},
|
||||
{"Net Profit", "-0.232%"},
|
||||
{"Sharpe Ratio", "-7.739"},
|
||||
{"Probabilistic Sharpe Ratio", "1.216%"},
|
||||
{"Loss Rate", "100%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0.027"},
|
||||
{"Beta", "-0.174"},
|
||||
{"Annual Standard Deviation", "0.006"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "-11.586"},
|
||||
{"Tracking Error", "0.042"},
|
||||
{"Treynor Ratio", "0.286"},
|
||||
{"Total Fees", "$2.00"},
|
||||
{"Fitness Score", "0"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-19.883"},
|
||||
{"Return Over Maximum Drawdown", "-67.224"},
|
||||
{"Portfolio Turnover", "0.014"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "721476625"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Custom.Quiver;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.AltData
|
||||
{
|
||||
/// <summary>
|
||||
/// Quiver Quantitative is a provider of alternative data.
|
||||
/// This algorithm shows how to consume the <see cref="QuiverWallStreetBets"/>
|
||||
/// </summary>
|
||||
public class QuiverWallStreetBetsDataAlgorithm : QCAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2019, 1, 1);
|
||||
SetEndDate(2020, 6, 1);
|
||||
SetCash(100000);
|
||||
|
||||
var aapl = AddEquity("AAPL", Resolution.Daily).Symbol;
|
||||
var quiverWSBSymbol = AddData<QuiverWallStreetBets>(aapl).Symbol;
|
||||
var history = History<QuiverWallStreetBets>(quiverWSBSymbol, 60, Resolution.Daily);
|
||||
|
||||
Debug($"We got {history.Count()} items from our history request");
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
var points = data.Get<QuiverWallStreetBets>();
|
||||
foreach (var point in points.Values)
|
||||
{
|
||||
// Go long in the stock if it was mentioned more than 5 times in the WallStreetBets daily discussion
|
||||
if (point.Mentions > 5)
|
||||
{
|
||||
SetHoldings(point.Symbol.Underlying, 1);
|
||||
}
|
||||
// Go short in the stock if it was mentioned less than 5 times in the WallStreetBets daily discussion
|
||||
if (point.Mentions < 5)
|
||||
{
|
||||
SetHoldings(point.Symbol.Underlying, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// </summary>
|
||||
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
{
|
||||
if (!_equityBought && data.ContainsKey(_spy)) {
|
||||
//Buy our Equity
|
||||
var quantity = CalculateOrderQuantity(_spy, .1m);
|
||||
@@ -114,7 +114,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// </summary>
|
||||
/// <param name="orderEvent">OrderEvent object that contains all the information about the event</param>
|
||||
public override void OnOrderEvent(OrderEvent orderEvent)
|
||||
{
|
||||
{
|
||||
// Get the order from our transactions
|
||||
var order = Transactions.GetOrderById(orderEvent.OrderId);
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
|
||||
// All PartiallyFilled orders should have a LastFillTime
|
||||
case OrderStatus.PartiallyFilled:
|
||||
if (order.LastFillTime == null)
|
||||
if (order.LastFillTime == null)
|
||||
{
|
||||
throw new Exception("LastFillTime should not be null");
|
||||
}
|
||||
@@ -183,9 +183,9 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
throw new Exception("OptionExercise order price should be strike price!!");
|
||||
}
|
||||
|
||||
if (orderEvent.Quantity != 1)
|
||||
if (orderEvent.Quantity != -1)
|
||||
{
|
||||
throw new Exception("OrderEvent Quantity should be 1");
|
||||
throw new Exception("OrderEvent Quantity should be -1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Treynor Ratio", "-0.018"},
|
||||
{"Total Fees", "$2.00"},
|
||||
{"Fitness Score", "0.213"},
|
||||
{"OrderListHash", "-1514011542"}
|
||||
{"OrderListHash", "904167951"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,4 +119,4 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"OrderListHash", "491919591"}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1073240275"}
|
||||
{"OrderListHash", "415415696"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1935621950"}
|
||||
{"OrderListHash", "687310345"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1708974186"}
|
||||
{"OrderListHash", "737971736"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstration of how to chain a coarse and fine universe selection with an option chain universe selection model
|
||||
/// that will add and remove an <see cref="OptionChainUniverse"/> for each symbol selected on fine
|
||||
/// </summary>
|
||||
public class CoarseFineOptionUniverseChainRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
// initialize our changes to nothing
|
||||
private SecurityChanges _changes = SecurityChanges.None;
|
||||
private int _optionCount;
|
||||
private Symbol _lastEquityAdded;
|
||||
private Symbol _aapl;
|
||||
private Symbol _twx;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_twx = QuantConnect.Symbol.Create("TWX", SecurityType.Equity, Market.USA);
|
||||
_aapl = QuantConnect.Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
|
||||
UniverseSettings.Resolution = Resolution.Minute;
|
||||
|
||||
SetStartDate(2014, 06, 05);
|
||||
SetEndDate(2014, 06, 06);
|
||||
|
||||
var selectionUniverse = AddUniverse(enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl },
|
||||
enumerable => new[] { Time.Date <= new DateTime(2014, 6, 5) ? _twx : _aapl });
|
||||
|
||||
AddUniverseOptions(selectionUniverse, universe =>
|
||||
{
|
||||
if (universe.Underlying == null)
|
||||
{
|
||||
throw new Exception("Underlying data point is null! This shouldn't happen, each OptionChainUniverse handles and should provide this");
|
||||
}
|
||||
return universe.IncludeWeeklys()
|
||||
.FrontMonth()
|
||||
.Contracts(universe.Take(5));
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
// if we have no changes, do nothing
|
||||
if (_changes == SecurityChanges.None ||
|
||||
_changes.AddedSecurities.Any(security => security.Price == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// liquidate removed securities
|
||||
foreach (var security in _changes.RemovedSecurities)
|
||||
{
|
||||
if (security.Invested)
|
||||
{
|
||||
Liquidate(security.Symbol);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var security in _changes.AddedSecurities)
|
||||
{
|
||||
if (!security.Symbol.HasUnderlying)
|
||||
{
|
||||
_lastEquityAdded = security.Symbol;
|
||||
}
|
||||
else
|
||||
{
|
||||
// options added should all match prev added security
|
||||
if (security.Symbol.Underlying != _lastEquityAdded)
|
||||
{
|
||||
throw new Exception($"Unexpected symbol added {security.Symbol}");
|
||||
}
|
||||
|
||||
_optionCount++;
|
||||
}
|
||||
|
||||
SetHoldings(security.Symbol, 0.05m);
|
||||
|
||||
var config = SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(security.Symbol).ToList();
|
||||
|
||||
if (!config.Any())
|
||||
{
|
||||
throw new Exception($"Was expecting configurations for {security.Symbol}");
|
||||
}
|
||||
if (config.Any(dataConfig => dataConfig.DataNormalizationMode != DataNormalizationMode.Raw))
|
||||
{
|
||||
throw new Exception($"Was expecting DataNormalizationMode.Raw configurations for {security.Symbol}");
|
||||
}
|
||||
}
|
||||
_changes = SecurityChanges.None;
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(SecurityChanges changes)
|
||||
{
|
||||
_changes += changes;
|
||||
}
|
||||
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
var config = SubscriptionManager.Subscriptions.ToList();
|
||||
if (config.Any(dataConfig => dataConfig.Symbol == _twx || dataConfig.Symbol.Underlying == _twx))
|
||||
{
|
||||
throw new Exception($"Was NOT expecting any configurations for {_twx} or it's options, since coarse/fine should have deselected it");
|
||||
}
|
||||
|
||||
if (_optionCount == 0)
|
||||
{
|
||||
throw new Exception("Option universe chain did not add any option!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "13"},
|
||||
{"Average Win", "0.65%"},
|
||||
{"Average Loss", "-0.05%"},
|
||||
{"Compounding Annual Return", "3216040423556140000000000%"},
|
||||
{"Drawdown", "0.500%"},
|
||||
{"Expectancy", "1.393"},
|
||||
{"Net Profit", "32.840%"},
|
||||
{"Sharpe Ratio", "7.14272222483913E+15"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "83%"},
|
||||
{"Win Rate", "17%"},
|
||||
{"Profit-Loss Ratio", "13.36"},
|
||||
{"Alpha", "2.59468989671647E+16"},
|
||||
{"Beta", "67.661"},
|
||||
{"Annual Standard Deviation", "3.633"},
|
||||
{"Annual Variance", "13.196"},
|
||||
{"Information Ratio", "7.24987266907741E+15"},
|
||||
{"Tracking Error", "3.579"},
|
||||
{"Treynor Ratio", "383485597312030"},
|
||||
{"Total Fees", "$13.00"},
|
||||
{"Fitness Score", "0.232"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "79228162514264337593543950335"},
|
||||
{"Portfolio Turnover", "0.232"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1630141557"}
|
||||
};
|
||||
}
|
||||
}
|
||||
138
Algorithm.CSharp/CustomBuyingPowerModelAlgorithm.cs
Normal file
138
Algorithm.CSharp/CustomBuyingPowerModelAlgorithm.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstration of using custom buying power model in backtesting.
|
||||
/// QuantConnect allows you to model all orders as deeply and accurately as you need.
|
||||
/// </summary>
|
||||
/// <meta name="tag" content="trading and orders" />
|
||||
/// <meta name="tag" content="transaction fees and slippage" />
|
||||
/// <meta name="tag" content="custom buying power models" />
|
||||
public class CustomBuyingPowerModelAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _spy;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2013, 10, 01);
|
||||
SetEndDate(2013, 10, 31);
|
||||
var security = AddEquity("SPY", Resolution.Hour);
|
||||
_spy = security.Symbol;
|
||||
|
||||
// set the buying power model
|
||||
security.SetBuyingPowerModel(new CustomBuyingPowerModel());
|
||||
}
|
||||
|
||||
public void OnData(Slice slice)
|
||||
{
|
||||
if (Portfolio.Invested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var quantity = CalculateOrderQuantity(_spy, 1m);
|
||||
if (quantity % 100 != 0)
|
||||
{
|
||||
throw new Exception($"CustomBuyingPowerModel only allow quantity that is multiple of 100 and {quantity} was found");
|
||||
}
|
||||
|
||||
// We normally get insufficient buying power model, but the
|
||||
// CustomBuyingPowerModel always says that there is sufficient buying power for the orders
|
||||
MarketOrder(_spy, quantity * 10);
|
||||
}
|
||||
|
||||
public class CustomBuyingPowerModel : BuyingPowerModel
|
||||
{
|
||||
public override GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(
|
||||
GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters)
|
||||
{
|
||||
var quantity = base.GetMaximumOrderQuantityForTargetBuyingPower(parameters).Quantity;
|
||||
quantity = Math.Floor(quantity / 100) * 100;
|
||||
return new GetMaximumOrderQuantityResult(quantity);
|
||||
}
|
||||
|
||||
public override HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(
|
||||
HasSufficientBuyingPowerForOrderParameters parameters)
|
||||
{
|
||||
return new HasSufficientBuyingPowerForOrderResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "1"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "5672.520%"},
|
||||
{"Drawdown", "22.500%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "40.601%"},
|
||||
{"Sharpe Ratio", "40.201"},
|
||||
{"Probabilistic Sharpe Ratio", "77.339%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "41.848"},
|
||||
{"Beta", "9.224"},
|
||||
{"Annual Standard Deviation", "1.164"},
|
||||
{"Annual Variance", "1.355"},
|
||||
{"Information Ratio", "44.459"},
|
||||
{"Tracking Error", "1.04"},
|
||||
{"Treynor Ratio", "5.073"},
|
||||
{"Total Fees", "$30.00"},
|
||||
{"Fitness Score", "0.418"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "113.05"},
|
||||
{"Return Over Maximum Drawdown", "442.81"},
|
||||
{"Portfolio Turnover", "0.418"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "639761089"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,12 @@ using QuantConnect.Securities;
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstration of using custom fee, slippage and fill models for modelling transactions in backtesting.
|
||||
/// Demonstration of using custom fee, slippage, fill, and buying power models for modelling transactions in backtesting.
|
||||
/// QuantConnect allows you to model all orders as deeply and accurately as you need.
|
||||
/// </summary>
|
||||
/// <meta name="tag" content="trading and orders" />
|
||||
/// <meta name="tag" content="transaction fees and slippage" />
|
||||
/// <meta name="tag" content="custom buying power models" />
|
||||
/// <meta name="tag" content="custom transaction models" />
|
||||
/// <meta name="tag" content="custom slippage models" />
|
||||
/// <meta name="tag" content="custom fee models" />
|
||||
@@ -50,6 +51,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
_security.SetFeeModel(new CustomFeeModel(this));
|
||||
_security.SetFillModel(new CustomFillModel(this));
|
||||
_security.SetSlippageModel(new CustomSlippageModel(this));
|
||||
_security.SetBuyingPowerModel(new CustomBuyingPowerModel(this));
|
||||
}
|
||||
|
||||
public void OnData(TradeBars data)
|
||||
@@ -60,13 +62,13 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
if (Time.Day > 10 && _security.Holdings.Quantity <= 0)
|
||||
{
|
||||
var quantity = CalculateOrderQuantity(_spy, .5m);
|
||||
Log("MarketOrder: " + quantity);
|
||||
Log($"MarketOrder: {quantity}");
|
||||
MarketOrder(_spy, quantity, asynchronous: true); // async needed for partial fill market orders
|
||||
}
|
||||
else if (Time.Day > 20 && _security.Holdings.Quantity >= 0)
|
||||
{
|
||||
var quantity = CalculateOrderQuantity(_spy, -.5m);
|
||||
Log("MarketOrder: " + quantity);
|
||||
Log($"MarketOrder: {quantity}");
|
||||
MarketOrder(_spy, quantity, asynchronous: true); // async needed for partial fill market orders
|
||||
}
|
||||
}
|
||||
@@ -109,7 +111,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
fill.Status = OrderStatus.PartiallyFilled;
|
||||
}
|
||||
|
||||
_algorithm.Log("CustomFillModel: " + fill);
|
||||
_algorithm.Log($"CustomFillModel: {fill}");
|
||||
|
||||
return fill;
|
||||
}
|
||||
@@ -131,7 +133,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
1m,
|
||||
parameters.Security.Price*parameters.Order.AbsoluteQuantity*0.00001m);
|
||||
|
||||
_algorithm.Log("CustomFeeModel: " + fee);
|
||||
_algorithm.Log($"CustomFeeModel: {fee}");
|
||||
return new OrderFee(new CashAmount(fee, "USD"));
|
||||
}
|
||||
}
|
||||
@@ -150,11 +152,31 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
// custom slippage math
|
||||
var slippage = asset.Price*0.0001m*(decimal) Math.Log10(2*(double) order.AbsoluteQuantity);
|
||||
|
||||
_algorithm.Log("CustomSlippageModel: " + slippage);
|
||||
_algorithm.Log($"CustomSlippageModel: {slippage}");
|
||||
return slippage;
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomBuyingPowerModel : BuyingPowerModel
|
||||
{
|
||||
private readonly QCAlgorithm _algorithm;
|
||||
|
||||
public CustomBuyingPowerModel(QCAlgorithm algorithm)
|
||||
{
|
||||
_algorithm = algorithm;
|
||||
}
|
||||
|
||||
public override HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(
|
||||
HasSufficientBuyingPowerForOrderParameters parameters)
|
||||
{
|
||||
// custom behavior: this model will assume that there is always enough buying power
|
||||
var hasSufficientBuyingPowerForOrderResult = new HasSufficientBuyingPowerForOrderResult(true);
|
||||
_algorithm.Log($"CustomBuyingPowerModel: {hasSufficientBuyingPowerForOrderResult.IsSufficient}");
|
||||
|
||||
return hasSufficientBuyingPowerForOrderResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
|
||||
@@ -187,12 +187,12 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Total Trades", "6441"},
|
||||
{"Average Win", "0.07%"},
|
||||
{"Average Loss", "-0.07%"},
|
||||
{"Compounding Annual Return", "13.284%"},
|
||||
{"Compounding Annual Return", "13.331%"},
|
||||
{"Drawdown", "10.700%"},
|
||||
{"Expectancy", "0.061"},
|
||||
{"Net Profit", "13.284%"},
|
||||
{"Sharpe Ratio", "0.96"},
|
||||
{"Probabilistic Sharpe Ratio", "46.111%"},
|
||||
{"Net Profit", "13.331%"},
|
||||
{"Sharpe Ratio", "0.963"},
|
||||
{"Probabilistic Sharpe Ratio", "46.232%"},
|
||||
{"Loss Rate", "46%"},
|
||||
{"Win Rate", "54%"},
|
||||
{"Profit-Loss Ratio", "0.97"},
|
||||
@@ -200,15 +200,15 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Beta", "-0.066"},
|
||||
{"Annual Standard Deviation", "0.121"},
|
||||
{"Annual Variance", "0.015"},
|
||||
{"Information Ratio", "0.004"},
|
||||
{"Information Ratio", "0.006"},
|
||||
{"Tracking Error", "0.171"},
|
||||
{"Treynor Ratio", "-1.754"},
|
||||
{"Total Fees", "$8669.33"},
|
||||
{"Treynor Ratio", "-1.761"},
|
||||
{"Total Fees", "$8669.41"},
|
||||
{"Fitness Score", "0.675"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "1.124"},
|
||||
{"Return Over Maximum Drawdown", "1.242"},
|
||||
{"Sortino Ratio", "1.127"},
|
||||
{"Return Over Maximum Drawdown", "1.246"},
|
||||
{"Portfolio Turnover", "1.64"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
@@ -223,7 +223,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1120327913"}
|
||||
{"OrderListHash", "-75671425"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,12 +160,12 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Total Trades", "5059"},
|
||||
{"Average Win", "0.08%"},
|
||||
{"Average Loss", "-0.08%"},
|
||||
{"Compounding Annual Return", "14.901%"},
|
||||
{"Compounding Annual Return", "14.950%"},
|
||||
{"Drawdown", "10.600%"},
|
||||
{"Expectancy", "0.075"},
|
||||
{"Net Profit", "14.901%"},
|
||||
{"Sharpe Ratio", "1.068"},
|
||||
{"Probabilistic Sharpe Ratio", "50.201%"},
|
||||
{"Net Profit", "14.950%"},
|
||||
{"Sharpe Ratio", "1.072"},
|
||||
{"Probabilistic Sharpe Ratio", "50.327%"},
|
||||
{"Loss Rate", "45%"},
|
||||
{"Win Rate", "55%"},
|
||||
{"Profit-Loss Ratio", "0.97"},
|
||||
@@ -173,15 +173,15 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Beta", "-0.066"},
|
||||
{"Annual Standard Deviation", "0.121"},
|
||||
{"Annual Variance", "0.015"},
|
||||
{"Information Ratio", "0.08"},
|
||||
{"Information Ratio", "0.083"},
|
||||
{"Tracking Error", "0.171"},
|
||||
{"Treynor Ratio", "-1.963"},
|
||||
{"Total Fees", "$6806.57"},
|
||||
{"Treynor Ratio", "-1.971"},
|
||||
{"Total Fees", "$6806.67"},
|
||||
{"Fitness Score", "0.694"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "1.261"},
|
||||
{"Return Over Maximum Drawdown", "1.404"},
|
||||
{"Sortino Ratio", "1.265"},
|
||||
{"Return Over Maximum Drawdown", "1.409"},
|
||||
{"Portfolio Turnover", "1.296"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
@@ -196,7 +196,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "974523768"}
|
||||
{"OrderListHash", "1142077166"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks that the Tick BidPrice and AskPrices are adjusted like Value.
|
||||
/// </summary>
|
||||
public class EquityTickQuoteAdjustedModeRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _ibm;
|
||||
private bool _bought;
|
||||
private bool _sold;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2013, 10, 7);
|
||||
SetEndDate(2013, 10, 11);
|
||||
SetCash(100000);
|
||||
|
||||
_ibm = AddEquity("IBM", Resolution.Tick).Symbol;
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (!data.Ticks.ContainsKey(_ibm))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var security = Securities[_ibm];
|
||||
if (!security.HasData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var tick in data.Ticks[_ibm])
|
||||
{
|
||||
if (tick.BidPrice != 0 && !_bought && ((tick.Value - tick.BidPrice) <= 0.05m))
|
||||
{
|
||||
SetHoldings(_ibm, 1);
|
||||
_bought = true;
|
||||
return;
|
||||
}
|
||||
if (tick.AskPrice != 0 && _bought && !_sold && Math.Abs((double)tick.Value - (double)tick.AskPrice) <= 0.05)
|
||||
{
|
||||
Liquidate(_ibm);
|
||||
_sold = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "-0.01%"},
|
||||
{"Compounding Annual Return", "-0.500%"},
|
||||
{"Drawdown", "0.000%"},
|
||||
{"Expectancy", "-1"},
|
||||
{"Net Profit", "-0.006%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "100%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0"},
|
||||
{"Beta", "0"},
|
||||
{"Annual Standard Deviation", "0"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "-8.769"},
|
||||
{"Tracking Error", "0.22"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$6.41"},
|
||||
{"Fitness Score", "0.248"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-82.815"},
|
||||
{"Portfolio Turnover", "0.497"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1213851303"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm testing doing some history requests outside market hours, reproducing GH issue #4783
|
||||
/// </summary>
|
||||
public class ExtendedMarketHoursHistoryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private int _minuteHistoryCount;
|
||||
private int _hourHistoryCount;
|
||||
private int _dailyHistoryCount;
|
||||
|
||||
/// <summary>
|
||||
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2013, 10, 07);
|
||||
SetEndDate(2013, 10, 09);
|
||||
SetCash(100000);
|
||||
|
||||
AddEquity("SPY", Resolution.Minute, extendedMarketHours:true, fillDataForward:false);
|
||||
|
||||
Schedule.On("RunHistoryCall", DateRules.EveryDay(), TimeRules.Every(TimeSpan.FromHours(1)), RunHistoryCall);
|
||||
}
|
||||
|
||||
private void RunHistoryCall()
|
||||
{
|
||||
var spy = Securities["SPY"];
|
||||
var regularHours = spy.Exchange.Hours.IsOpen(Time, false);
|
||||
var extendedHours = !regularHours && spy.Exchange.Hours.IsOpen(Time, true);
|
||||
|
||||
if (regularHours)
|
||||
{
|
||||
_minuteHistoryCount++;
|
||||
var history = History(spy.Symbol, 5, Resolution.Minute).Count();
|
||||
if (history != 5)
|
||||
{
|
||||
throw new Exception($"Unexpected Minute data count: {history}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (extendedHours)
|
||||
{
|
||||
_hourHistoryCount++;
|
||||
var history = History(spy.Symbol, 5, Resolution.Hour).Count();
|
||||
if (history != 5)
|
||||
{
|
||||
throw new Exception($"Unexpected Hour data count {history}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_dailyHistoryCount++;
|
||||
var history = History(spy.Symbol, 5, Resolution.Daily).Count();
|
||||
if (history != 5)
|
||||
{
|
||||
throw new Exception($"Unexpected Daily data count {history}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
/// </summary>
|
||||
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
SetHoldings("SPY", 1);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (_minuteHistoryCount != 3 * 6)
|
||||
{
|
||||
throw new Exception($"Unexpected minute history requests count {_minuteHistoryCount}");
|
||||
}
|
||||
// 6 pre market from 4am to 9am + 4 post market 4pm to 7pm
|
||||
if (_hourHistoryCount != 3 * 10)
|
||||
{
|
||||
throw new Exception($"Unexpected hour history requests count {_hourHistoryCount}");
|
||||
}
|
||||
// 0am to 3am + 8pm to 11pm, last day ends at 8pm
|
||||
if (_dailyHistoryCount != (2 * 8 + 5))
|
||||
{
|
||||
throw new Exception($"Unexpected Daily history requests count: {_dailyHistoryCount}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "20"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0.00%"},
|
||||
{"Compounding Annual Return", "-74.182%"},
|
||||
{"Drawdown", "2.200%"},
|
||||
{"Expectancy", "-1"},
|
||||
{"Net Profit", "-1.046%"},
|
||||
{"Sharpe Ratio", "-8.269"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "100%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "-0.19"},
|
||||
{"Beta", "0.579"},
|
||||
{"Annual Standard Deviation", "0.065"},
|
||||
{"Annual Variance", "0.004"},
|
||||
{"Information Ratio", "1.326"},
|
||||
{"Tracking Error", "0.049"},
|
||||
{"Treynor Ratio", "-0.934"},
|
||||
{"Total Fees", "$22.26"},
|
||||
{"Fitness Score", "0.002"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-11.855"},
|
||||
{"Return Over Maximum Drawdown", "-70.945"},
|
||||
{"Portfolio Turnover", "0.342"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1961710414"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "699698796"}
|
||||
{"OrderListHash", "1717552327"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
}
|
||||
|
||||
var firstBar = history.First().Bars.GetValue(symbol);
|
||||
if (firstBar.EndTime != new DateTime(1998, 3, 3) || firstBar.Close != 26.3607004m)
|
||||
if (firstBar.EndTime != new DateTime(1998, 3, 3) || firstBar.Close != 25.11427695m)
|
||||
{
|
||||
throw new Exception("First History bar - unexpected data received");
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1459983342"}
|
||||
{"OrderListHash", "187652813"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,32 +65,32 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "2"},
|
||||
{"Total Trades", "5"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "-0.98%"},
|
||||
{"Compounding Annual Return", "-53.792%"},
|
||||
{"Drawdown", "1.500%"},
|
||||
{"Average Loss", "-0.52%"},
|
||||
{"Compounding Annual Return", "246.602%"},
|
||||
{"Drawdown", "2.300%"},
|
||||
{"Expectancy", "-1"},
|
||||
{"Net Profit", "-0.982%"},
|
||||
{"Sharpe Ratio", "-5.949"},
|
||||
{"Probabilistic Sharpe Ratio", "1.216%"},
|
||||
{"Net Profit", "1.602%"},
|
||||
{"Sharpe Ratio", "8.065"},
|
||||
{"Probabilistic Sharpe Ratio", "65.943%"},
|
||||
{"Loss Rate", "100%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "-0.973"},
|
||||
{"Beta", "0.268"},
|
||||
{"Annual Standard Deviation", "0.077"},
|
||||
{"Annual Variance", "0.006"},
|
||||
{"Information Ratio", "-14.167"},
|
||||
{"Tracking Error", "0.168"},
|
||||
{"Treynor Ratio", "-1.705"},
|
||||
{"Total Fees", "$6.51"},
|
||||
{"Fitness Score", "0.249"},
|
||||
{"Alpha", "-0.157"},
|
||||
{"Beta", "1.015"},
|
||||
{"Annual Standard Deviation", "0.223"},
|
||||
{"Annual Variance", "0.05"},
|
||||
{"Information Ratio", "-27.079"},
|
||||
{"Tracking Error", "0.005"},
|
||||
{"Treynor Ratio", "1.772"},
|
||||
{"Total Fees", "$16.28"},
|
||||
{"Fitness Score", "0.999"},
|
||||
{"Kelly Criterion Estimate", "38.64"},
|
||||
{"Kelly Criterion Probability Value", "0.229"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-55.465"},
|
||||
{"Portfolio Turnover", "0.498"},
|
||||
{"Return Over Maximum Drawdown", "78.607"},
|
||||
{"Portfolio Turnover", "1.246"},
|
||||
{"Total Insights Generated", "100"},
|
||||
{"Total Insights Closed", "99"},
|
||||
{"Total Insights Analysis Completed", "99"},
|
||||
@@ -104,7 +104,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "54.5455%"},
|
||||
{"Rolling Averaged Population Direction", "59.8056%"},
|
||||
{"Rolling Averaged Population Magnitude", "59.8056%"},
|
||||
{"OrderListHash", "160051570"}
|
||||
{"OrderListHash", "-1552239367"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
122
Algorithm.CSharp/OptionAssignmentRegressionAlgorithm.cs
Normal file
122
Algorithm.CSharp/OptionAssignmentRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression algorithm verifies automatic option contract assignment behavior.
|
||||
/// </summary>
|
||||
/// <meta name="tag" content="regression test" />
|
||||
/// <meta name="tag" content="options" />
|
||||
/// <meta name="tag" content="using data" />
|
||||
/// <meta name="tag" content="filter selection" />
|
||||
public class OptionAssignmentRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Security Stock;
|
||||
|
||||
private Security CallOption;
|
||||
private Symbol CallOptionSymbol;
|
||||
|
||||
private Security PutOption;
|
||||
private Symbol PutOptionSymbol;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2015, 12, 23);
|
||||
SetEndDate(2015, 12, 24);
|
||||
SetCash(100000);
|
||||
Stock = AddEquity("GOOG", Resolution.Minute);
|
||||
|
||||
var contracts = OptionChainProvider.GetOptionContractList(Stock.Symbol, UtcTime).ToList();
|
||||
|
||||
PutOptionSymbol = contracts
|
||||
.Where(c => c.ID.OptionRight == OptionRight.Put)
|
||||
.OrderBy(c => c.ID.Date)
|
||||
.First(c => c.ID.StrikePrice == 800m);
|
||||
|
||||
CallOptionSymbol = contracts
|
||||
.Where(c => c.ID.OptionRight == OptionRight.Call)
|
||||
.OrderBy(c => c.ID.Date)
|
||||
.First(c => c.ID.StrikePrice == 600m);
|
||||
|
||||
PutOption = AddOptionContract(PutOptionSymbol);
|
||||
CallOption = AddOptionContract(CallOptionSymbol);
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (!Portfolio.Invested && Stock.Price != 0 && PutOption.Price != 0 && CallOption.Price != 0)
|
||||
{
|
||||
// this gets executed on start and after each auto-assignment, finally ending with expiration assignment
|
||||
MarketOrder(PutOptionSymbol, -1);
|
||||
MarketOrder(CallOptionSymbol, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanRunLocally { get; } = true;
|
||||
public Language[] Languages { get; } = {Language.CSharp};
|
||||
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "22"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "0%"},
|
||||
{"Drawdown", "0%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "0%"},
|
||||
{"Sharpe Ratio", "0"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0"},
|
||||
{"Beta", "0"},
|
||||
{"Annual Standard Deviation", "0"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "0"},
|
||||
{"Tracking Error", "0"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$12.00"},
|
||||
{"Fitness Score", "0.5"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-50.218"},
|
||||
{"Portfolio Turnover", "6.713"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1597098916"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ using QuantConnect.Interfaces;
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Demonstration of the Option Chain Provider -- a much faster mechanism for manually specifying the option contracts you'd like to recieve
|
||||
/// Demonstration of the Option Chain Provider -- a much faster mechanism for manually specifying the option contracts you'd like to receive
|
||||
/// data for and manually subscribing to them.
|
||||
/// </summary>
|
||||
/// <meta name="tag" content="strategy example" />
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Regression algorithm making sure that the added universe selection does not remove the option chain during it's daily refresh
|
||||
/// </summary>
|
||||
public class OptionChainedAndUniverseSelectionRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private Symbol _aaplOption;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
UniverseSettings.Resolution = Resolution.Minute;
|
||||
|
||||
SetStartDate(2014, 06, 05);
|
||||
SetEndDate(2014, 06, 09);
|
||||
|
||||
_aaplOption = AddOption("AAPL").Symbol;
|
||||
AddUniverseSelection(new DailyUniverseSelectionModel("MyCustomSelectionModel", time => new[] { "AAPL" }, this));
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
Buy("AAPL", 1);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
var config = SubscriptionManager.Subscriptions.ToList();
|
||||
if (config.All(dataConfig => dataConfig.Symbol != "AAPL"))
|
||||
{
|
||||
throw new Exception("Was expecting configurations for AAPL");
|
||||
}
|
||||
if (config.All(dataConfig => dataConfig.Symbol.SecurityType != SecurityType.Option))
|
||||
{
|
||||
throw new Exception($"Was expecting configurations for {_aaplOption}");
|
||||
}
|
||||
}
|
||||
|
||||
private class DailyUniverseSelectionModel : CustomUniverseSelectionModel
|
||||
{
|
||||
private DateTime _lastRefresh;
|
||||
private IAlgorithm _algorithm;
|
||||
|
||||
public DailyUniverseSelectionModel(string name, Func<DateTime, IEnumerable<string>> selector, IAlgorithm algorithm) : base(name, selector)
|
||||
{
|
||||
_algorithm = algorithm;
|
||||
}
|
||||
|
||||
public override DateTime GetNextRefreshTimeUtc()
|
||||
{
|
||||
if (_lastRefresh != _algorithm.Time.Date)
|
||||
{
|
||||
_lastRefresh = _algorithm.Time.Date;
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
return DateTime.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "1"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "0.678%"},
|
||||
{"Drawdown", "3.700%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "0.009%"},
|
||||
{"Sharpe Ratio", "7.969"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0.046"},
|
||||
{"Beta", "-0.032"},
|
||||
{"Annual Standard Deviation", "0.001"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "-24.461"},
|
||||
{"Tracking Error", "0.044"},
|
||||
{"Treynor Ratio", "-0.336"},
|
||||
{"Total Fees", "$1.00"},
|
||||
{"Fitness Score", "0.003"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "79228162514264337593543950335"},
|
||||
{"Portfolio Turnover", "0.003"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1779427412"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "1038273097"}
|
||||
{"OrderListHash", "-1726463684"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,13 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// <param name="slice">The current slice of data keyed by symbol string</param>
|
||||
public override void OnData(Slice slice)
|
||||
{
|
||||
foreach (var dividend in slice.Dividends.Values)
|
||||
{
|
||||
if (dividend.ReferencePrice != 32.59m || dividend.Distribution != 3.82m)
|
||||
{
|
||||
throw new Exception($"{Time} - Invalid dividend {dividend}");
|
||||
}
|
||||
}
|
||||
if (!Portfolio.Invested)
|
||||
{
|
||||
if (Time.Day == 28 && Time.Hour > 9 && Time.Minute > 0)
|
||||
@@ -139,11 +146,11 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Total Trades", "4"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "-0.02%"},
|
||||
{"Compounding Annual Return", "-0.453%"},
|
||||
{"Compounding Annual Return", "-0.492%"},
|
||||
{"Drawdown", "0.000%"},
|
||||
{"Expectancy", "-1"},
|
||||
{"Net Profit", "-0.006%"},
|
||||
{"Sharpe Ratio", "-3.554"},
|
||||
{"Sharpe Ratio", "-3.943"},
|
||||
{"Probabilistic Sharpe Ratio", "0%"},
|
||||
{"Loss Rate", "100%"},
|
||||
{"Win Rate", "0%"},
|
||||
@@ -152,15 +159,15 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Beta", "0"},
|
||||
{"Annual Standard Deviation", "0.002"},
|
||||
{"Annual Variance", "0"},
|
||||
{"Information Ratio", "-3.554"},
|
||||
{"Information Ratio", "-3.943"},
|
||||
{"Tracking Error", "0.002"},
|
||||
{"Treynor Ratio", "0"},
|
||||
{"Total Fees", "$4.00"},
|
||||
{"Fitness Score", "0.001"},
|
||||
{"Fitness Score", "0"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "-1.768"},
|
||||
{"Return Over Maximum Drawdown", "-2.808"},
|
||||
{"Portfolio Turnover", "0.001"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
|
||||
@@ -142,8 +142,18 @@
|
||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="AddAlphaModelAlgorithm.cs" />
|
||||
<Compile Include="CustomBuyingPowerModelAlgorithm.cs" />
|
||||
<Compile Include="AddOptionContractExpiresRegressionAlgorithm.cs" />
|
||||
<Compile Include="AltData\QuiverWallStreetBetsDataAlgorithm.cs" />
|
||||
<Compile Include="ScaledFillForwardDataRegressionAlgorithm.cs" />
|
||||
<Compile Include="DailyHistoryForDailyResolutionRegressionAlgorithm.cs" />
|
||||
<Compile Include="DailyHistoryForMinuteResolutionRegressionAlgorithm.cs" />
|
||||
<Compile Include="ExtendedMarketHoursHistoryRegressionAlgorithm.cs" />
|
||||
<Compile Include="EquityTickQuoteAdjustedModeRegressionAlgorithm.cs" />
|
||||
<Compile Include="AddOptionContractFromUniverseRegressionAlgorithm.cs" />
|
||||
<Compile Include="CoarseFineOptionUniverseChainRegressionAlgorithm.cs" />
|
||||
<Compile Include="OptionChainedAndUniverseSelectionRegressionAlgorithm.cs" />
|
||||
<Compile Include="OptionAssignmentRegressionAlgorithm.cs" />
|
||||
<Compile Include="SwitchDataModeRegressionAlgorithm.cs" />
|
||||
<Compile Include="AddRemoveOptionUniverseRegressionAlgorithm.cs" />
|
||||
<Compile Include="AddRemoveSecurityRegressionAlgorithm.cs" />
|
||||
|
||||
143
Algorithm.CSharp/ScaledFillForwardDataRegressionAlgorithm.cs
Normal file
143
Algorithm.CSharp/ScaledFillForwardDataRegressionAlgorithm.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// This regression test algorithm reproduces issue https://github.com/QuantConnect/Lean/issues/4834
|
||||
/// fixed in PR https://github.com/QuantConnect/Lean/pull/4836
|
||||
/// Adjusted data of fill forward bars should use original scale factor
|
||||
/// </summary>
|
||||
public class ScaledFillForwardDataRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private TradeBar _lastRealBar;
|
||||
private Symbol _twx;
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2014, 6, 5);
|
||||
SetEndDate(2014, 6, 9);
|
||||
|
||||
_twx = AddEquity("TWX", Resolution.Minute, extendedMarketHours: true).Symbol;
|
||||
Schedule.On(DateRules.EveryDay(_twx), TimeRules.Every(TimeSpan.FromHours(1)), PlotPrice);
|
||||
}
|
||||
|
||||
private void PlotPrice()
|
||||
{
|
||||
Plot($"{_twx}", "Ask", Securities[_twx].AskPrice);
|
||||
Plot($"{_twx}", "Bid", Securities[_twx].BidPrice);
|
||||
Plot($"{_twx}", "Price", Securities[_twx].Price);
|
||||
Plot("Portfolio.TPV", "Value", Portfolio.TotalPortfolioValue);
|
||||
}
|
||||
|
||||
public override void OnData(Slice data)
|
||||
{
|
||||
var current = data.Bars.FirstOrDefault().Value;
|
||||
if (current != null)
|
||||
{
|
||||
if (Time == new DateTime(2014, 06, 09, 4, 1, 0) && !Portfolio.Invested)
|
||||
{
|
||||
if (!current.IsFillForward)
|
||||
{
|
||||
throw new Exception($"Was expecting a first fill forward bar {Time}");
|
||||
}
|
||||
|
||||
// trade on the first bar after a factor price scale change. +10 so we fill ASAP. Limit so it fills in extended market hours
|
||||
LimitOrder(_twx, 1000, _lastRealBar.Close + 10);
|
||||
}
|
||||
|
||||
if (_lastRealBar == null || !current.IsFillForward)
|
||||
{
|
||||
_lastRealBar = current;
|
||||
}
|
||||
else if (_lastRealBar.Close != current.Close)
|
||||
{
|
||||
throw new Exception($"FillForwarded data point at {Time} was scaled. Actual: {current.Close}; Expected: {_lastRealBar.Close}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndOfAlgorithm()
|
||||
{
|
||||
if (_lastRealBar == null)
|
||||
{
|
||||
throw new Exception($"Not all expected data points were received.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public Language[] Languages { get; } = { Language.CSharp };
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "1"},
|
||||
{"Average Win", "0%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "32.825%"},
|
||||
{"Drawdown", "0.800%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "0.377%"},
|
||||
{"Sharpe Ratio", "8.953"},
|
||||
{"Probabilistic Sharpe Ratio", "95.977%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "0%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "0.314"},
|
||||
{"Beta", "-0.104"},
|
||||
{"Annual Standard Deviation", "0.03"},
|
||||
{"Annual Variance", "0.001"},
|
||||
{"Information Ratio", "-3.498"},
|
||||
{"Tracking Error", "0.05"},
|
||||
{"Treynor Ratio", "-2.573"},
|
||||
{"Total Fees", "$5.00"},
|
||||
{"Fitness Score", "0.158"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "79228162514264337593543950335"},
|
||||
{"Return Over Maximum Drawdown", "79228162514264337593543950335"},
|
||||
{"Portfolio Turnover", "0.158"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
{"Long Insight Count", "0"},
|
||||
{"Short Insight Count", "0"},
|
||||
{"Long/Short Ratio", "100%"},
|
||||
{"Estimated Monthly Alpha Value", "$0"},
|
||||
{"Total Accumulated Estimated Alpha Value", "$0"},
|
||||
{"Mean Population Estimated Insight Value", "$0"},
|
||||
{"Mean Population Direction", "0%"},
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "960108217"}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{
|
||||
if (_expectedCloseValues.Count > 0)
|
||||
{
|
||||
throw new Exception($"Not all expected data points were recieved.");
|
||||
throw new Exception($"Not all expected data points were received.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-569072921"}
|
||||
{"OrderListHash", "359885308"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,28 +117,28 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Total Trades", "3528"},
|
||||
{"Average Win", "0.67%"},
|
||||
{"Average Loss", "-0.71%"},
|
||||
{"Compounding Annual Return", "17.318%"},
|
||||
{"Compounding Annual Return", "17.227%"},
|
||||
{"Drawdown", "63.700%"},
|
||||
{"Expectancy", "0.020"},
|
||||
{"Net Profit", "17.318%"},
|
||||
{"Sharpe Ratio", "0.836"},
|
||||
{"Probabilistic Sharpe Ratio", "33.715%"},
|
||||
{"Net Profit", "17.227%"},
|
||||
{"Sharpe Ratio", "0.834"},
|
||||
{"Probabilistic Sharpe Ratio", "33.688%"},
|
||||
{"Loss Rate", "48%"},
|
||||
{"Win Rate", "52%"},
|
||||
{"Profit-Loss Ratio", "0.95"},
|
||||
{"Alpha", "0.826"},
|
||||
{"Alpha", "0.825"},
|
||||
{"Beta", "-0.34"},
|
||||
{"Annual Standard Deviation", "0.945"},
|
||||
{"Annual Variance", "0.893"},
|
||||
{"Information Ratio", "0.714"},
|
||||
{"Information Ratio", "0.713"},
|
||||
{"Tracking Error", "0.957"},
|
||||
{"Treynor Ratio", "-2.325"},
|
||||
{"Total Fees", "$24713.42"},
|
||||
{"Treynor Ratio", "-2.323"},
|
||||
{"Total Fees", "$24760.85"},
|
||||
{"Fitness Score", "0.54"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "0.24"},
|
||||
{"Return Over Maximum Drawdown", "0.272"},
|
||||
{"Sortino Ratio", "0.238"},
|
||||
{"Return Over Maximum Drawdown", "0.27"},
|
||||
{"Portfolio Turnover", "7.204"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
@@ -153,7 +153,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1547947497"}
|
||||
{"OrderListHash", "843493486"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,32 +172,32 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Trades", "5"},
|
||||
{"Total Trades", "4"},
|
||||
{"Average Win", "0.64%"},
|
||||
{"Average Loss", "0%"},
|
||||
{"Compounding Annual Return", "-74.197%"},
|
||||
{"Drawdown", "6.600%"},
|
||||
{"Compounding Annual Return", "-56.577%"},
|
||||
{"Drawdown", "3.800%"},
|
||||
{"Expectancy", "0"},
|
||||
{"Net Profit", "-6.115%"},
|
||||
{"Sharpe Ratio", "-2.281"},
|
||||
{"Probabilistic Sharpe Ratio", "11.870%"},
|
||||
{"Net Profit", "-3.811%"},
|
||||
{"Sharpe Ratio", "-2.773"},
|
||||
{"Probabilistic Sharpe Ratio", "13.961%"},
|
||||
{"Loss Rate", "0%"},
|
||||
{"Win Rate", "100%"},
|
||||
{"Profit-Loss Ratio", "0"},
|
||||
{"Alpha", "-0.684"},
|
||||
{"Beta", "-0.113"},
|
||||
{"Annual Standard Deviation", "0.292"},
|
||||
{"Annual Variance", "0.085"},
|
||||
{"Information Ratio", "-1.606"},
|
||||
{"Tracking Error", "0.312"},
|
||||
{"Treynor Ratio", "5.866"},
|
||||
{"Total Fees", "$5.00"},
|
||||
{"Fitness Score", "0.017"},
|
||||
{"Alpha", "-0.504"},
|
||||
{"Beta", "-0.052"},
|
||||
{"Annual Standard Deviation", "0.179"},
|
||||
{"Annual Variance", "0.032"},
|
||||
{"Information Ratio", "-1.599"},
|
||||
{"Tracking Error", "0.207"},
|
||||
{"Treynor Ratio", "9.508"},
|
||||
{"Total Fees", "$4.00"},
|
||||
{"Fitness Score", "0.008"},
|
||||
{"Kelly Criterion Estimate", "0"},
|
||||
{"Kelly Criterion Probability Value", "0"},
|
||||
{"Sortino Ratio", "-2.584"},
|
||||
{"Return Over Maximum Drawdown", "-11.287"},
|
||||
{"Portfolio Turnover", "0.177"},
|
||||
{"Sortino Ratio", "-3.791"},
|
||||
{"Return Over Maximum Drawdown", "-14.846"},
|
||||
{"Portfolio Turnover", "0.136"},
|
||||
{"Total Insights Generated", "0"},
|
||||
{"Total Insights Closed", "0"},
|
||||
{"Total Insights Analysis Completed", "0"},
|
||||
@@ -211,7 +211,7 @@ namespace QuantConnect.Algorithm.CSharp
|
||||
{"Mean Population Magnitude", "0%"},
|
||||
{"Rolling Averaged Population Direction", "0%"},
|
||||
{"Rolling Averaged Population Magnitude", "0%"},
|
||||
{"OrderListHash", "-1386253041"}
|
||||
{"OrderListHash", "1484950465"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,11 @@ namespace QuantConnect.Algorithm.Framework.Risk
|
||||
}
|
||||
|
||||
var pnl = GetTotalDrawdownPercent(currentValue);
|
||||
if (pnl < _maximumDrawdownPercent)
|
||||
if (pnl < _maximumDrawdownPercent && targets.Length != 0)
|
||||
{
|
||||
foreach(var target in targets)
|
||||
// reset the trailing high value for restart investing on next rebalcing period
|
||||
_initialised = false;
|
||||
foreach (var target in targets)
|
||||
yield return new PortfolioTarget(target.Symbol, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,11 @@ class MaximumDrawdownPercentPortfolio(RiskManagementModel):
|
||||
return [] # return if new high reached
|
||||
|
||||
pnl = self.GetTotalDrawdownPercent(currentValue)
|
||||
if pnl < self.maximumDrawdownPercent:
|
||||
if pnl < self.maximumDrawdownPercent and len(targets) != 0:
|
||||
self.initialised = False # reset the trailing high value for restart investing on next rebalcing period
|
||||
return [ PortfolioTarget(target.Symbol, 0) for target in targets ]
|
||||
|
||||
return []
|
||||
|
||||
def GetTotalDrawdownPercent(self, currentValue):
|
||||
return (float(currentValue) / float(self.portfolioHigh)) - 1.0
|
||||
return (float(currentValue) / float(self.portfolioHigh)) - 1.0
|
||||
|
||||
@@ -15,12 +15,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Auxiliary;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Securities.Option;
|
||||
|
||||
namespace QuantConnect.Algorithm.Framework.Selection
|
||||
{
|
||||
@@ -107,53 +104,11 @@ namespace QuantConnect.Algorithm.Framework.Selection
|
||||
// prevent creating duplicate option chains -- one per underlying
|
||||
if (uniqueUnderlyingSymbols.Add(optionSymbol.Underlying))
|
||||
{
|
||||
yield return CreateOptionChain(algorithm, optionSymbol);
|
||||
yield return algorithm.CreateOptionChain(optionSymbol, Filter, _universeSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the canonical <see cref="Option"/> chain security for a given symbol
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance to create universes for</param>
|
||||
/// <param name="symbol">Symbol of the option</param>
|
||||
/// <param name="settings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
|
||||
/// <param name="initializer">Performs extra initialization (such as setting models) after we create a new security object</param>
|
||||
/// <returns><see cref="Option"/> for the given symbol</returns>
|
||||
[Obsolete("This method is obsolete because SecurityInitializer is obsolete and will not be used.")]
|
||||
protected virtual Option CreateOptionChainSecurity(QCAlgorithm algorithm, Symbol symbol, UniverseSettings settings, ISecurityInitializer initializer)
|
||||
{
|
||||
return CreateOptionChainSecurity(
|
||||
algorithm.SubscriptionManager.SubscriptionDataConfigService,
|
||||
symbol,
|
||||
settings,
|
||||
algorithm.Securities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the canonical <see cref="Option"/> chain security for a given symbol
|
||||
/// </summary>
|
||||
/// <param name="subscriptionDataConfigService">The service used to create new <see cref="SubscriptionDataConfig"/></param>
|
||||
/// <param name="symbol">Symbol of the option</param>
|
||||
/// <param name="settings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
|
||||
/// <param name="securityManager">Used to create new <see cref="Security"/></param>
|
||||
/// <returns><see cref="Option"/> for the given symbol</returns>
|
||||
protected virtual Option CreateOptionChainSecurity(
|
||||
ISubscriptionDataConfigService subscriptionDataConfigService,
|
||||
Symbol symbol,
|
||||
UniverseSettings settings,
|
||||
SecurityManager securityManager)
|
||||
{
|
||||
var config = subscriptionDataConfigService.Add(
|
||||
typeof(ZipEntryName),
|
||||
symbol,
|
||||
settings.Resolution,
|
||||
settings.FillForward,
|
||||
settings.ExtendedMarketHours,
|
||||
false);
|
||||
return (Option)securityManager.CreateSecurity(symbol, config, settings.Leverage, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the option chain universe filter
|
||||
/// </summary>
|
||||
@@ -162,55 +117,5 @@ namespace QuantConnect.Algorithm.Framework.Selection
|
||||
// NOP
|
||||
return filter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="OptionChainUniverse"/> for a given symbol
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance to create universes for</param>
|
||||
/// <param name="symbol">Symbol of the option</param>
|
||||
/// <returns><see cref="OptionChainUniverse"/> for the given symbol</returns>
|
||||
private OptionChainUniverse CreateOptionChain(QCAlgorithm algorithm, Symbol symbol)
|
||||
{
|
||||
if (symbol.SecurityType != SecurityType.Option)
|
||||
{
|
||||
throw new ArgumentException("CreateOptionChain requires an option symbol.");
|
||||
}
|
||||
|
||||
// rewrite non-canonical symbols to be canonical
|
||||
var market = symbol.ID.Market;
|
||||
var underlying = symbol.Underlying;
|
||||
if (!symbol.IsCanonical())
|
||||
{
|
||||
var alias = $"?{underlying.Value}";
|
||||
symbol = Symbol.Create(underlying.Value, SecurityType.Option, market, alias);
|
||||
}
|
||||
|
||||
// resolve defaults if not specified
|
||||
var settings = _universeSettings ?? algorithm.UniverseSettings;
|
||||
|
||||
// create canonical security object, but don't duplicate if it already exists
|
||||
Security security;
|
||||
Option optionChain;
|
||||
if (!algorithm.Securities.TryGetValue(symbol, out security))
|
||||
{
|
||||
optionChain = CreateOptionChainSecurity(
|
||||
algorithm.SubscriptionManager.SubscriptionDataConfigService,
|
||||
symbol,
|
||||
settings,
|
||||
algorithm.Securities);
|
||||
}
|
||||
else
|
||||
{
|
||||
optionChain = (Option)security;
|
||||
}
|
||||
|
||||
// set the option chain contract filter function
|
||||
optionChain.SetFilter(Filter);
|
||||
|
||||
// force option chain security to not be directly tradable AFTER it's configured to ensure it's not overwritten
|
||||
optionChain.IsTradable = false;
|
||||
|
||||
return new OptionChainUniverse(optionChain, settings, algorithm.LiveMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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 clr import AddReference
|
||||
AddReference("System")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
AddReference("QuantConnect.Common")
|
||||
|
||||
from System import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from datetime import *
|
||||
|
||||
### <summary>
|
||||
### We add an option contract using 'QCAlgorithm.AddOptionContract' and place a trade, the underlying
|
||||
### gets deselected from the universe selection but should still be present since we manually added the option contract.
|
||||
### Later we call 'QCAlgorithm.RemoveOptionContract' and expect both option and underlying to be removed.
|
||||
### </summary>
|
||||
class AddOptionContractExpiresRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
|
||||
|
||||
self.SetStartDate(2014, 6, 5)
|
||||
self.SetEndDate(2014, 6, 30)
|
||||
|
||||
self._expiration = datetime(2014, 6, 21)
|
||||
self._option = None
|
||||
self._traded = False
|
||||
|
||||
self._twx = Symbol.Create("TWX", SecurityType.Equity, Market.USA)
|
||||
|
||||
self.AddUniverse("my-daily-universe-name", self.Selector)
|
||||
|
||||
def Selector(self, time):
|
||||
return [ "AAPL" ]
|
||||
|
||||
def OnData(self, data):
|
||||
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
|
||||
Arguments:
|
||||
data: Slice object keyed by symbol containing the stock data
|
||||
'''
|
||||
if self._option == None:
|
||||
options = self.OptionChainProvider.GetOptionContractList(self._twx, self.Time)
|
||||
options = sorted(options, key=lambda x: x.ID.Symbol)
|
||||
|
||||
option = next((option for option in options if option.ID.Date == self._expiration and option.ID.OptionRight == OptionRight.Call and option.ID.OptionStyle == OptionStyle.American), None)
|
||||
if option != None:
|
||||
self._option = self.AddOptionContract(option).Symbol;
|
||||
|
||||
if self._option != None and self.Securities[self._option].Price != 0 and not self._traded:
|
||||
self._traded = True;
|
||||
self.Buy(self._option, 1);
|
||||
|
||||
if self.Time > self._expiration and self.Securities[self._twx].Invested:
|
||||
# we liquidate the option exercised position
|
||||
self.Liquidate(self._twx);
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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 clr import AddReference
|
||||
AddReference("System")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
AddReference("QuantConnect.Common")
|
||||
|
||||
from System import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from datetime import *
|
||||
|
||||
### <summary>
|
||||
### We add an option contract using 'QCAlgorithm.AddOptionContract' and place a trade, the underlying
|
||||
### gets deselected from the universe selection but should still be present since we manually added the option contract.
|
||||
### Later we call 'QCAlgorithm.RemoveOptionContract' and expect both option and underlying to be removed.
|
||||
### </summary>
|
||||
class AddOptionContractFromUniverseRegressionAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
|
||||
|
||||
self.SetStartDate(2014, 6, 5)
|
||||
self.SetEndDate(2014, 6, 9)
|
||||
|
||||
self._expiration = datetime(2014, 6, 21)
|
||||
self._securityChanges = None
|
||||
self._option = None
|
||||
self._traded = False
|
||||
|
||||
self._twx = Symbol.Create("TWX", SecurityType.Equity, Market.USA)
|
||||
self._aapl = Symbol.Create("AAPL", SecurityType.Equity, Market.USA)
|
||||
self.UniverseSettings.Resolution = Resolution.Minute
|
||||
self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
|
||||
|
||||
self.AddUniverse(self.Selector, self.Selector)
|
||||
|
||||
def Selector(self, fundamental):
|
||||
if self.Time <= datetime(2014, 6, 5):
|
||||
return [ self._twx ]
|
||||
return [ self._aapl ]
|
||||
|
||||
def OnData(self, data):
|
||||
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
|
||||
|
||||
Arguments:
|
||||
data: Slice object keyed by symbol containing the stock data
|
||||
'''
|
||||
if self._option != None and self.Securities[self._option].Price != 0 and not self._traded:
|
||||
self._traded = True;
|
||||
self.Buy(self._option, 1);
|
||||
|
||||
if self.Time == datetime(2014, 6, 6, 14, 0, 0):
|
||||
# liquidate & remove the option
|
||||
self.RemoveOptionContract(self._option)
|
||||
|
||||
def OnSecuritiesChanged(self, changes):
|
||||
# keep track of all removed and added securities
|
||||
if self._securityChanges == None:
|
||||
self._securityChanges = changes
|
||||
else:
|
||||
self._securityChanges.op_Addition(self._securityChanges, changes)
|
||||
|
||||
if any(security.Symbol.SecurityType == SecurityType.Option for security in changes.AddedSecurities):
|
||||
return
|
||||
|
||||
for addedSecurity in changes.AddedSecurities:
|
||||
options = self.OptionChainProvider.GetOptionContractList(addedSecurity.Symbol, self.Time)
|
||||
options = sorted(options, key=lambda x: x.ID.Symbol)
|
||||
|
||||
option = next((option for option in options if option.ID.Date == self._expiration and option.ID.OptionRight == OptionRight.Call and option.ID.OptionStyle == OptionStyle.American), None)
|
||||
|
||||
self.AddOptionContract(option)
|
||||
|
||||
# just keep the first we got
|
||||
if self._option == None:
|
||||
self._option = option
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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 clr import AddReference
|
||||
AddReference("System")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
AddReference("QuantConnect.Common")
|
||||
|
||||
from System import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Custom.Quiver import *
|
||||
|
||||
### <summary>
|
||||
### Quiver Quantitative is a provider of alternative data.
|
||||
### This algorithm shows how to consume the 'QuiverWallStreetBets'
|
||||
### </summary>
|
||||
class QuiverWallStreetBetsDataAlgorithm(QCAlgorithm):
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2019, 1, 1)
|
||||
self.SetEndDate(2020, 6, 1)
|
||||
self.SetCash(100000)
|
||||
|
||||
aapl = self.AddEquity("AAPL", Resolution.Daily).Symbol
|
||||
quiverWSBSymbol = self.AddData(QuiverWallStreetBets, aapl).Symbol
|
||||
history = self.History(QuiverWallStreetBets, quiverWSBSymbol, 60, Resolution.Daily)
|
||||
|
||||
self.Debug(f"We got {len(history)} items from our history request");
|
||||
|
||||
def OnData(self, data):
|
||||
points = data.Get(QuiverWallStreetBets)
|
||||
for point in points.Values:
|
||||
# Go long in the stock if it was mentioned more than 5 times in the WallStreetBets daily discussion
|
||||
if point.Mentions > 5:
|
||||
self.SetHoldings(point.Symbol.Underlying, 1)
|
||||
|
||||
# Go short in the stock if it was mentioned less than 5 times in the WallStreetBets daily discussion
|
||||
if point.Mentions < 5:
|
||||
self.SetHoldings(point.Symbol.Underlying, -1)
|
||||
@@ -0,0 +1,99 @@
|
||||
# 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 clr import AddReference
|
||||
AddReference("System.Core")
|
||||
AddReference("System.Collections")
|
||||
AddReference("QuantConnect.Common")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
|
||||
from System import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Data.UniverseSelection import *
|
||||
from datetime import *
|
||||
|
||||
### <summary>
|
||||
### Demonstration of how to chain a coarse and fine universe selection with an option chain universe selection model
|
||||
### that will add and remove an'OptionChainUniverse' for each symbol selected on fine
|
||||
### </summary>
|
||||
class CoarseFineOptionUniverseChainRegressionAlgorithm(QCAlgorithm):
|
||||
|
||||
def Initialize(self):
|
||||
'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
|
||||
|
||||
self.SetStartDate(2014,6,5) #Set Start Date
|
||||
self.SetEndDate(2014,6,6) #Set End Date
|
||||
|
||||
self.UniverseSettings.Resolution = Resolution.Minute
|
||||
self._twx = Symbol.Create("TWX", SecurityType.Equity, Market.USA)
|
||||
self._aapl = Symbol.Create("AAPL", SecurityType.Equity, Market.USA)
|
||||
self._lastEquityAdded = None
|
||||
self._changes = None
|
||||
self._optionCount = 0
|
||||
|
||||
universe = self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
|
||||
|
||||
self.AddUniverseOptions(universe, self.OptionFilterFunction)
|
||||
|
||||
def OptionFilterFunction(self, universe):
|
||||
universe.IncludeWeeklys().FrontMonth()
|
||||
|
||||
contracts = list()
|
||||
for symbol in universe:
|
||||
if len(contracts) == 5:
|
||||
break
|
||||
contracts.append(symbol)
|
||||
return universe.Contracts(contracts)
|
||||
|
||||
def CoarseSelectionFunction(self, coarse):
|
||||
if self.Time <= datetime(2014,6,5):
|
||||
return [ self._twx ]
|
||||
return [ self._aapl ]
|
||||
|
||||
def FineSelectionFunction(self, fine):
|
||||
if self.Time <= datetime(2014,6,5):
|
||||
return [ self._twx ]
|
||||
return [ self._aapl ]
|
||||
|
||||
def OnData(self, data):
|
||||
if self._changes == None or any(security.Price == 0 for security in self._changes.AddedSecurities):
|
||||
return
|
||||
|
||||
# liquidate removed securities
|
||||
for security in self._changes.RemovedSecurities:
|
||||
if security.Invested:
|
||||
self.Liquidate(security.Symbol);
|
||||
|
||||
for security in self._changes.AddedSecurities:
|
||||
if not security.Symbol.HasUnderlying:
|
||||
self._lastEquityAdded = security.Symbol;
|
||||
else:
|
||||
# options added should all match prev added security
|
||||
if security.Symbol.Underlying != self._lastEquityAdded:
|
||||
raise ValueError(f"Unexpected symbol added {security.Symbol}")
|
||||
self._optionCount += 1
|
||||
|
||||
self.SetHoldings(security.Symbol, 0.05)
|
||||
self._changes = None
|
||||
|
||||
# this event fires whenever we have changes to our universe
|
||||
def OnSecuritiesChanged(self, changes):
|
||||
if self._changes == None:
|
||||
self._changes = changes
|
||||
return
|
||||
self._changes = self._changes.op_Addition(self._changes, changes)
|
||||
|
||||
def OnEndOfAlgorithm(self):
|
||||
if self._optionCount == 0:
|
||||
raise ValueError("Option universe chain did not add any option!")
|
||||
@@ -22,7 +22,6 @@ from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Indicators import *
|
||||
from QuantConnect.Securities import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Data.Consolidators import *
|
||||
from CustomDataRegressionAlgorithm import Bitcoin
|
||||
from datetime import timedelta
|
||||
|
||||
65
Algorithm.Python/CustomBuyingPowerModelAlgorithm.py
Normal file
65
Algorithm.Python/CustomBuyingPowerModelAlgorithm.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# 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 clr import AddReference
|
||||
AddReference("System")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
AddReference("QuantConnect.Common")
|
||||
|
||||
from System import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Securities import *
|
||||
import numpy as np
|
||||
|
||||
### <summary>
|
||||
### Demonstration of using custom buying power model in backtesting.
|
||||
### QuantConnect allows you to model all orders as deeply and accurately as you need.
|
||||
### </summary>
|
||||
### <meta name="tag" content="trading and orders" />
|
||||
### <meta name="tag" content="transaction fees and slippage" />
|
||||
### <meta name="tag" content="custom buying power models" />
|
||||
class CustomBuyingPowerModelAlgorithm(QCAlgorithm):
|
||||
'''Demonstration of using custom buying power model in backtesting.
|
||||
QuantConnect allows you to model all orders as deeply and accurately as you need.'''
|
||||
|
||||
def Initialize(self):
|
||||
self.SetStartDate(2013,10,1) # Set Start Date
|
||||
self.SetEndDate(2013,10,31) # Set End Date
|
||||
security = self.AddEquity("SPY", Resolution.Hour)
|
||||
self.spy = security.Symbol
|
||||
|
||||
# set the buying power model
|
||||
security.SetBuyingPowerModel(CustomBuyingPowerModel())
|
||||
|
||||
def OnData(self, slice):
|
||||
if self.Portfolio.Invested:
|
||||
return
|
||||
|
||||
quantity = self.CalculateOrderQuantity(self.spy, 1)
|
||||
if quantity % 100 != 0:
|
||||
raise Exception(f'CustomBuyingPowerModel only allow quantity that is multiple of 100 and {quantity} was found')
|
||||
|
||||
# We normally get insufficient buying power model, but the
|
||||
# CustomBuyingPowerModel always says that there is sufficient buying power for the orders
|
||||
self.MarketOrder(self.spy, quantity * 10)
|
||||
|
||||
|
||||
class CustomBuyingPowerModel(BuyingPowerModel):
|
||||
def GetMaximumOrderQuantityForTargetBuyingPower(self, parameters):
|
||||
quantity = super().GetMaximumOrderQuantityForTargetBuyingPower(parameters).Quantity
|
||||
quantity = np.floor(quantity / 100) * 100
|
||||
return GetMaximumOrderQuantityResult(quantity)
|
||||
|
||||
def HasSufficientBuyingPowerForOrder(self, parameters):
|
||||
return HasSufficientBuyingPowerForOrderResult(True)
|
||||
@@ -13,19 +13,18 @@
|
||||
|
||||
from clr import AddReference
|
||||
AddReference("System")
|
||||
AddReference("QuantConnect.Common")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
AddReference("QuantConnect.Indicators")
|
||||
AddReference("QuantConnect.Common")
|
||||
|
||||
from System import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Python import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Algorithm.Framework.Selection import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from QuantConnect.Data.Consolidators import *
|
||||
from QuantConnect.Indicators import *
|
||||
from QuantConnect.Data.Market import *
|
||||
from System import *
|
||||
from datetime import *
|
||||
|
||||
class CustomConsolidatorRegressionAlgorithm(QCAlgorithm):
|
||||
|
||||
@@ -21,6 +21,7 @@ from QuantConnect import *
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Algorithm.Framework.Selection import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.Custom import *
|
||||
from QuantConnect.Data.Custom.SEC import *
|
||||
from QuantConnect.Data.UniverseSelection import *
|
||||
|
||||
|
||||
@@ -27,16 +27,17 @@ import numpy as np
|
||||
import random
|
||||
|
||||
### <summary>
|
||||
### Demonstration of using custom fee, slippage and fill models for modelling transactions in backtesting.
|
||||
### Demonstration of using custom fee, slippage, fill, and buying power models for modelling transactions in backtesting.
|
||||
### QuantConnect allows you to model all orders as deeply and accurately as you need.
|
||||
### </summary>
|
||||
### <meta name="tag" content="trading and orders" />
|
||||
### <meta name="tag" content="transaction fees and slippage" />
|
||||
### <meta name="tag" content="custom buying power models" />
|
||||
### <meta name="tag" content="custom transaction models" />
|
||||
### <meta name="tag" content="custom slippage models" />
|
||||
### <meta name="tag" content="custom fee models" />
|
||||
class CustomModelsAlgorithm(QCAlgorithm):
|
||||
'''Demonstration of using custom fee, slippage and fill models for modelling transactions in backtesting.
|
||||
'''Demonstration of using custom fee, slippage, fill, and buying power models for modelling transactions in backtesting.
|
||||
QuantConnect allows you to model all orders as deeply and accurately as you need.'''
|
||||
|
||||
def Initialize(self):
|
||||
@@ -49,6 +50,7 @@ class CustomModelsAlgorithm(QCAlgorithm):
|
||||
self.security.SetFeeModel(CustomFeeModel(self))
|
||||
self.security.SetFillModel(CustomFillModel(self))
|
||||
self.security.SetSlippageModel(CustomSlippageModel(self))
|
||||
self.security.SetBuyingPowerModel(CustomBuyingPowerModel(self))
|
||||
|
||||
|
||||
def OnData(self, data):
|
||||
@@ -57,12 +59,12 @@ class CustomModelsAlgorithm(QCAlgorithm):
|
||||
|
||||
if self.Time.day > 10 and self.security.Holdings.Quantity <= 0:
|
||||
quantity = self.CalculateOrderQuantity(self.spy, .5)
|
||||
self.Log("MarketOrder: " + str(quantity))
|
||||
self.Log(f"MarketOrder: {quantity}")
|
||||
self.MarketOrder(self.spy, quantity, True) # async needed for partial fill market orders
|
||||
|
||||
elif self.Time.day > 20 and self.security.Holdings.Quantity >= 0:
|
||||
quantity = self.CalculateOrderQuantity(self.spy, -.5)
|
||||
self.Log("MarketOrder: " + str(quantity))
|
||||
self.Log(f"MarketOrder: {quantity}")
|
||||
self.MarketOrder(self.spy, quantity, True) # async needed for partial fill market orders
|
||||
|
||||
# If we want to use methods from other models, you need to inherit from one of them
|
||||
@@ -90,7 +92,7 @@ class CustomFillModel(ImmediateFillModel):
|
||||
absoluteRemaining = absoluteRemaining - absoluteFillQuantity
|
||||
self.absoluteRemainingByOrderId[order.Id] = absoluteRemaining
|
||||
fill.Status = OrderStatus.PartiallyFilled
|
||||
self.algorithm.Log("CustomFillModel: " + str(fill))
|
||||
self.algorithm.Log(f"CustomFillModel: {fill}")
|
||||
return fill
|
||||
|
||||
class CustomFeeModel(FeeModel):
|
||||
@@ -102,7 +104,7 @@ class CustomFeeModel(FeeModel):
|
||||
fee = max(1, parameters.Security.Price
|
||||
* parameters.Order.AbsoluteQuantity
|
||||
* 0.00001)
|
||||
self.algorithm.Log("CustomFeeModel: " + str(fee))
|
||||
self.algorithm.Log(f"CustomFeeModel: {fee}")
|
||||
return OrderFee(CashAmount(fee, "USD"))
|
||||
|
||||
class CustomSlippageModel:
|
||||
@@ -112,5 +114,15 @@ class CustomSlippageModel:
|
||||
def GetSlippageApproximation(self, asset, order):
|
||||
# custom slippage math
|
||||
slippage = asset.Price * 0.0001 * np.log10(2*float(order.AbsoluteQuantity))
|
||||
self.algorithm.Log("CustomSlippageModel: " + str(slippage))
|
||||
return slippage
|
||||
self.algorithm.Log(f"CustomSlippageModel: {slippage}")
|
||||
return slippage
|
||||
|
||||
class CustomBuyingPowerModel(BuyingPowerModel):
|
||||
def __init__(self, algorithm):
|
||||
self.algorithm = algorithm
|
||||
|
||||
def HasSufficientBuyingPowerForOrder(self, parameters):
|
||||
# custom behavior: this model will assume that there is always enough buying power
|
||||
hasSufficientBuyingPowerForOrderResult = HasSufficientBuyingPowerForOrderResult(True)
|
||||
self.algorithm.Log(f"CustomBuyingPowerModel: {hasSufficientBuyingPowerForOrderResult.IsSufficient}")
|
||||
return hasSufficientBuyingPowerForOrderResult
|
||||
@@ -12,15 +12,15 @@
|
||||
# limitations under the License.
|
||||
|
||||
from clr import AddReference
|
||||
AddReference("System.Core")
|
||||
AddReference("QuantConnect.Common")
|
||||
AddReference("System")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
AddReference("QuantConnect.Algorithm.Framework")
|
||||
AddReference("QuantConnect.Common")
|
||||
|
||||
from System import *
|
||||
from QuantConnect import *
|
||||
from QuantConnect.Algorithm import QCAlgorithm
|
||||
from QuantConnect.Algorithm import *
|
||||
from QuantConnect.Algorithm.Framework.Selection import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Data.UniverseSelection import *
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
from clr import AddReference
|
||||
AddReference("System")
|
||||
AddReference("QuantConnect.Algorithm")
|
||||
AddReference("QuantConnect.Algorithm.Framework")
|
||||
AddReference("QuantConnect.Common")
|
||||
|
||||
from QuantConnect import *
|
||||
@@ -24,7 +23,6 @@ from QuantConnect.Algorithm.Framework.Execution import *
|
||||
from QuantConnect.Algorithm.Framework.Portfolio import *
|
||||
from QuantConnect.Algorithm.Framework.Selection import *
|
||||
from QuantConnect.Brokerages import *
|
||||
from QuantConnect.Data import *
|
||||
from QuantConnect.Interfaces import *
|
||||
from QuantConnect.Orders import *
|
||||
from System import *
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
<ItemGroup>
|
||||
<Content Include="AccumulativeInsightPortfolioRegressionAlgorithm.py" />
|
||||
<Content Include="AddAlphaModelAlgorithm.py" />
|
||||
<Content Include="AddOptionContractExpiresRegressionAlgorithm.py" />
|
||||
<Content Include="AddOptionContractFromUniverseRegressionAlgorithm.py" />
|
||||
<Content Include="AddRiskManagementAlgorithm.py" />
|
||||
<Content Include="AddUniverseSelectionModelAlgorithm.py" />
|
||||
<Content Include="Alphas\ContingentClaimsAnalysisDefaultPredictionAlpha.py" />
|
||||
@@ -63,6 +65,7 @@
|
||||
<Content Include="Alphas\VIXDualThrustAlpha.py" />
|
||||
<Content Include="AltData\CachedAlternativeDataAlgorithm.py" />
|
||||
<Content Include="AltData\BenzingaNewsAlgorithm.py" />
|
||||
<Content Include="AltData\QuiverWallStreetBetsDataAlgorithm.py" />
|
||||
<Content Include="AltData\SECReport8KAlgorithm.py" />
|
||||
<Content Include="AltData\SmartInsiderTransactionAlgorithm.py" />
|
||||
<Content Include="AltData\USTreasuryYieldCurveRateAlgorithm.py" />
|
||||
@@ -83,11 +86,13 @@
|
||||
<None Include="BasicTemplateOptionsPriceModel.py" />
|
||||
<Content Include="Benchmarks\SECReportBenchmarkAlgorithm.py" />
|
||||
<Content Include="Benchmarks\SmartInsiderEventBenchmarkAlgorithm.py" />
|
||||
<Content Include="CoarseFineOptionUniverseChainRegressionAlgorithm.py" />
|
||||
<Content Include="CoarseTiingoNewsUniverseSelectionAlgorithm.py" />
|
||||
<Content Include="ConsolidateRegressionAlgorithm.py" />
|
||||
<Content Include="CustomConsolidatorRegressionAlgorithm.py" />
|
||||
<Content Include="CustomDataAddDataOnSecuritiesChangedRegressionAlgorithm.py" />
|
||||
<Content Include="CustomDataAddDataCoarseSelectionRegressionAlgorithm.py" />
|
||||
<None Include="CustomBuyingPowerModelAlgorithm.py" />
|
||||
<Content Include="DynamicSecurityDataAlgorithm.py" />
|
||||
<Content Include="ConfidenceWeightedFrameworkAlgorithm.py" />
|
||||
<Content Include="ExtendedMarketTradingRegressionAlgorithm.py" />
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<Compile Include="ConstituentsQC500GeneratorAlgorithm.py" />
|
||||
<Compile Include="ConstituentsUniverseRegressionAlgorithm.py" />
|
||||
<Compile Include="ConvertToFrameworkAlgorithm.py" />
|
||||
<Compile Include="CustomBuyingPowerModelAlgorithm.py" />
|
||||
<Compile Include="CustomDataAddDataCoarseSelectionRegressionAlgorithm.py" />
|
||||
<Compile Include="CustomDataAddDataOnSecuritiesChangedRegressionAlgorithm.py" />
|
||||
<Compile Include="CustomDataAddDataRegressionAlgorithm.py" />
|
||||
|
||||
@@ -24,16 +24,14 @@ Before we enable python support, follow the [installation instructions](https://
|
||||
3. Install [wrapt=1.11.2](https://pypi.org/project/wrapt/) module.
|
||||
|
||||
*Note:* If you encounter the "System.DllNotFoundException: python3.6m" runtime error when running Python algorithms on macOS:
|
||||
1. Find `libpython3.6m.dylib` in your Python installation folder. If you installed Python with Anaconda, it may be find at
|
||||
```
|
||||
/Users/{your_user_name}/anaconda3/lib/libpython3.6m.dylib
|
||||
```
|
||||
2. Open `Lean/Launcher/bin/Debug/Python.Runtime.dll.config`, add the following text and save:
|
||||
```
|
||||
<configuration>
|
||||
<dllmap dll="python3.6m" target="{the path in step 1 including libpython3.6m.dylib}" os="!windows"/>
|
||||
</configuration>
|
||||
```
|
||||
1. Find `libpython3.6m.dylib` in your Python installation folder. If you installed Python with Anaconda, it may be found at
|
||||
```
|
||||
/Users/{your_user_name}/anaconda3/lib/libpython3.6m.dylib
|
||||
```
|
||||
2. Open `Lean/Launcher/bin/Debug/Python.Runtime.dll.config`, add the following text under `<configuration> ... </configuration>` and save:
|
||||
```
|
||||
<dllmap dll="python3.6m" target="{the path in step 1 including libpython3.6m.dylib}" os="osx"/>
|
||||
```
|
||||
Note: Specify the install of v3.6.8 _exactly_, i.e. if with conda `conda install python=3.6.8` as this is a known compatible version and other versions may have issues as of this writing.
|
||||
|
||||
#### [Linux](https://github.com/QuantConnect/Lean#linux-debian-ubuntu)
|
||||
@@ -50,18 +48,32 @@ conda install -y pandas=0.25.3
|
||||
conda install -y wrapt=1.11.2
|
||||
```
|
||||
|
||||
*Note:* There is a [known issue](https://github.com/pythonnet/pythonnet/issues/609) with python 3.6.5 that prevents pythonnet installation, please upgrade python to version 3.6.8:
|
||||
```
|
||||
conda install -y python=3.6.8
|
||||
```
|
||||
*Note 1:* There is a [known issue](https://github.com/pythonnet/pythonnet/issues/609) with python 3.6.5 that prevents pythonnet installation, please upgrade python to version 3.6.8:
|
||||
```
|
||||
conda install -y python=3.6.8
|
||||
```
|
||||
|
||||
*Note 2:* If you encounter the "System.DllNotFoundException: python3.6m" runtime error when running Python algorithms on Linux:
|
||||
1. Find `libpython3.6m.so` in your Python installation folder. If you installed Python with Miniconda, it may be found at
|
||||
```
|
||||
/home/{your_user_name}/miniconda3/envs/{qc_environment}/lib/libpython3.6m.so
|
||||
```
|
||||
Note that you can create a new virtual environment with all required dependencies by executing:
|
||||
```
|
||||
conda create -n qc_environment python=3.6.8 cython=0.29.11 pandas=0.25.3 wrapt=1.11.2
|
||||
|
||||
```
|
||||
2. Open `Lean/Launcher/bin/Debug/Python.Runtime.dll.config`, add the following text under `<configuration> ... </configuration>` and save:
|
||||
```
|
||||
<dllmap dll="python3.6m" target="{the path in step 1 including libpython3.6m.so}" os="linux"/>
|
||||
```
|
||||
### Run python algorithm
|
||||
1. Update the [config](https://github.com/QuantConnect/Lean/blob/master/Launcher/config.json) to run the python algorithm:
|
||||
```json
|
||||
"algorithm-type-name": "BasicTemplateAlgorithm",
|
||||
"algorithm-language": "Python",
|
||||
"algorithm-location": "../../../Algorithm.Python/BasicTemplateAlgorithm.py",
|
||||
```
|
||||
```json
|
||||
"algorithm-type-name": "BasicTemplateAlgorithm",
|
||||
"algorithm-language": "Python",
|
||||
"algorithm-location": "../../../Algorithm.Python/BasicTemplateAlgorithm.py",
|
||||
```
|
||||
2. Rebuild LEAN.
|
||||
3. Run LEAN. You should see the same result of the C# algorithm you tested earlier.
|
||||
|
||||
|
||||
@@ -69,9 +69,20 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
public void FrameworkPostInitialize()
|
||||
{
|
||||
//Prevents execution in the case of cash brokerage with IExecutionModel and IPortfolioConstructionModel
|
||||
if (PortfolioConstruction.GetType() != typeof(NullPortfolioConstructionModel)
|
||||
&& Execution.GetType() != typeof(NullExecutionModel)
|
||||
&& BrokerageModel.AccountType == AccountType.Cash)
|
||||
{
|
||||
throw new InvalidOperationException($"Non null {nameof(IExecutionModel)} and {nameof(IPortfolioConstructionModel)} are currently unsuitable for Cash Modeled brokerages (e.g. GDAX) and may result in unexpected trades."
|
||||
+ " To prevent possible user error we've restricted them to Margin trading. You can select margin account types with"
|
||||
+ $" SetBrokerage( ... AccountType.Margin). Or please set them to {nameof(NullExecutionModel)}, {nameof(NullPortfolioConstructionModel)}");
|
||||
}
|
||||
foreach (var universe in UniverseSelection.CreateUniverses(this))
|
||||
{
|
||||
AddUniverse(universe);
|
||||
// on purpose we don't call 'AddUniverse' here so that these universes don't get registered as user added
|
||||
// this is so that later during 'UniverseSelection.CreateUniverses' we wont remove them from UniverseManager
|
||||
_pendingUniverseAdditions.Add(universe);
|
||||
}
|
||||
|
||||
if (DebugMode)
|
||||
@@ -94,8 +105,7 @@ namespace QuantConnect.Algorithm
|
||||
foreach (var ukvp in UniverseManager)
|
||||
{
|
||||
var universeSymbol = ukvp.Key;
|
||||
var qcUserDefined = UserDefinedUniverse.CreateSymbol(ukvp.Value.SecurityType, ukvp.Value.Market);
|
||||
if (universeSymbol.Equals(qcUserDefined))
|
||||
if (_userAddedUniverses.Contains(universeSymbol))
|
||||
{
|
||||
// prevent removal of qc algorithm created user defined universes
|
||||
continue;
|
||||
@@ -216,16 +226,7 @@ namespace QuantConnect.Algorithm
|
||||
Log($"{Time}: RISK ADJUSTED TARGETS: {string.Join(" | ", riskAdjustedTargets.Select(t => t.ToString()).OrderBy(t => t))}");
|
||||
}
|
||||
}
|
||||
|
||||
if (riskAdjustedTargets.Length > 0
|
||||
&& Execution.GetType() != typeof(NullExecutionModel)
|
||||
&& BrokerageModel.AccountType == AccountType.Cash)
|
||||
{
|
||||
throw new InvalidOperationException($"Non null {nameof(IExecutionModel)} and {nameof(IPortfolioConstructionModel)} are currently unsuitable for Cash Modeled brokerages (e.g. GDAX) and may result in unexpected trades."
|
||||
+ " To prevent possible user error we've restricted them to Margin trading. You can select margin account types with"
|
||||
+ $" SetBrokerage( ... AccountType.Margin). Or please set them to {nameof(NullExecutionModel)}, {nameof(NullPortfolioConstructionModel)}");
|
||||
}
|
||||
|
||||
|
||||
Execution.Execute(this, riskAdjustedTargets);
|
||||
}
|
||||
|
||||
|
||||
@@ -696,18 +696,21 @@ namespace QuantConnect.Algorithm
|
||||
|
||||
private SecurityExchangeHours GetExchangeHours(Symbol symbol)
|
||||
{
|
||||
Security security;
|
||||
if (Securities.TryGetValue(symbol, out security))
|
||||
{
|
||||
return security.Exchange.Hours;
|
||||
}
|
||||
|
||||
return GetMarketHours(symbol).ExchangeHours;
|
||||
}
|
||||
|
||||
private MarketHoursDatabase.Entry GetMarketHours(Symbol symbol)
|
||||
{
|
||||
return MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
|
||||
var hoursEntry = MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
|
||||
|
||||
// user can override the exchange hours in algorithm, i.e. HistoryAlgorithm
|
||||
Security security;
|
||||
if (Securities.TryGetValue(symbol, out security))
|
||||
{
|
||||
return new MarketHoursDatabase.Entry(hoursEntry.DataTimeZone, security.Exchange.Hours);
|
||||
}
|
||||
|
||||
return hoursEntry;
|
||||
}
|
||||
|
||||
private Resolution GetResolution(Symbol symbol, Resolution? resolution)
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace QuantConnect.Algorithm
|
||||
{
|
||||
foreach (var indicator in indicators)
|
||||
{
|
||||
Plot(chart, indicator.Name, indicator);
|
||||
Plot(chart, indicator.Name, indicator.Current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,22 +230,22 @@ namespace QuantConnect.Algorithm
|
||||
/// will be executed on day changes in the NewYork time zone (<see cref="TimeZones.NewYork"/>
|
||||
/// </summary>
|
||||
/// <param name="pyObject">Defines an initial coarse selection</param>
|
||||
public void AddUniverse(PyObject pyObject)
|
||||
public Universe AddUniverse(PyObject pyObject)
|
||||
{
|
||||
Func<IEnumerable<CoarseFundamental>, object> coarseFunc;
|
||||
Universe universe;
|
||||
|
||||
if (pyObject.TryConvert(out universe))
|
||||
{
|
||||
AddUniverse(universe);
|
||||
return AddUniverse(universe);
|
||||
}
|
||||
else if (pyObject.TryConvert(out universe, allowPythonDerivative: true))
|
||||
{
|
||||
AddUniverse(new UniversePythonWrapper(pyObject));
|
||||
return AddUniverse(new UniversePythonWrapper(pyObject));
|
||||
}
|
||||
else if (pyObject.TryConvertToDelegate(out coarseFunc))
|
||||
{
|
||||
AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate());
|
||||
return AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -262,7 +262,7 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="pyObject">Defines an initial coarse selection or a universe</param>
|
||||
/// <param name="pyfine">Defines a more detailed selection with access to more data</param>
|
||||
public void AddUniverse(PyObject pyObject, PyObject pyfine)
|
||||
public Universe AddUniverse(PyObject pyObject, PyObject pyfine)
|
||||
{
|
||||
Func<IEnumerable<CoarseFundamental>, object> coarseFunc;
|
||||
Func<IEnumerable<FineFundamental>, object> fineFunc;
|
||||
@@ -270,11 +270,11 @@ namespace QuantConnect.Algorithm
|
||||
|
||||
if (pyObject.TryConvert(out universe) && pyfine.TryConvertToDelegate(out fineFunc))
|
||||
{
|
||||
AddUniverse(universe, fineFunc.ConvertToUniverseSelectionSymbolDelegate());
|
||||
return AddUniverse(universe, fineFunc.ConvertToUniverseSelectionSymbolDelegate());
|
||||
}
|
||||
else if (pyObject.TryConvertToDelegate(out coarseFunc) && pyfine.TryConvertToDelegate(out fineFunc))
|
||||
{
|
||||
AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate(),
|
||||
return AddUniverse(coarseFunc.ConvertToUniverseSelectionSymbolDelegate(),
|
||||
fineFunc.ConvertToUniverseSelectionSymbolDelegate());
|
||||
}
|
||||
else
|
||||
@@ -293,10 +293,10 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="resolution">The resolution this universe should be triggered on</param>
|
||||
/// <param name="pySelector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
|
||||
public void AddUniverse(string name, Resolution resolution, PyObject pySelector)
|
||||
public Universe AddUniverse(string name, Resolution resolution, PyObject pySelector)
|
||||
{
|
||||
var selector = pySelector.ConvertToDelegate<Func<DateTime, object>>();
|
||||
AddUniverse(name, resolution, selector.ConvertToUniverseSelectionStringDelegate());
|
||||
return AddUniverse(name, resolution, selector.ConvertToUniverseSelectionStringDelegate());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -305,10 +305,10 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="pySelector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
|
||||
public void AddUniverse(string name, PyObject pySelector)
|
||||
public Universe AddUniverse(string name, PyObject pySelector)
|
||||
{
|
||||
var selector = pySelector.ConvertToDelegate<Func<DateTime, object>>();
|
||||
AddUniverse(name, selector.ConvertToUniverseSelectionStringDelegate());
|
||||
return AddUniverse(name, selector.ConvertToUniverseSelectionStringDelegate());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -320,10 +320,10 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="market">The market of the universe</param>
|
||||
/// <param name="universeSettings">The subscription settings used for securities added from this universe</param>
|
||||
/// <param name="pySelector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
|
||||
public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
|
||||
public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
|
||||
{
|
||||
var selector = pySelector.ConvertToDelegate<Func<DateTime, object>>();
|
||||
AddUniverse(securityType, name, resolution, market, universeSettings, selector.ConvertToUniverseSelectionStringDelegate());
|
||||
return AddUniverse(securityType, name, resolution, market, universeSettings, selector.ConvertToUniverseSelectionStringDelegate());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -334,9 +334,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="T">The data type</param>
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse(PyObject T, string name, PyObject selector)
|
||||
public Universe AddUniverse(PyObject T, string name, PyObject selector)
|
||||
{
|
||||
AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -348,9 +348,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse(PyObject T, string name, Resolution resolution, PyObject selector)
|
||||
public Universe AddUniverse(PyObject T, string name, Resolution resolution, PyObject selector)
|
||||
{
|
||||
AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -363,9 +363,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="universeSettings">The settings used for securities added by this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse(PyObject T, string name, Resolution resolution, UniverseSettings universeSettings, PyObject selector)
|
||||
public Universe AddUniverse(PyObject T, string name, Resolution resolution, UniverseSettings universeSettings, PyObject selector)
|
||||
{
|
||||
AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
|
||||
return AddUniverse(T.CreateType(), SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -377,9 +377,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="universeSettings">The settings used for securities added by this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse(PyObject T, string name, UniverseSettings universeSettings, PyObject selector)
|
||||
public Universe AddUniverse(PyObject T, string name, UniverseSettings universeSettings, PyObject selector)
|
||||
{
|
||||
AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
|
||||
return AddUniverse(T.CreateType(), SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -392,9 +392,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="market">The market for selected symbols</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, PyObject selector)
|
||||
public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, PyObject selector)
|
||||
{
|
||||
AddUniverse(T.CreateType(), securityType, name, resolution, market, UniverseSettings, selector);
|
||||
return AddUniverse(T.CreateType(), securityType, name, resolution, market, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -407,9 +407,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="market">The market for selected symbols</param>
|
||||
/// <param name="universeSettings">The subscription settings to use for newly created subscriptions</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject selector)
|
||||
public Universe AddUniverse(PyObject T, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject selector)
|
||||
{
|
||||
AddUniverse(T.CreateType(), securityType, name, resolution, market, universeSettings, selector);
|
||||
return AddUniverse(T.CreateType(), securityType, name, resolution, market, universeSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -422,7 +422,7 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="market">The market for selected symbols</param>
|
||||
/// <param name="universeSettings">The subscription settings to use for newly created subscriptions</param>
|
||||
/// <param name="pySelector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse(Type dataType, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
|
||||
public Universe AddUniverse(Type dataType, SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, PyObject pySelector)
|
||||
{
|
||||
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
|
||||
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
|
||||
@@ -432,7 +432,7 @@ namespace QuantConnect.Algorithm
|
||||
|
||||
var selector = pySelector.ConvertToDelegate<Func<IEnumerable<IBaseData>, object>>();
|
||||
|
||||
AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, baseDatas =>
|
||||
return AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, baseDatas =>
|
||||
{
|
||||
var result = selector(baseDatas);
|
||||
return ReferenceEquals(result, Universe.Unchanged)
|
||||
@@ -442,6 +442,30 @@ namespace QuantConnect.Algorithm
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new universe selection model and adds it to the algorithm. This universe selection model will chain to the security
|
||||
/// changes of a given <see cref="Universe"/> selection output and create a new <see cref="OptionChainUniverse"/> for each of them
|
||||
/// </summary>
|
||||
/// <param name="universe">The universe we want to chain an option universe selection model too</param>
|
||||
/// <param name="optionFilter">The option filter universe to use</param>
|
||||
public void AddUniverseOptions(PyObject universe, PyObject optionFilter)
|
||||
{
|
||||
Func<OptionFilterUniverse, OptionFilterUniverse> convertedOptionChain;
|
||||
Universe universeToChain;
|
||||
|
||||
if (universe.TryConvert(out universeToChain) && optionFilter.TryConvertToDelegate(out convertedOptionChain))
|
||||
{
|
||||
AddUniverseOptions(universeToChain, convertedOptionChain);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Py.GIL())
|
||||
{
|
||||
throw new ArgumentException($"QCAlgorithm.AddChainedEquityOptionUniverseSelectionModel: {universe.Repr()} or {optionFilter.Repr()} is not a valid argument.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the consolidator to receive automatic updates as well as configures the indicator to receive updates
|
||||
/// from the consolidator.
|
||||
@@ -780,7 +804,7 @@ namespace QuantConnect.Algorithm
|
||||
var res = GetResolution(x, resolution);
|
||||
var exchange = GetExchangeHours(x);
|
||||
var start = _historyRequestFactory.GetStartTimeAlgoTz(x, periods, res, exchange, config.DataTimeZone);
|
||||
return _historyRequestFactory.CreateHistoryRequest(config, start, Time.RoundDown(res.ToTimeSpan()), exchange, res);
|
||||
return _historyRequestFactory.CreateHistoryRequest(config, start, Time, exchange, res);
|
||||
});
|
||||
|
||||
return PandasConverter.GetDataFrame(History(requests.Where(x => x != null)).Memoize());
|
||||
@@ -843,8 +867,7 @@ namespace QuantConnect.Algorithm
|
||||
var res = GetResolution(symbol, resolution);
|
||||
var marketHours = GetMarketHours(symbol);
|
||||
var start = _historyRequestFactory.GetStartTimeAlgoTz(symbol, periods, res, marketHours.ExchangeHours, marketHours.DataTimeZone);
|
||||
var end = Time.RoundDown(res.ToTimeSpan());
|
||||
return History(type, symbol, start, end, resolution);
|
||||
return History(type, symbol, start, Time, resolution);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -488,9 +488,11 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="tag">String tag for the order (optional)</param>
|
||||
public OrderTicket ExerciseOption(Symbol optionSymbol, int quantity, bool asynchronous = false, string tag = "")
|
||||
{
|
||||
var option = (Option)Securities[optionSymbol];
|
||||
var option = (Option) Securities[optionSymbol];
|
||||
|
||||
var request = CreateSubmitOrderRequest(OrderType.OptionExercise, option, quantity, tag, DefaultOrderProperties?.Clone());
|
||||
// SubmitOrderRequest.Quantity indicates the change in holdings quantity, therefore manual exercise quantities must be negative
|
||||
// PreOrderChecksImpl confirms that we don't hold a short position, so we're lenient here and accept +/- quantity values
|
||||
var request = CreateSubmitOrderRequest(OrderType.OptionExercise, option, -Math.Abs(quantity), tag, DefaultOrderProperties?.Clone());
|
||||
|
||||
// If warming up, do not submit
|
||||
if (IsWarmingUp)
|
||||
@@ -657,7 +659,9 @@ namespace QuantConnect.Algorithm
|
||||
Security security;
|
||||
if (!Securities.TryGetValue(request.Symbol, out security))
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity, "You haven't requested " + request.Symbol.ToString() + " data. Add this with AddSecurity() in the Initialize() Method.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity,
|
||||
$"You haven't requested {request.Symbol} data. Add this with AddSecurity() in the Initialize() Method."
|
||||
);
|
||||
}
|
||||
|
||||
//Ordering 0 is useless.
|
||||
@@ -677,7 +681,9 @@ namespace QuantConnect.Algorithm
|
||||
|
||||
if (!security.IsTradable)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.NonTradableSecurity, "The security with symbol '" + request.Symbol.ToString() + "' is marked as non-tradable.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.NonTradableSecurity,
|
||||
$"The security with symbol '{request.Symbol}' is marked as non-tradable."
|
||||
);
|
||||
}
|
||||
|
||||
var price = security.Price;
|
||||
@@ -685,13 +691,17 @@ namespace QuantConnect.Algorithm
|
||||
//Check the exchange is open before sending a market on close orders
|
||||
if (request.OrderType == OrderType.MarketOnClose && !security.Exchange.ExchangeOpen)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen,
|
||||
$"{request.OrderType} order and exchange not open."
|
||||
);
|
||||
}
|
||||
|
||||
//Check the exchange is open before sending a exercise orders
|
||||
if (request.OrderType == OrderType.OptionExercise && !security.Exchange.ExchangeOpen)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen,
|
||||
$"{request.OrderType} order and exchange not open."
|
||||
);
|
||||
}
|
||||
|
||||
if (price == 0)
|
||||
@@ -704,11 +714,15 @@ namespace QuantConnect.Algorithm
|
||||
var quoteCurrency = security.QuoteCurrency.Symbol;
|
||||
if (!Portfolio.CashBook.TryGetValue(quoteCurrency, out quoteCash))
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.QuoteCurrencyRequired, request.Symbol.Value + ": requires " + quoteCurrency + " in the cashbook to trade.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.QuoteCurrencyRequired,
|
||||
$"{request.Symbol.Value}: requires {quoteCurrency} in the cashbook to trade."
|
||||
);
|
||||
}
|
||||
if (security.QuoteCurrency.ConversionRate == 0m)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ConversionRateZero, request.Symbol.Value + ": requires " + quoteCurrency + " to have a non-zero conversion rate. This can be caused by lack of data.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ConversionRateZero,
|
||||
$"{request.Symbol.Value}: requires {quoteCurrency} to have a non-zero conversion rate. This can be caused by lack of data."
|
||||
);
|
||||
}
|
||||
|
||||
// need to also check base currency existence/conversion rate on forex orders
|
||||
@@ -718,18 +732,24 @@ namespace QuantConnect.Algorithm
|
||||
var baseCurrency = ((IBaseCurrencySymbol)security).BaseCurrencySymbol;
|
||||
if (!Portfolio.CashBook.TryGetValue(baseCurrency, out baseCash))
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " in the cashbook to trade.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired,
|
||||
$"{request.Symbol.Value}: requires {baseCurrency} and {quoteCurrency} in the cashbook to trade."
|
||||
);
|
||||
}
|
||||
if (baseCash.ConversionRate == 0m)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " to have non-zero conversion rates. This can be caused by lack of data.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero,
|
||||
$"{request.Symbol.Value}: requires {baseCurrency} and {quoteCurrency} to have non-zero conversion rates. This can be caused by lack of data."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Make sure the security has some data:
|
||||
if (!security.HasData)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData, "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData,
|
||||
"There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point."
|
||||
);
|
||||
}
|
||||
|
||||
// We've already processed too many orders: max 10k
|
||||
@@ -737,28 +757,38 @@ namespace QuantConnect.Algorithm
|
||||
{
|
||||
Status = AlgorithmStatus.Stopped;
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.ExceededMaximumOrders,
|
||||
$"You have exceeded maximum number of orders ({_maxOrders.ToStringInvariant()}), for unlimited orders upgrade your account."
|
||||
Invariant($"You have exceeded maximum number of orders ({_maxOrders}), for unlimited orders upgrade your account.")
|
||||
);
|
||||
}
|
||||
|
||||
if (request.OrderType == OrderType.OptionExercise)
|
||||
{
|
||||
if (security.Type != SecurityType.Option)
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.NonExercisableSecurity, "The security with symbol '" + request.Symbol.ToString() + "' is not exercisable.");
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.NonExercisableSecurity,
|
||||
$"The security with symbol '{request.Symbol}' is not exercisable."
|
||||
);
|
||||
}
|
||||
|
||||
if (security.Holdings.IsShort)
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType, "The security with symbol '" + request.Symbol.ToString() + "' has a short option position. Only long option positions are exercisable.");
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType,
|
||||
$"The security with symbol '{request.Symbol}' has a short option position. Only long option positions are exercisable."
|
||||
);
|
||||
}
|
||||
|
||||
if (request.Quantity > security.Holdings.Quantity)
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType, "Cannot exercise more contracts of '" + request.Symbol.ToString() + "' than is currently available in the portfolio. ");
|
||||
|
||||
if (request.Quantity <= 0.0m)
|
||||
OrderResponse.ZeroQuantity(request);
|
||||
if (Math.Abs(request.Quantity) > security.Holdings.Quantity)
|
||||
{
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType,
|
||||
$"Cannot exercise more contracts of '{request.Symbol}' than is currently available in the portfolio. "
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.OrderType == OrderType.MarketOnClose)
|
||||
{
|
||||
var nextMarketClose = security.Exchange.Hours.GetNextMarketClose(security.LocalTime, false);
|
||||
|
||||
// must be submitted with at least 10 minutes in trading day, add buffer allow order submission
|
||||
var latestSubmissionTime = nextMarketClose.Subtract(Orders.MarketOnCloseOrder.DefaultSubmissionTimeBuffer);
|
||||
if (!security.Exchange.ExchangeOpen || Time > latestSubmissionTime)
|
||||
@@ -766,7 +796,9 @@ namespace QuantConnect.Algorithm
|
||||
// tell the user we require a 16 minute buffer, on minute data in live a user will receive the 3:44->3:45 bar at 3:45,
|
||||
// this is already too late to submit one of these orders, so make the user do it at the 3:43->3:44 bar so it's submitted
|
||||
// to the brokerage before 3:45.
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate, "MarketOnClose orders must be placed with at least a 16 minute buffer before market close.");
|
||||
return OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate,
|
||||
"MarketOnClose orders must be placed with at least a 16 minute buffer before market close."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Algorithm.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
@@ -32,6 +33,8 @@ namespace QuantConnect.Algorithm
|
||||
private readonly object _pendingUniverseAdditionsLock = new object();
|
||||
private readonly List<UserDefinedUniverseAddition> _pendingUserDefinedUniverseSecurityAdditions = new List<UserDefinedUniverseAddition>();
|
||||
private readonly List<Universe> _pendingUniverseAdditions = new List<Universe>();
|
||||
// this is so that later during 'UniverseSelection.CreateUniverses' we wont remove these user universes from the UniverseManager
|
||||
private readonly HashSet<Symbol> _userAddedUniverses = new HashSet<Symbol>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets universe manager which holds universes keyed by their symbol
|
||||
@@ -179,11 +182,13 @@ namespace QuantConnect.Algorithm
|
||||
/// Adds the universe to the algorithm
|
||||
/// </summary>
|
||||
/// <param name="universe">The universe to be added</param>
|
||||
public void AddUniverse(Universe universe)
|
||||
public Universe AddUniverse(Universe universe)
|
||||
{
|
||||
// The universe will be added at the end of time step, same as the AddData user defined universes.
|
||||
// This is required to be independent of the start and end date set during initialize
|
||||
_pendingUniverseAdditions.Add(universe);
|
||||
_userAddedUniverses.Add(universe.Configuration.Symbol);
|
||||
return universe;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -194,9 +199,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <typeparam name="T">The data type</typeparam>
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
public Universe AddUniverse<T>(string name, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,9 +212,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <typeparam name="T">The data type</typeparam>
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse<T>(string name, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -221,9 +226,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="universeSettings">The settings used for securities added by this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
public Universe AddUniverse<T>(string name, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -235,9 +240,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="universeSettings">The settings used for securities added by this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse<T>(string name, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, universeSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,9 +254,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, Resolution resolution, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
public Universe AddUniverse<T>(string name, Resolution resolution, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -263,9 +268,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, Resolution resolution, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse<T>(string name, Resolution resolution, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -278,9 +283,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="universeSettings">The settings used for securities added by this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, Resolution resolution, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
public Universe AddUniverse<T>(string name, Resolution resolution, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -293,9 +298,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="universeSettings">The settings used for securities added by this universe</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(string name, Resolution resolution, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse<T>(string name, Resolution resolution, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, universeSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -308,9 +313,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="market">The market for selected symbols</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
public Universe AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
{
|
||||
AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
|
||||
return AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -323,9 +328,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="resolution">The epected resolution of the universe data</param>
|
||||
/// <param name="market">The market for selected symbols</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
{
|
||||
AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
|
||||
return AddUniverse(securityType, name, resolution, market, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -338,14 +343,14 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="market">The market for selected symbols</param>
|
||||
/// <param name="universeSettings">The subscription settings to use for newly created subscriptions</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
public Universe AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<Symbol>> selector)
|
||||
{
|
||||
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
|
||||
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
|
||||
var exchangeTimeZone = marketHoursDbEntry.ExchangeHours.TimeZone;
|
||||
var symbol = QuantConnect.Symbol.Create(name, securityType, market, baseDataType: typeof(T));
|
||||
var config = new SubscriptionDataConfig(typeof(T), symbol, resolution, dataTimeZone, exchangeTimeZone, false, false, true, true, isFilteredSubscription: false);
|
||||
AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, d => selector(d.OfType<T>())));
|
||||
return AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer, d => selector(d.OfType<T>())));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -358,14 +363,14 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="market">The market for selected symbols</param>
|
||||
/// <param name="universeSettings">The subscription settings to use for newly created subscriptions</param>
|
||||
/// <param name="selector">Function delegate that performs selection on the universe data</param>
|
||||
public void AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse<T>(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func<IEnumerable<T>, IEnumerable<string>> selector)
|
||||
{
|
||||
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
|
||||
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
|
||||
var exchangeTimeZone = marketHoursDbEntry.ExchangeHours.TimeZone;
|
||||
var symbol = QuantConnect.Symbol.Create(name, securityType, market, baseDataType: typeof(T));
|
||||
var config = new SubscriptionDataConfig(typeof(T), symbol, resolution, dataTimeZone, exchangeTimeZone, false, false, true, true, isFilteredSubscription: false);
|
||||
AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer,
|
||||
return AddUniverse(new FuncUniverse(config, universeSettings, SecurityInitializer,
|
||||
d => selector(d.OfType<T>()).Select(x => QuantConnect.Symbol.Create(x, securityType, market, baseDataType: typeof(T))))
|
||||
);
|
||||
}
|
||||
@@ -375,9 +380,9 @@ namespace QuantConnect.Algorithm
|
||||
/// will be executed on day changes in the NewYork time zone (<see cref="TimeZones.NewYork"/>
|
||||
/// </summary>
|
||||
/// <param name="selector">Defines an initial coarse selection</param>
|
||||
public void AddUniverse(Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> selector)
|
||||
public Universe AddUniverse(Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> selector)
|
||||
{
|
||||
AddUniverse(new CoarseFundamentalUniverse(UniverseSettings, SecurityInitializer, selector));
|
||||
return AddUniverse(new CoarseFundamentalUniverse(UniverseSettings, SecurityInitializer, selector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -386,11 +391,11 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="coarseSelector">Defines an initial coarse selection</param>
|
||||
/// <param name="fineSelector">Defines a more detailed selection with access to more data</param>
|
||||
public void AddUniverse(Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> coarseSelector, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> fineSelector)
|
||||
public Universe AddUniverse(Func<IEnumerable<CoarseFundamental>, IEnumerable<Symbol>> coarseSelector, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> fineSelector)
|
||||
{
|
||||
var coarse = new CoarseFundamentalUniverse(UniverseSettings, SecurityInitializer, coarseSelector);
|
||||
|
||||
AddUniverse(new FineFundamentalFilteredUniverse(coarse, fineSelector));
|
||||
return AddUniverse(new FineFundamentalFilteredUniverse(coarse, fineSelector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -399,9 +404,9 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="universe">The universe to be filtered with fine fundamental selection</param>
|
||||
/// <param name="fineSelector">Defines a more detailed selection with access to more data</param>
|
||||
public void AddUniverse(Universe universe, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> fineSelector)
|
||||
public Universe AddUniverse(Universe universe, Func<IEnumerable<FineFundamental>, IEnumerable<Symbol>> fineSelector)
|
||||
{
|
||||
AddUniverse(new FineFundamentalFilteredUniverse(universe, fineSelector));
|
||||
return AddUniverse(new FineFundamentalFilteredUniverse(universe, fineSelector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -410,9 +415,9 @@ namespace QuantConnect.Algorithm
|
||||
/// </summary>
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="selector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
|
||||
public void AddUniverse(string name, Func<DateTime, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse(string name, Func<DateTime, IEnumerable<string>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, Resolution.Daily, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -422,9 +427,9 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="name">A unique name for this universe</param>
|
||||
/// <param name="resolution">The resolution this universe should be triggered on</param>
|
||||
/// <param name="selector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
|
||||
public void AddUniverse(string name, Resolution resolution, Func<DateTime, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse(string name, Resolution resolution, Func<DateTime, IEnumerable<string>> selector)
|
||||
{
|
||||
AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
return AddUniverse(SecurityType.Equity, name, resolution, Market.USA, UniverseSettings, selector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -436,14 +441,25 @@ namespace QuantConnect.Algorithm
|
||||
/// <param name="market">The market of the universe</param>
|
||||
/// <param name="universeSettings">The subscription settings used for securities added from this universe</param>
|
||||
/// <param name="selector">Function delegate that accepts a DateTime and returns a collection of string symbols</param>
|
||||
public void AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func<DateTime, IEnumerable<string>> selector)
|
||||
public Universe AddUniverse(SecurityType securityType, string name, Resolution resolution, string market, UniverseSettings universeSettings, Func<DateTime, IEnumerable<string>> selector)
|
||||
{
|
||||
var marketHoursDbEntry = MarketHoursDatabase.GetEntry(market, name, securityType);
|
||||
var dataTimeZone = marketHoursDbEntry.DataTimeZone;
|
||||
var exchangeTimeZone = marketHoursDbEntry.ExchangeHours.TimeZone;
|
||||
var symbol = QuantConnect.Symbol.Create(name, securityType, market);
|
||||
var config = new SubscriptionDataConfig(typeof(CoarseFundamental), symbol, resolution, dataTimeZone, exchangeTimeZone, false, false, true, isFilteredSubscription: false);
|
||||
AddUniverse(new UserDefinedUniverse(config, universeSettings, resolution.ToTimeSpan(), selector));
|
||||
return AddUniverse(new UserDefinedUniverse(config, universeSettings, resolution.ToTimeSpan(), selector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new universe selection model and adds it to the algorithm. This universe selection model will chain to the security
|
||||
/// changes of a given <see cref="Universe"/> selection output and create a new <see cref="OptionChainUniverse"/> for each of them
|
||||
/// </summary>
|
||||
/// <param name="universe">The universe we want to chain an option universe selection model too</param>
|
||||
/// <param name="optionFilter">The option filter universe to use</param>
|
||||
public void AddUniverseOptions(Universe universe, Func<OptionFilterUniverse, OptionFilterUniverse> optionFilter)
|
||||
{
|
||||
AddUniverseSelection(new OptionChainedUniverseSelectionModel(universe, optionFilter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -501,7 +517,8 @@ namespace QuantConnect.Algorithm
|
||||
TimeSpan.Zero),
|
||||
QuantConnect.Time.MaxTimeSpan,
|
||||
new List<Symbol>());
|
||||
_pendingUniverseAdditions.Add(universe);
|
||||
|
||||
AddUniverse(universe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Algorithm.Selection;
|
||||
using QuantConnect.Storage;
|
||||
|
||||
namespace QuantConnect.Algorithm
|
||||
@@ -958,8 +959,19 @@ namespace QuantConnect.Algorithm
|
||||
// the time rules need to know the default time zone as well
|
||||
TimeRules.SetDefaultTimeZone(timeZone);
|
||||
|
||||
// reset the current time according to the time zone
|
||||
SetDateTime(_startDate.ConvertToUtc(TimeZone));
|
||||
// In BackTest mode we reset the Algorithm time to reflect the new timezone
|
||||
// startDate is set by the user so we expect it to be for their timezone already
|
||||
// so there is no need to update it.
|
||||
if (!LiveMode)
|
||||
{
|
||||
SetDateTime(_startDate.ConvertToUtc(TimeZone));
|
||||
}
|
||||
// In live mode we need to adjust startDate to reflect the new timezone
|
||||
// startDate is set by Lean to the default timezone (New York), so we must update it here
|
||||
else
|
||||
{
|
||||
_startDate = DateTime.UtcNow.ConvertFromUtc(TimeZone).Date;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1135,6 +1147,8 @@ namespace QuantConnect.Algorithm
|
||||
"Cannot change AccountCurrency after algorithm initialized.");
|
||||
}
|
||||
|
||||
Debug($"Changing account currency from {AccountCurrency} to {accountCurrency}...");
|
||||
|
||||
Portfolio.SetAccountCurrency(accountCurrency);
|
||||
}
|
||||
|
||||
@@ -1359,7 +1373,8 @@ namespace QuantConnect.Algorithm
|
||||
Securities.SetLiveMode(live);
|
||||
if (live)
|
||||
{
|
||||
_startDate = DateTime.Today;
|
||||
// startDate is set relative to the algorithm's timezone.
|
||||
_startDate = DateTime.UtcNow.ConvertFromUtc(TimeZone).Date;
|
||||
_endDate = QuantConnect.Time.EndOfTime;
|
||||
}
|
||||
}
|
||||
@@ -1497,7 +1512,8 @@ namespace QuantConnect.Algorithm
|
||||
{
|
||||
universe = new FuturesChainUniverse((Future)security, settings);
|
||||
}
|
||||
_pendingUniverseAdditions.Add(universe);
|
||||
|
||||
AddUniverse(universe);
|
||||
}
|
||||
return security;
|
||||
}
|
||||
@@ -1607,7 +1623,7 @@ namespace QuantConnect.Algorithm
|
||||
/// <returns>The new <see cref="Option"/> security</returns>
|
||||
public Option AddOptionContract(Symbol symbol, Resolution? resolution = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage)
|
||||
{
|
||||
var configs = SubscriptionManager.SubscriptionDataConfigService.Add(symbol, resolution, fillDataForward);
|
||||
var configs = SubscriptionManager.SubscriptionDataConfigService.Add(symbol, resolution, fillDataForward, dataNormalizationMode:DataNormalizationMode.Raw);
|
||||
var option = (Option)Securities.CreateSecurity(symbol, configs, leverage);
|
||||
|
||||
// add underlying if not present
|
||||
@@ -1641,8 +1657,26 @@ namespace QuantConnect.Algorithm
|
||||
equity.RefreshDataNormalizationModeProperty();
|
||||
|
||||
option.Underlying = equity;
|
||||
Securities.Add(option);
|
||||
|
||||
AddToUserDefinedUniverse(option, configs);
|
||||
// get or create the universe
|
||||
var universeSymbol = OptionContractUniverse.CreateSymbol(symbol.ID.Market, symbol.Underlying.SecurityType);
|
||||
Universe universe;
|
||||
if (!UniverseManager.TryGetValue(universeSymbol, out universe))
|
||||
{
|
||||
universe = _pendingUniverseAdditions.FirstOrDefault(u => u.Configuration.Symbol == universeSymbol)
|
||||
?? AddUniverse(new OptionContractUniverse(new SubscriptionDataConfig(configs.First(), symbol: universeSymbol), UniverseSettings));
|
||||
}
|
||||
|
||||
// update the universe
|
||||
var optionUniverse = universe as OptionContractUniverse;
|
||||
if (optionUniverse != null)
|
||||
{
|
||||
foreach (var subscriptionDataConfig in configs.Concat(underlyingConfigs))
|
||||
{
|
||||
optionUniverse.Add(subscriptionDataConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
@@ -1689,6 +1723,17 @@ namespace QuantConnect.Algorithm
|
||||
return AddSecurity<Crypto>(SecurityType.Crypto, ticker, resolution, market, fillDataForward, leverage, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the security with the specified symbol. This will cancel all
|
||||
/// open orders and then liquidate any existing holdings
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol of the security to be removed</param>
|
||||
/// <remarks>Sugar syntax for <see cref="AddOptionContract"/></remarks>
|
||||
public bool RemoveOptionContract(Symbol symbol)
|
||||
{
|
||||
return RemoveSecurity(symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the security with the specified symbol. This will cancel all
|
||||
/// open orders and then liquidate any existing holdings
|
||||
@@ -1744,6 +1789,7 @@ namespace QuantConnect.Algorithm
|
||||
|
||||
// finally, dispose and remove the canonical security from the universe manager
|
||||
UniverseManager.Remove(symbol);
|
||||
_userAddedUniverses.Remove(symbol);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -179,6 +179,8 @@
|
||||
<Compile Include="Selection\ManualUniverse.cs" />
|
||||
<Compile Include="Selection\ManualUniverseSelectionModel.cs" />
|
||||
<Compile Include="Selection\NullUniverseSelectionModel.cs" />
|
||||
<Compile Include="Selection\OptionChainedUniverseSelectionModel.cs" />
|
||||
<Compile Include="Selection\OptionContractUniverse.cs" />
|
||||
<Compile Include="Selection\UniverseSelectionModel.cs" />
|
||||
<Compile Include="Selection\UniverseSelectionModelPythonWrapper.cs" />
|
||||
<Compile Include="UniverseDefinitions.cs" />
|
||||
|
||||
83
Algorithm/Selection/OptionChainedUniverseSelectionModel.cs
Normal file
83
Algorithm/Selection/OptionChainedUniverseSelectionModel.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
|
||||
namespace QuantConnect.Algorithm.Selection
|
||||
{
|
||||
/// <summary>
|
||||
/// This universe selection model will chain to the security changes of a given <see cref="Universe"/> selection
|
||||
/// output and create a new <see cref="OptionChainUniverse"/> for each of them
|
||||
/// </summary>
|
||||
public class OptionChainedUniverseSelectionModel : UniverseSelectionModel
|
||||
{
|
||||
private DateTime _nextRefreshTimeUtc;
|
||||
private IEnumerable<Symbol> _currentSymbols;
|
||||
private readonly UniverseSettings _universeSettings;
|
||||
private readonly Func<OptionFilterUniverse, OptionFilterUniverse> _optionFilter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.
|
||||
/// </summary>
|
||||
public override DateTime GetNextRefreshTimeUtc() => _nextRefreshTimeUtc;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="OptionChainedUniverseSelectionModel"/>
|
||||
/// </summary>
|
||||
/// <param name="universe">The universe we want to chain to</param>
|
||||
/// <param name="optionFilter">The option filter universe to use</param>
|
||||
/// <param name="universeSettings">Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed</param>
|
||||
public OptionChainedUniverseSelectionModel(Universe universe,
|
||||
Func<OptionFilterUniverse, OptionFilterUniverse> optionFilter,
|
||||
UniverseSettings universeSettings = null)
|
||||
{
|
||||
_optionFilter = optionFilter;
|
||||
_universeSettings = universeSettings;
|
||||
_nextRefreshTimeUtc = DateTime.MaxValue;
|
||||
|
||||
_currentSymbols = Enumerable.Empty<Symbol>();
|
||||
universe.SelectionChanged += (sender, args) =>
|
||||
{
|
||||
// the universe we were watching changed, this will trigger a call to CreateUniverses
|
||||
_nextRefreshTimeUtc = DateTime.MinValue;
|
||||
|
||||
_currentSymbols = ((Universe.SelectionEventArgs)args).CurrentSelection
|
||||
.Select(symbol => Symbol.Create(symbol.Value, SecurityType.Option, symbol.ID.Market, $"?{symbol.Value}"))
|
||||
.ToList();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the universes for this algorithm. Called once after <see cref="IAlgorithm.Initialize"/>
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance to create universes for</param>
|
||||
/// <returns>The universes to be used by the algorithm</returns>
|
||||
public override IEnumerable<Universe> CreateUniverses(QCAlgorithm algorithm)
|
||||
{
|
||||
_nextRefreshTimeUtc = DateTime.MaxValue;
|
||||
|
||||
foreach (var optionSymbol in _currentSymbols)
|
||||
{
|
||||
yield return algorithm.CreateOptionChain(optionSymbol, _optionFilter, _universeSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Algorithm/Selection/OptionContractUniverse.cs
Normal file
97
Algorithm/Selection/OptionContractUniverse.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Algorithm.Selection
|
||||
{
|
||||
/// <summary>
|
||||
/// This universe will hold single option contracts and their underlying, managing removals and additions
|
||||
/// </summary>
|
||||
public class OptionContractUniverse : UserDefinedUniverse
|
||||
{
|
||||
private readonly HashSet<Symbol> _symbols;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new empty instance
|
||||
/// </summary>
|
||||
/// <param name="configuration">The universe configuration to use</param>
|
||||
/// <param name="universeSettings">The universe settings to use</param>
|
||||
public OptionContractUniverse(SubscriptionDataConfig configuration, UniverseSettings universeSettings)
|
||||
: base(configuration, universeSettings, Time.EndOfTimeTimeSpan,
|
||||
// Argument isn't used since we override 'SelectSymbols'
|
||||
Enumerable.Empty<Symbol>())
|
||||
{
|
||||
_symbols = new HashSet<Symbol>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the symbols defined by the user for this universe
|
||||
/// </summary>
|
||||
/// <param name="utcTime">The current utc time</param>
|
||||
/// <param name="data">The symbols to remain in the universe</param>
|
||||
/// <returns>The data that passes the filter</returns>
|
||||
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
|
||||
{
|
||||
return _symbols;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event invocator for the <see cref="UserDefinedUniverse.CollectionChanged"/> event
|
||||
/// </summary>
|
||||
/// <param name="args">The notify collection changed event arguments</param>
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
if (args.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
var removedSymbol = (Symbol)args.OldItems[0];
|
||||
_symbols.Remove(removedSymbol);
|
||||
|
||||
// the option has been removed! This can happen when the user manually removed the option contract we remove the underlying
|
||||
if (removedSymbol.SecurityType == SecurityType.Option)
|
||||
{
|
||||
Remove(removedSymbol.Underlying);
|
||||
}
|
||||
}
|
||||
else if (args.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
// QCAlgorithm.AddOptionContract will add both underlying and option contract
|
||||
_symbols.Add((Symbol)args.NewItems[0]);
|
||||
}
|
||||
|
||||
base.OnCollectionChanged(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a user defined universe symbol
|
||||
/// </summary>
|
||||
/// <param name="market">The market</param>
|
||||
/// <param name="securityType">The underlying option security type</param>
|
||||
/// <returns>A symbol for user defined universe of the specified security type and market</returns>
|
||||
public static Symbol CreateSymbol(string market, SecurityType securityType)
|
||||
{
|
||||
var ticker = $"qc-universe-optioncontract-{securityType.SecurityTypeToLower()}-{market.ToLowerInvariant()}";
|
||||
var underlying = Symbol.Create(ticker, securityType, market);
|
||||
var sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying.ID, market, 0, 0, 0);
|
||||
|
||||
return new Symbol(sid, ticker);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,8 +288,7 @@ logging.captureWarnings(True)"
|
||||
catch (Exception err)
|
||||
{
|
||||
errorMessage = "Algorithm type name not found, or unable to resolve multiple algorithm types to a single type. Please verify algorithm type name matches the algorithm name in the configuration file and that there is one and only one class derived from QCAlgorithm.";
|
||||
errorMessage += err.InnerException == null ? err.Message : err.InnerException.Message;
|
||||
Log.Error($"Loader.TryCreateILAlgorithm(): {errorMessage}");
|
||||
Log.Error($"Loader.TryCreateILAlgorithm(): {errorMessage}\n{err.InnerException ?? err}");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
506
Api/Api.cs
506
Api/Api.cs
@@ -19,8 +19,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.API;
|
||||
using QuantConnect.Data.Market;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Orders;
|
||||
using RestSharp;
|
||||
@@ -76,13 +75,15 @@ namespace QuantConnect.Api
|
||||
|
||||
public ProjectResponse CreateProject(string name, Language language)
|
||||
{
|
||||
var request = new RestRequest("projects/create", Method.POST);
|
||||
var request = new RestRequest("projects/create", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
name = name,
|
||||
language = language
|
||||
name,
|
||||
language
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
ProjectResponse result;
|
||||
@@ -98,10 +99,15 @@ namespace QuantConnect.Api
|
||||
|
||||
public ProjectResponse ReadProject(int projectId)
|
||||
{
|
||||
var request = new RestRequest("projects/read", Method.GET);
|
||||
var request = new RestRequest("projects/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
ProjectResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -115,8 +121,11 @@ namespace QuantConnect.Api
|
||||
|
||||
public ProjectResponse ListProjects()
|
||||
{
|
||||
var request = new RestRequest("projects/read", Method.GET);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
var request = new RestRequest("projects/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
ProjectResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -133,11 +142,17 @@ namespace QuantConnect.Api
|
||||
|
||||
public ProjectFilesResponse AddProjectFile(int projectId, string name, string content)
|
||||
{
|
||||
var request = new RestRequest("files/create", Method.POST);
|
||||
var request = new RestRequest("files/create", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("name", name);
|
||||
request.AddParameter("content", content);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
name,
|
||||
content
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
ProjectFilesResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -155,11 +170,17 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse UpdateProjectFileName(int projectId, string oldFileName, string newFileName)
|
||||
{
|
||||
var request = new RestRequest("files/update", Method.POST);
|
||||
var request = new RestRequest("files/update", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("name", oldFileName);
|
||||
request.AddParameter("newName", newFileName);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
name = oldFileName,
|
||||
newName = newFileName
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -177,11 +198,17 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse UpdateProjectFileContent(int projectId, string fileName, string newFileContents)
|
||||
{
|
||||
var request = new RestRequest("files/update", Method.POST);
|
||||
var request = new RestRequest("files/update", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("name", fileName);
|
||||
request.AddParameter("content", newFileContents);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
name = fileName,
|
||||
content = newFileContents
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -197,9 +224,15 @@ namespace QuantConnect.Api
|
||||
|
||||
public ProjectFilesResponse ReadProjectFiles(int projectId)
|
||||
{
|
||||
var request = new RestRequest("files/read", Method.GET);
|
||||
var request = new RestRequest("files/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
ProjectFilesResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -216,10 +249,16 @@ namespace QuantConnect.Api
|
||||
|
||||
public ProjectFilesResponse ReadProjectFile(int projectId, string fileName)
|
||||
{
|
||||
var request = new RestRequest("files/read", Method.GET);
|
||||
var request = new RestRequest("files/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("name", fileName);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
name = fileName
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
ProjectFilesResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -235,10 +274,16 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse DeleteProjectFile(int projectId, string name)
|
||||
{
|
||||
var request = new RestRequest("files/delete", Method.POST);
|
||||
var request = new RestRequest("files/delete", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("name", name);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
name,
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -253,12 +298,16 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse DeleteProject(int projectId)
|
||||
{
|
||||
var request = new RestRequest("projects/delete", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
var request = new RestRequest("projects/delete", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId = projectId
|
||||
projectId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -272,11 +321,16 @@ namespace QuantConnect.Api
|
||||
|
||||
public Compile CreateCompile(int projectId)
|
||||
{
|
||||
var request = new RestRequest("compile/create", Method.POST);
|
||||
var request = new RestRequest("compile/create", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId = projectId
|
||||
projectId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
Compile result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -291,10 +345,17 @@ namespace QuantConnect.Api
|
||||
|
||||
public Compile ReadCompile(int projectId, string compileId)
|
||||
{
|
||||
var request = new RestRequest("compile/read", Method.GET);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("compileId", compileId);
|
||||
var request = new RestRequest("compile/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
compileId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
Compile result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -311,10 +372,18 @@ namespace QuantConnect.Api
|
||||
|
||||
public Backtest CreateBacktest(int projectId, string compileId, string backtestName)
|
||||
{
|
||||
var request = new RestRequest("backtests/create", Method.POST);
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("compileId", compileId);
|
||||
request.AddParameter("backtestName", backtestName);
|
||||
var request = new RestRequest("backtests/create", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
compileId,
|
||||
backtestName
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
Backtest result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -329,9 +398,17 @@ namespace QuantConnect.Api
|
||||
|
||||
public Backtest ReadBacktest(int projectId, string backtestId)
|
||||
{
|
||||
var request = new RestRequest("backtests/read", Method.GET);
|
||||
request.AddParameter("backtestId", backtestId);
|
||||
request.AddParameter("projectId", projectId);
|
||||
var request = new RestRequest("backtests/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
backtestId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
Backtest result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -348,15 +425,19 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse UpdateBacktest(int projectId, string backtestId, string name = "", string note = "")
|
||||
{
|
||||
var request = new RestRequest("backtests/update", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
var request = new RestRequest("backtests/update", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId = projectId,
|
||||
backtestId = backtestId,
|
||||
name = name,
|
||||
note = note
|
||||
projectId,
|
||||
backtestId,
|
||||
name,
|
||||
note
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
Backtest result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -370,8 +451,16 @@ namespace QuantConnect.Api
|
||||
|
||||
public BacktestList ListBacktests(int projectId)
|
||||
{
|
||||
var request = new RestRequest("backtests/read", Method.GET);
|
||||
request.AddParameter("projectId", projectId);
|
||||
var request = new RestRequest("backtests/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
BacktestList result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -386,10 +475,17 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse DeleteBacktest(int projectId, string backtestId)
|
||||
{
|
||||
var request = new RestRequest("backtests/delete", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("backtestId", backtestId);
|
||||
request.AddParameter("projectId", projectId);
|
||||
var request = new RestRequest("backtests/delete", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
backtestId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -400,7 +496,7 @@ namespace QuantConnect.Api
|
||||
/// </summary>
|
||||
/// <param name="projectId">Id of the project on QuantConnect</param>
|
||||
/// <param name="compileId">Id of the compilation on QuantConnect</param>
|
||||
/// <param name="serverType">Type of server instance that will run the algorithm</param>
|
||||
/// <param name="nodeId">Id of the node that will run the algorithm</param>
|
||||
/// <param name="baseLiveAlgorithmSettings">Brokerage specific <see cref="BaseLiveAlgorithmSettings">BaseLiveAlgorithmSettings</see>.</param>
|
||||
/// <param name="versionId">The version of the Lean used to run the algorithm.
|
||||
/// -1 is master, however, sometimes this can create problems with live deployments.
|
||||
@@ -409,19 +505,23 @@ namespace QuantConnect.Api
|
||||
|
||||
public LiveAlgorithm CreateLiveAlgorithm(int projectId,
|
||||
string compileId,
|
||||
string serverType,
|
||||
string nodeId,
|
||||
BaseLiveAlgorithmSettings baseLiveAlgorithmSettings,
|
||||
string versionId = "-1")
|
||||
{
|
||||
var request = new RestRequest("live/create", Method.POST);
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.Parameters.Clear();
|
||||
var body = JsonConvert.SerializeObject(new LiveAlgorithmApiSettingsWrapper(projectId,
|
||||
compileId,
|
||||
serverType,
|
||||
baseLiveAlgorithmSettings,
|
||||
versionId));
|
||||
request.AddParameter("application/json", body, ParameterType.RequestBody);
|
||||
var request = new RestRequest("live/create", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(
|
||||
new LiveAlgorithmApiSettingsWrapper
|
||||
(projectId,
|
||||
compileId,
|
||||
nodeId,
|
||||
baseLiveAlgorithmSettings,
|
||||
versionId)
|
||||
), ParameterType.RequestBody);
|
||||
|
||||
LiveAlgorithm result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -451,18 +551,26 @@ namespace QuantConnect.Api
|
||||
"The Api only supports Algorithm Statuses of Running, Stopped, RuntimeError and Liquidated");
|
||||
}
|
||||
|
||||
var request = new RestRequest("live/read", Method.GET);
|
||||
|
||||
if (status.HasValue)
|
||||
var request = new RestRequest("live/read", Method.POST)
|
||||
{
|
||||
request.AddParameter("status", status.ToString());
|
||||
}
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
var epochStartTime = startTime == null ? 0 : Time.DateTimeToUnixTimeStamp(startTime.Value);
|
||||
var epochEndTime = endTime == null ? Time.DateTimeToUnixTimeStamp(DateTime.UtcNow) : Time.DateTimeToUnixTimeStamp(endTime.Value);
|
||||
|
||||
request.AddParameter("start", epochStartTime);
|
||||
request.AddParameter("end", epochEndTime);
|
||||
JObject obj = new JObject
|
||||
{
|
||||
{ "start", epochStartTime },
|
||||
{ "end", epochEndTime }
|
||||
};
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
obj.Add("status", status.ToString());
|
||||
}
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
|
||||
|
||||
LiveList result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -478,9 +586,17 @@ namespace QuantConnect.Api
|
||||
|
||||
public LiveAlgorithmResults ReadLiveAlgorithm(int projectId, string deployId)
|
||||
{
|
||||
var request = new RestRequest("live/read", Method.GET);
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("deployId", deployId);
|
||||
var request = new RestRequest("live/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId,
|
||||
deployId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
LiveAlgorithmResults result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -494,9 +610,16 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse LiquidateLiveAlgorithm(int projectId)
|
||||
{
|
||||
var request = new RestRequest("live/update/liquidate", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("projectId", projectId);
|
||||
var request = new RestRequest("live/update/liquidate", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -510,9 +633,16 @@ namespace QuantConnect.Api
|
||||
|
||||
public RestResponse StopLiveAlgorithm(int projectId)
|
||||
{
|
||||
var request = new RestRequest("live/update/stop", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("projectId", projectId);
|
||||
var request = new RestRequest("live/update/stop", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
projectId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
return result;
|
||||
@@ -532,13 +662,19 @@ namespace QuantConnect.Api
|
||||
var epochStartTime = startTime == null ? 0 : Time.DateTimeToUnixTimeStamp(startTime.Value);
|
||||
var epochEndTime = endTime == null ? Time.DateTimeToUnixTimeStamp(DateTime.UtcNow) : Time.DateTimeToUnixTimeStamp(endTime.Value);
|
||||
|
||||
var request = new RestRequest("live/read/log", Method.GET);
|
||||
var request = new RestRequest("live/read/log", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("format", "json");
|
||||
request.AddParameter("projectId", projectId);
|
||||
request.AddParameter("algorithmId", algorithmId);
|
||||
request.AddParameter("start", epochStartTime);
|
||||
request.AddParameter("end", epochEndTime);
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
format = "json",
|
||||
projectId,
|
||||
algorithmId,
|
||||
start = epochStartTime,
|
||||
end = epochEndTime
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
LiveLog result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -555,14 +691,20 @@ namespace QuantConnect.Api
|
||||
|
||||
public Link ReadDataLink(Symbol symbol, Resolution resolution, DateTime date)
|
||||
{
|
||||
var request = new RestRequest("data/read", Method.GET);
|
||||
var request = new RestRequest("data/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("format", "link");
|
||||
request.AddParameter("ticker", symbol.Value.ToLowerInvariant());
|
||||
request.AddParameter("type", symbol.ID.SecurityType.ToLower());
|
||||
request.AddParameter("market", symbol.ID.Market);
|
||||
request.AddParameter("resolution", resolution);
|
||||
request.AddParameter("date", date.ToStringInvariant("yyyyMMdd"));
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
format = "link",
|
||||
ticker = symbol.Value.ToLowerInvariant(),
|
||||
type = symbol.ID.SecurityType.ToLower(),
|
||||
market = symbol.ID.Market,
|
||||
resolution = resolution.ToString(),
|
||||
date = date.ToStringInvariant("yyyyMMdd")
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
Link result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -577,44 +719,22 @@ namespace QuantConnect.Api
|
||||
/// <returns><see cref="BacktestReport"/></returns>
|
||||
public BacktestReport ReadBacktestReport(int projectId, string backtestId)
|
||||
{
|
||||
var request = new RestRequest("backtests/read/report", Method.POST);
|
||||
request.AddParameter("backtestId", backtestId);
|
||||
request.AddParameter("projectId", projectId);
|
||||
var request = new RestRequest("backtests/read/report", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
backtestId,
|
||||
projectId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
BacktestReport report;
|
||||
ApiConnection.TryRequest(request, out report);
|
||||
return report;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will get the prices for requested symbols
|
||||
/// </summary>
|
||||
/// <param name="symbols">Symbols for which the price is requested</param>
|
||||
/// <returns><see cref="Prices"/></returns>
|
||||
public PricesList ReadPrices(IEnumerable<Symbol> symbols)
|
||||
{
|
||||
var symbolByID = new Dictionary<string, Symbol>();
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
symbolByID[symbol.ID.ToString()] = symbol;
|
||||
}
|
||||
|
||||
var request = new RestRequest("prices", Method.POST);
|
||||
var symbolsToRequest = string.Join(",", symbolByID.Keys);
|
||||
request.AddParameter("symbols", symbolsToRequest);
|
||||
|
||||
PricesList pricesList;
|
||||
if (ApiConnection.TryRequest(request, out pricesList))
|
||||
{
|
||||
foreach (var price in pricesList.Prices)
|
||||
{
|
||||
price.Symbol = symbolByID[price.SymbolID];
|
||||
}
|
||||
}
|
||||
|
||||
return pricesList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to download and save the data purchased through QuantConnect
|
||||
/// </summary>
|
||||
@@ -705,54 +825,6 @@ namespace QuantConnect.Api
|
||||
//
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all split events between the specified times. From and to are inclusive.
|
||||
/// </summary>
|
||||
/// <param name="from">The first date to get splits for</param>
|
||||
/// <param name="to">The last date to get splits for</param>
|
||||
/// <returns>A list of all splits in the specified range</returns>
|
||||
public List<Data.Market.Split> GetSplits(DateTime from, DateTime to)
|
||||
{
|
||||
var request = new RestRequest("splits", Method.POST);
|
||||
request.AddParameter("from", from.ToStringInvariant("yyyyMMdd"));
|
||||
request.AddParameter("to", from.ToStringInvariant("yyyyMMdd"));
|
||||
|
||||
SplitList splits;
|
||||
ApiConnection.TryRequest(request, out splits);
|
||||
|
||||
return splits.Splits.Select(s => new Data.Market.Split(
|
||||
s.Symbol,
|
||||
s.Date,
|
||||
s.ReferencePrice,
|
||||
s.SplitFactor,
|
||||
SplitType.SplitOccurred)
|
||||
).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all dividend events between the specified times. From and to are inclusive.
|
||||
/// </summary>
|
||||
/// <param name="from">The first date to get dividend for</param>
|
||||
/// <param name="to">The last date to get dividend for</param>
|
||||
/// <returns>A list of all dividend in the specified range</returns>
|
||||
public List<Data.Market.Dividend> GetDividends(DateTime from, DateTime to)
|
||||
{
|
||||
var request = new RestRequest("dividends", Method.POST);
|
||||
request.AddParameter("from", from.ToStringInvariant("yyyyMMdd"));
|
||||
request.AddParameter("to", from.ToStringInvariant("yyyyMMdd"));
|
||||
|
||||
DividendList dividends;
|
||||
ApiConnection.TryRequest(request, out dividends);
|
||||
|
||||
return dividends.Dividends.Select(s => new Data.Market.Dividend(
|
||||
s.Symbol,
|
||||
s.Date,
|
||||
s.DividendPerShare,
|
||||
s.ReferencePrice)
|
||||
).ToList();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Local implementation for downloading data to algorithms
|
||||
/// </summary>
|
||||
@@ -808,32 +880,44 @@ namespace QuantConnect.Api
|
||||
/// <param name="name">The name of the new node</param>
|
||||
/// <param name="organizationId">ID of the organization</param>
|
||||
/// <param name="sku"><see cref="SKU"/> Object representing configuration</param>
|
||||
/// <returns>Returns <see cref="CreatedNode"/> which contains API response and
|
||||
/// <returns>Returns <see cref="CreatedNode"/> which contains API response and
|
||||
/// <see cref="Node"/></returns>
|
||||
public CreatedNode CreateNode(string name, string organizationId, SKU sku)
|
||||
{
|
||||
var request = new RestRequest("nodes/create", Method.POST);
|
||||
request.AddParameter("name", name);
|
||||
request.AddParameter("organizationId", organizationId);
|
||||
request.AddParameter("sku", sku.ToString());
|
||||
var request = new RestRequest("nodes/create", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
name,
|
||||
organizationId,
|
||||
sku = sku.ToString()
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
CreatedNode result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the nodes associated with the organization, creating a
|
||||
/// Reads the nodes associated with the organization, creating a
|
||||
/// <see cref="NodeList"/> for the response
|
||||
/// </summary>
|
||||
/// <param name="organizationId">ID of the organization</param>
|
||||
/// <returns><see cref="NodeList"/> containing Backtest, Research, and Live Nodes</returns>
|
||||
public NodeList ReadNodes(string organizationId)
|
||||
{
|
||||
var request = new RestRequest("nodes/read", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("organizationId", organizationId);
|
||||
var request = new RestRequest("nodes/read", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
organizationId,
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
NodeList result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -849,11 +933,17 @@ namespace QuantConnect.Api
|
||||
/// <returns><see cref="RestResponse"/> containing success response and errors</returns>
|
||||
public RestResponse UpdateNode(string nodeId, string newName, string organizationId)
|
||||
{
|
||||
var request = new RestRequest("nodes/update", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("nodeId", nodeId);
|
||||
request.AddParameter("name", newName);
|
||||
request.AddParameter("organizationId", organizationId);
|
||||
var request = new RestRequest("nodes/update", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
nodeId,
|
||||
name = newName,
|
||||
organizationId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -868,10 +958,16 @@ namespace QuantConnect.Api
|
||||
/// <returns><see cref="RestResponse"/> containing success response and errors</returns>
|
||||
public RestResponse DeleteNode(string nodeId, string organizationId)
|
||||
{
|
||||
var request = new RestRequest("nodes/delete", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("nodeId", nodeId);
|
||||
request.AddParameter("organizationId", organizationId);
|
||||
var request = new RestRequest("nodes/delete", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
nodeId,
|
||||
organizationId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
@@ -886,10 +982,16 @@ namespace QuantConnect.Api
|
||||
/// <returns><see cref="RestResponse"/> containing success response and errors</returns>
|
||||
public RestResponse StopNode(string nodeId, string organizationId)
|
||||
{
|
||||
var request = new RestRequest("nodes/stop", Method.POST);
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
request.AddParameter("nodeId", nodeId);
|
||||
request.AddParameter("organizationId", organizationId);
|
||||
var request = new RestRequest("nodes/stop", Method.POST)
|
||||
{
|
||||
RequestFormat = DataFormat.Json
|
||||
};
|
||||
|
||||
request.AddParameter("application/json", JsonConvert.SerializeObject(new
|
||||
{
|
||||
nodeId,
|
||||
organizationId
|
||||
}), ParameterType.RequestBody);
|
||||
|
||||
RestResponse result;
|
||||
ApiConnection.TryRequest(request, out result);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.API;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
|
||||
2271
Api/QuantConnect-Platform-2.0.0.yaml
Normal file
2271
Api/QuantConnect-Platform-2.0.0.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -110,6 +110,11 @@ namespace QuantConnect.Brokerages.Alpaca
|
||||
/// </summary>
|
||||
public override bool IsConnected => _sockClient.IsConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the brokerage account's base currency
|
||||
/// </summary>
|
||||
public override string AccountBaseCurrency => Currencies.USD;
|
||||
|
||||
/// <summary>
|
||||
/// Connects the client to the broker's remote servers
|
||||
/// </summary>
|
||||
@@ -117,6 +122,8 @@ namespace QuantConnect.Brokerages.Alpaca
|
||||
{
|
||||
if (IsConnected) return;
|
||||
|
||||
AccountBaseCurrency = GetAccountBaseCurrency();
|
||||
|
||||
_sockClient.Connect();
|
||||
}
|
||||
|
||||
@@ -153,8 +160,7 @@ namespace QuantConnect.Brokerages.Alpaca
|
||||
|
||||
return new List<CashAmount>
|
||||
{
|
||||
new CashAmount(balance.TradableCash,
|
||||
Currencies.USD)
|
||||
new CashAmount(balance.TradableCash, balance.Currency)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -293,6 +299,19 @@ namespace QuantConnect.Brokerages.Alpaca
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the account base currency
|
||||
/// </summary>
|
||||
private string GetAccountBaseCurrency()
|
||||
{
|
||||
CheckRateLimiting();
|
||||
|
||||
var task = _alpacaTradingClient.GetAccountAsync();
|
||||
var balance = task.SynchronouslyAwaitTaskResult();
|
||||
|
||||
return balance.Currency;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,19 +54,8 @@ namespace QuantConnect.Brokerages
|
||||
/// <returns>The latest price</returns>
|
||||
public decimal GetLastPrice(Symbol symbol)
|
||||
{
|
||||
var result = _api.ReadPrices(new[] { symbol });
|
||||
if (!result.Success)
|
||||
{
|
||||
throw new Exception($"ReadPrices error: {string.Join(" - ", result.Errors)}");
|
||||
}
|
||||
|
||||
var priceData = result.Prices.FirstOrDefault(x => x.Symbol == symbol);
|
||||
if (priceData == null)
|
||||
{
|
||||
throw new Exception($"No price data available for symbol: {symbol.Value}");
|
||||
}
|
||||
|
||||
return priceData.Price;
|
||||
//NOP ReadPrices endpoint has been removed
|
||||
throw new InvalidOperationException("Prices endpoint is no longer supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +430,7 @@ namespace QuantConnect.Brokerages.Backtesting
|
||||
OnOrderEvent(fill);
|
||||
}
|
||||
|
||||
if (order.Type == OrderType.OptionExercise)
|
||||
if (fill.IsAssignment)
|
||||
{
|
||||
fill.Message = order.Tag;
|
||||
OnOptionPositionAssigned(fill);
|
||||
@@ -474,7 +474,8 @@ namespace QuantConnect.Brokerages.Backtesting
|
||||
|
||||
_pendingOptionAssignments.Add(option.Symbol);
|
||||
|
||||
var request = new SubmitOrderRequest(OrderType.OptionExercise, option.Type, option.Symbol, -quantity, 0m, 0m, Algorithm.UtcTime, "Simulated option assignment before expiration");
|
||||
// assignments always cause a positive change to option contract holdings
|
||||
var request = new SubmitOrderRequest(OrderType.OptionExercise, option.Type, option.Symbol, Math.Abs(quantity), 0m, 0m, Algorithm.UtcTime, "Simulated option assignment before expiration");
|
||||
|
||||
var ticket = Algorithm.Transactions.ProcessRequest(request);
|
||||
Log.Trace($"BacktestingBrokerage.ActivateOptionAssignment(): OrderId: {ticket.OrderId}");
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace QuantConnect.Brokerages.Backtesting
|
||||
// last update time
|
||||
private DateTime _lastUpdate = DateTime.MinValue;
|
||||
private Queue<DateTime> _assignmentScans;
|
||||
private static Random _rand = new Random((int)12345);
|
||||
private readonly Random _rand = new Random(12345);
|
||||
|
||||
/// <summary>
|
||||
/// We generate a list of time points when we would like to run our simulation. we then return true if the time is in the list.
|
||||
@@ -124,12 +124,12 @@ namespace QuantConnect.Brokerages.Backtesting
|
||||
|
||||
Func<Symbol, bool> deepITM = symbol =>
|
||||
{
|
||||
var undelyingPrice = algorithm.Securities[symbol.Underlying].Close;
|
||||
var underlyingPrice = algorithm.Securities[symbol.Underlying].Close;
|
||||
|
||||
var result =
|
||||
symbol.ID.OptionRight == OptionRight.Call ?
|
||||
(undelyingPrice - symbol.ID.StrikePrice) / undelyingPrice > _deepITM :
|
||||
(symbol.ID.StrikePrice - undelyingPrice) / undelyingPrice > _deepITM;
|
||||
symbol.ID.OptionRight == OptionRight.Call
|
||||
? (underlyingPrice - symbol.ID.StrikePrice) / underlyingPrice > _deepITM
|
||||
: (symbol.ID.StrikePrice - underlyingPrice) / underlyingPrice > _deepITM;
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -168,13 +168,13 @@ namespace QuantConnect.Brokerages.Backtesting
|
||||
|
||||
// we are interested in underlying bid price if we exercise calls and want to sell the underlying immediately.
|
||||
// we are interested in underlying ask price if we exercise puts
|
||||
var underlyingPrice = option.Symbol.ID.OptionRight == OptionRight.Call ?
|
||||
underlying.BidPrice :
|
||||
underlying.AskPrice;
|
||||
var underlyingPrice = option.Symbol.ID.OptionRight == OptionRight.Call
|
||||
? underlying.BidPrice
|
||||
: underlying.AskPrice;
|
||||
|
||||
var underlyingQuantity = option.Symbol.ID.OptionRight == OptionRight.Call ?
|
||||
option.GetExerciseQuantity((int)holding.AbsoluteQuantity) :
|
||||
-option.GetExerciseQuantity((int)holding.AbsoluteQuantity);
|
||||
// quantity is normally negative algo's holdings, but since we're modeling the contract holder (counter-party)
|
||||
// it's negative THEIR holdings. holding.Quantity is negative, so if counter-party exercises, they would reduce holdings
|
||||
var underlyingQuantity = option.GetExerciseQuantity(holding.Quantity);
|
||||
|
||||
// Scenario 1 (base): we just close option position
|
||||
var marketOrder1 = new MarketOrder(option.Symbol, -holding.Quantity, option.LocalTime.ConvertToUtc(option.Exchange.TimeZone));
|
||||
|
||||
226
Brokerages/Binance/BinanceBrokerage.Messaging.cs
Normal file
226
Brokerages/Binance/BinanceBrokerage.Messaging.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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 QuantConnect.Brokerages.Binance.Messages;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using QuantConnect.Data;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
public partial class BinanceBrokerage
|
||||
{
|
||||
private readonly ConcurrentQueue<WebSocketMessage> _messageBuffer = new ConcurrentQueue<WebSocketMessage>();
|
||||
private volatile bool _streamLocked;
|
||||
private readonly IDataAggregator _aggregator;
|
||||
|
||||
/// <summary>
|
||||
/// Locking object for the Ticks list in the data queue handler
|
||||
/// </summary>
|
||||
protected readonly object TickLocker = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Lock the streaming processing while we're sending orders as sometimes they fill before the REST call returns.
|
||||
/// </summary>
|
||||
private void LockStream()
|
||||
{
|
||||
_streamLocked = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlock stream and process all backed up messages.
|
||||
/// </summary>
|
||||
private void UnlockStream()
|
||||
{
|
||||
while (_messageBuffer.Any())
|
||||
{
|
||||
WebSocketMessage e;
|
||||
_messageBuffer.TryDequeue(out e);
|
||||
|
||||
OnMessageImpl(e);
|
||||
}
|
||||
|
||||
// Once dequeued in order; unlock stream.
|
||||
_streamLocked = false;
|
||||
}
|
||||
|
||||
private void WithLockedStream(Action code)
|
||||
{
|
||||
try
|
||||
{
|
||||
LockStream();
|
||||
code();
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnlockStream();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMessageImpl(WebSocketMessage e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var obj = JObject.Parse(e.Message);
|
||||
|
||||
var objError = obj["error"];
|
||||
if (objError != null)
|
||||
{
|
||||
var error = objError.ToObject<ErrorMessage>();
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, error.Code, error.Message));
|
||||
return;
|
||||
}
|
||||
|
||||
var objData = obj;
|
||||
|
||||
var objEventType = objData["e"];
|
||||
if (objEventType != null)
|
||||
{
|
||||
var eventType = objEventType.ToObject<string>();
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case "executionReport":
|
||||
var upd = objData.ToObject<Execution>();
|
||||
if (upd.ExecutionType.Equals("TRADE", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OnFillOrder(upd);
|
||||
}
|
||||
break;
|
||||
|
||||
case "trade":
|
||||
var trade = objData.ToObject<Trade>();
|
||||
EmitTradeTick(
|
||||
_symbolMapper.GetLeanSymbol(trade.Symbol, SecurityType.Crypto, Market.Binance),
|
||||
Time.UnixMillisecondTimeStampToDateTime(trade.Time),
|
||||
trade.Price,
|
||||
trade.Quantity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (objData["u"] != null)
|
||||
{
|
||||
var quote = objData.ToObject<BestBidAskQuote>();
|
||||
EmitQuoteTick(
|
||||
_symbolMapper.GetLeanSymbol(quote.Symbol, SecurityType.Crypto, Market.Binance),
|
||||
quote.BestBidPrice,
|
||||
quote.BestBidSize,
|
||||
quote.BestAskPrice,
|
||||
quote.BestAskSize);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1, $"Parsing wss message failed. Data: {e.Message} Exception: {exception}"));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitQuoteTick(Symbol symbol, decimal bidPrice, decimal bidSize, decimal askPrice, decimal askSize)
|
||||
{
|
||||
var tick = new Tick
|
||||
{
|
||||
AskPrice = askPrice,
|
||||
BidPrice = bidPrice,
|
||||
Time = DateTime.UtcNow,
|
||||
Symbol = symbol,
|
||||
TickType = TickType.Quote,
|
||||
AskSize = askSize,
|
||||
BidSize = bidSize
|
||||
};
|
||||
tick.SetValue();
|
||||
|
||||
lock (TickLocker)
|
||||
{
|
||||
_aggregator.Update(tick);
|
||||
}
|
||||
}
|
||||
|
||||
private void EmitTradeTick(Symbol symbol, DateTime time, decimal price, decimal quantity)
|
||||
{
|
||||
var tick = new Tick
|
||||
{
|
||||
Symbol = symbol,
|
||||
Value = price,
|
||||
Quantity = Math.Abs(quantity),
|
||||
Time = time,
|
||||
TickType = TickType.Trade
|
||||
};
|
||||
|
||||
lock (TickLocker)
|
||||
{
|
||||
_aggregator.Update(tick);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFillOrder(Execution data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var order = FindOrderByExternalId(data.OrderId);
|
||||
if (order == null)
|
||||
{
|
||||
// not our order, nothing else to do here
|
||||
return;
|
||||
}
|
||||
|
||||
var fillPrice = data.LastExecutedPrice;
|
||||
var fillQuantity = data.Direction == OrderDirection.Sell ? -data.LastExecutedQuantity : data.LastExecutedQuantity;
|
||||
var updTime = Time.UnixMillisecondTimeStampToDateTime(data.TransactionTime);
|
||||
var orderFee = new OrderFee(new CashAmount(data.Fee, data.FeeCurrency));
|
||||
var status = ConvertOrderStatus(data.OrderStatus);
|
||||
var orderEvent = new OrderEvent
|
||||
(
|
||||
order.Id, order.Symbol, updTime, status,
|
||||
data.Direction, fillPrice, fillQuantity,
|
||||
orderFee, $"Binance Order Event {data.Direction}"
|
||||
);
|
||||
|
||||
if (status == OrderStatus.Filled)
|
||||
{
|
||||
Orders.Order outOrder;
|
||||
CachedOrderIDs.TryRemove(order.Id, out outOrder);
|
||||
}
|
||||
|
||||
OnOrderEvent(orderEvent);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private Orders.Order FindOrderByExternalId(string brokerId)
|
||||
{
|
||||
var order = CachedOrderIDs
|
||||
.FirstOrDefault(o => o.Value.BrokerId.Contains(brokerId))
|
||||
.Value;
|
||||
if (order == null)
|
||||
{
|
||||
order = _algorithm.Transactions.GetOrderByBrokerageId(brokerId);
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Brokerages/Binance/BinanceBrokerage.Utility.cs
Normal file
53
Brokerages/Binance/BinanceBrokerage.Utility.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 QuantConnect.Orders;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
/// <summary>
|
||||
/// Binance utility methods
|
||||
/// </summary>
|
||||
public partial class BinanceBrokerage
|
||||
{
|
||||
private static OrderStatus ConvertOrderStatus(string raw)
|
||||
{
|
||||
switch (raw.LazyToUpper())
|
||||
{
|
||||
case "NEW":
|
||||
return OrderStatus.New;
|
||||
|
||||
case "PARTIALLY_FILLED":
|
||||
return OrderStatus.PartiallyFilled;
|
||||
|
||||
case "FILLED":
|
||||
return OrderStatus.Filled;
|
||||
|
||||
case "PENDING_CANCEL":
|
||||
return OrderStatus.CancelPending;
|
||||
|
||||
case "CANCELED":
|
||||
return OrderStatus.Canceled;
|
||||
|
||||
case "REJECTED":
|
||||
case "EXPIRED":
|
||||
return OrderStatus.Invalid;
|
||||
|
||||
default:
|
||||
return OrderStatus.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
491
Brokerages/Binance/BinanceBrokerage.cs
Normal file
491
Brokerages/Binance/BinanceBrokerage.cs
Normal file
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* 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 QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Packets;
|
||||
using QuantConnect.Securities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using QuantConnect.Util;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
/// <summary>
|
||||
/// Binance brokerage implementation
|
||||
/// </summary>
|
||||
[BrokerageFactory(typeof(BinanceBrokerageFactory))]
|
||||
public partial class BinanceBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
|
||||
{
|
||||
private const string WebSocketBaseUrl = "wss://stream.binance.com:9443/ws";
|
||||
|
||||
private readonly IAlgorithm _algorithm;
|
||||
private readonly SymbolPropertiesDatabaseSymbolMapper _symbolMapper = new SymbolPropertiesDatabaseSymbolMapper(Market.Binance);
|
||||
|
||||
private readonly RateGate _webSocketRateLimiter = new RateGate(5, TimeSpan.FromSeconds(1));
|
||||
private long _lastRequestId;
|
||||
|
||||
private readonly Timer _keepAliveTimer;
|
||||
private readonly Timer _reconnectTimer;
|
||||
private readonly BinanceRestApiClient _apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for brokerage
|
||||
/// </summary>
|
||||
/// <param name="apiKey">api key</param>
|
||||
/// <param name="apiSecret">api secret</param>
|
||||
/// <param name="algorithm">the algorithm instance is required to retrieve account type</param>
|
||||
/// <param name="aggregator">the aggregator for consolidating ticks</param>
|
||||
public BinanceBrokerage(string apiKey, string apiSecret, IAlgorithm algorithm, IDataAggregator aggregator)
|
||||
: base(WebSocketBaseUrl, new WebSocketClientWrapper(), null, apiKey, apiSecret, "Binance")
|
||||
{
|
||||
_algorithm = algorithm;
|
||||
_aggregator = aggregator;
|
||||
|
||||
var subscriptionManager = new EventBasedDataQueueHandlerSubscriptionManager();
|
||||
subscriptionManager.SubscribeImpl += (s, t) =>
|
||||
{
|
||||
Subscribe(s);
|
||||
return true;
|
||||
};
|
||||
subscriptionManager.UnsubscribeImpl += (s, t) => Unsubscribe(s);
|
||||
|
||||
SubscriptionManager = subscriptionManager;
|
||||
|
||||
_apiClient = new BinanceRestApiClient(
|
||||
_symbolMapper,
|
||||
algorithm?.Portfolio,
|
||||
apiKey,
|
||||
apiSecret);
|
||||
|
||||
_apiClient.OrderSubmit += (s, e) => OnOrderSubmit(e);
|
||||
_apiClient.OrderStatusChanged += (s, e) => OnOrderEvent(e);
|
||||
_apiClient.Message += (s, e) => OnMessage(e);
|
||||
|
||||
// User data streams will close after 60 minutes. It's recommended to send a ping about every 30 minutes.
|
||||
// Source: https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#pingkeep-alive-a-listenkey
|
||||
_keepAliveTimer = new Timer
|
||||
{
|
||||
// 30 minutes
|
||||
Interval = 30 * 60 * 1000
|
||||
};
|
||||
_keepAliveTimer.Elapsed += (s, e) => _apiClient.SessionKeepAlive();
|
||||
|
||||
WebSocket.Open += (s, e) => { _keepAliveTimer.Start(); };
|
||||
WebSocket.Closed += (s, e) => { _keepAliveTimer.Stop(); };
|
||||
|
||||
// A single connection to stream.binance.com is only valid for 24 hours; expect to be disconnected at the 24 hour mark
|
||||
// Source: https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#general-wss-information
|
||||
_reconnectTimer = new Timer
|
||||
{
|
||||
// 23.5 hours
|
||||
Interval = 23.5 * 60 * 60 * 1000
|
||||
};
|
||||
_reconnectTimer.Elapsed += (s, e) =>
|
||||
{
|
||||
Log.Trace("Daily websocket restart: disconnect");
|
||||
Disconnect();
|
||||
|
||||
Log.Trace("Daily websocket restart: connect");
|
||||
Connect();
|
||||
};
|
||||
}
|
||||
|
||||
#region IBrokerage
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the websocket connection is connected or in the process of connecting
|
||||
/// </summary>
|
||||
public override bool IsConnected => WebSocket.IsOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Creates wss connection
|
||||
/// </summary>
|
||||
public override void Connect()
|
||||
{
|
||||
if (IsConnected)
|
||||
return;
|
||||
|
||||
_apiClient.CreateListenKey();
|
||||
_reconnectTimer.Start();
|
||||
|
||||
WebSocket.Initialize($"{WebSocketBaseUrl}/{_apiClient.SessionId}");
|
||||
|
||||
base.Connect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the websockets connection
|
||||
/// </summary>
|
||||
public override void Disconnect()
|
||||
{
|
||||
_reconnectTimer.Stop();
|
||||
|
||||
WebSocket?.Close();
|
||||
_apiClient.StopSession();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all open positions
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override List<Holding> GetAccountHoldings()
|
||||
{
|
||||
return _apiClient.GetAccountHoldings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total account cash balance for specified account type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override List<CashAmount> GetCashBalance()
|
||||
{
|
||||
var account = _apiClient.GetCashBalance();
|
||||
var balances = account.Balances?.Where(balance => balance.Amount > 0).ToList();
|
||||
if (balances == null || !balances.Any())
|
||||
return new List<CashAmount>();
|
||||
|
||||
return balances
|
||||
.Select(b => new CashAmount(b.Amount, b.Asset.LazyToUpper()))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all orders not yet closed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override List<Order> GetOpenOrders()
|
||||
{
|
||||
var orders = _apiClient.GetOpenOrders();
|
||||
List<Order> list = new List<Order>();
|
||||
foreach (var item in orders)
|
||||
{
|
||||
Order order;
|
||||
switch (item.Type.LazyToUpper())
|
||||
{
|
||||
case "MARKET":
|
||||
order = new MarketOrder { Price = item.Price };
|
||||
break;
|
||||
case "LIMIT":
|
||||
case "LIMIT_MAKER":
|
||||
order = new LimitOrder { LimitPrice = item.Price };
|
||||
break;
|
||||
case "STOP_LOSS":
|
||||
case "TAKE_PROFIT":
|
||||
order = new StopMarketOrder { StopPrice = item.StopPrice, Price = item.Price };
|
||||
break;
|
||||
case "STOP_LOSS_LIMIT":
|
||||
case "TAKE_PROFIT_LIMIT":
|
||||
order = new StopLimitOrder { StopPrice = item.StopPrice, LimitPrice = item.Price };
|
||||
break;
|
||||
default:
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1,
|
||||
"BinanceBrokerage.GetOpenOrders: Unsupported order type returned from brokerage: " + item.Type));
|
||||
continue;
|
||||
}
|
||||
|
||||
order.Quantity = item.Quantity;
|
||||
order.BrokerId = new List<string> { item.Id };
|
||||
order.Symbol = _symbolMapper.GetLeanSymbol(item.Symbol, SecurityType.Crypto, Market.Binance);
|
||||
order.Time = Time.UnixMillisecondTimeStampToDateTime(item.Time);
|
||||
order.Status = ConvertOrderStatus(item.Status);
|
||||
order.Price = item.Price;
|
||||
|
||||
if (order.Status.IsOpen())
|
||||
{
|
||||
var cached = CachedOrderIDs.Where(c => c.Value.BrokerId.Contains(order.BrokerId.First())).ToList();
|
||||
if (cached.Any())
|
||||
{
|
||||
CachedOrderIDs[cached.First().Key] = order;
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(order);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Places a new order and assigns a new broker ID to the order
|
||||
/// </summary>
|
||||
/// <param name="order">The order to be placed</param>
|
||||
/// <returns>True if the request for a new order has been placed, false otherwise</returns>
|
||||
public override bool PlaceOrder(Order order)
|
||||
{
|
||||
var submitted = false;
|
||||
|
||||
WithLockedStream(() =>
|
||||
{
|
||||
submitted = _apiClient.PlaceOrder(order);
|
||||
});
|
||||
|
||||
return submitted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the order with the same id
|
||||
/// </summary>
|
||||
/// <param name="order">The new order information</param>
|
||||
/// <returns>True if the request was made for the order to be updated, false otherwise</returns>
|
||||
public override bool UpdateOrder(Order order)
|
||||
{
|
||||
throw new NotSupportedException("BinanceBrokerage.UpdateOrder: Order update not supported. Please cancel and re-create.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the order with the specified ID
|
||||
/// </summary>
|
||||
/// <param name="order">The order to cancel</param>
|
||||
/// <returns>True if the request was submitted for cancellation, false otherwise</returns>
|
||||
public override bool CancelOrder(Order order)
|
||||
{
|
||||
var submitted = false;
|
||||
|
||||
WithLockedStream(() =>
|
||||
{
|
||||
submitted = _apiClient.CancelOrder(order);
|
||||
});
|
||||
|
||||
return submitted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the history for the requested security
|
||||
/// </summary>
|
||||
/// <param name="request">The historical data request</param>
|
||||
/// <returns>An enumerable of bars covering the span specified in the request</returns>
|
||||
public override IEnumerable<BaseData> GetHistory(Data.HistoryRequest request)
|
||||
{
|
||||
if (request.Resolution == Resolution.Tick || request.Resolution == Resolution.Second)
|
||||
{
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidResolution",
|
||||
$"{request.Resolution} resolution is not supported, no history returned"));
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (request.TickType != TickType.Trade)
|
||||
{
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidTickType",
|
||||
$"{request.TickType} tick type not supported, no history returned"));
|
||||
yield break;
|
||||
}
|
||||
|
||||
var period = request.Resolution.ToTimeSpan();
|
||||
|
||||
foreach (var kline in _apiClient.GetHistory(request))
|
||||
{
|
||||
yield return new TradeBar()
|
||||
{
|
||||
Time = Time.UnixMillisecondTimeStampToDateTime(kline.OpenTime),
|
||||
Symbol = request.Symbol,
|
||||
Low = kline.Low,
|
||||
High = kline.High,
|
||||
Open = kline.Open,
|
||||
Close = kline.Close,
|
||||
Volume = kline.Volume,
|
||||
Value = kline.Close,
|
||||
DataType = MarketDataType.TradeBar,
|
||||
Period = period,
|
||||
EndTime = Time.UnixMillisecondTimeStampToDateTime(kline.OpenTime + (long)period.TotalMilliseconds)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wss message handler
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
public override void OnMessage(object sender, WebSocketMessage e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_streamLocked)
|
||||
{
|
||||
_messageBuffer.Enqueue(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Log.Error(err);
|
||||
}
|
||||
|
||||
OnMessageImpl(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDataQueueHandler
|
||||
|
||||
/// <summary>
|
||||
/// Sets the job we're subscribing for
|
||||
/// </summary>
|
||||
/// <param name="job">Job we're subscribing for</param>
|
||||
public void SetJob(LiveNodePacket job)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to the specified configuration
|
||||
/// </summary>
|
||||
/// <param name="dataConfig">defines the parameters to subscribe to a data feed</param>
|
||||
/// <param name="newDataAvailableHandler">handler to be fired on new data available</param>
|
||||
/// <returns>The new enumerator for this subscription request</returns>
|
||||
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
|
||||
{
|
||||
if (!CanSubscribe(dataConfig.Symbol))
|
||||
{
|
||||
return Enumerable.Empty<BaseData>().GetEnumerator();
|
||||
}
|
||||
|
||||
var enumerator = _aggregator.Add(dataConfig, newDataAvailableHandler);
|
||||
SubscriptionManager.Subscribe(dataConfig);
|
||||
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified configuration
|
||||
/// </summary>
|
||||
/// <param name="dataConfig">Subscription config to be removed</param>
|
||||
public void Unsubscribe(SubscriptionDataConfig dataConfig)
|
||||
{
|
||||
SubscriptionManager.Unsubscribe(dataConfig);
|
||||
_aggregator.Remove(dataConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this brokerage supports the specified symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol</param>
|
||||
/// <returns>returns true if brokerage supports the specified symbol; otherwise false</returns>
|
||||
private static bool CanSubscribe(Symbol symbol)
|
||||
{
|
||||
return !symbol.Value.Contains("UNIVERSE") &&
|
||||
symbol.SecurityType == SecurityType.Crypto;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
_keepAliveTimer.DisposeSafely();
|
||||
_reconnectTimer.DisposeSafely();
|
||||
_apiClient.DisposeSafely();
|
||||
_webSocketRateLimiter.DisposeSafely();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the requested symbols (using an individual streaming channel)
|
||||
/// </summary>
|
||||
/// <param name="symbols">The list of symbols to subscribe</param>
|
||||
public override void Subscribe(IEnumerable<Symbol> symbols)
|
||||
{
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
Send(WebSocket,
|
||||
new
|
||||
{
|
||||
method = "SUBSCRIBE",
|
||||
@params = new[]
|
||||
{
|
||||
$"{symbol.Value.ToLowerInvariant()}@trade",
|
||||
$"{symbol.Value.ToLowerInvariant()}@bookTicker"
|
||||
},
|
||||
id = GetNextRequestId()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends current subscriptions
|
||||
/// </summary>
|
||||
private bool Unsubscribe(IEnumerable<Symbol> symbols)
|
||||
{
|
||||
if (WebSocket.IsOpen)
|
||||
{
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
Send(WebSocket,
|
||||
new
|
||||
{
|
||||
method = "UNSUBSCRIBE",
|
||||
@params = new[]
|
||||
{
|
||||
$"{symbol.Value.ToLowerInvariant()}@trade",
|
||||
$"{symbol.Value.ToLowerInvariant()}@bookTicker"
|
||||
},
|
||||
id = GetNextRequestId()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Send(IWebSocket webSocket, object obj)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(obj);
|
||||
|
||||
if (!_webSocketRateLimiter.WaitToProceed(TimeSpan.Zero))
|
||||
{
|
||||
_webSocketRateLimiter.WaitToProceed();
|
||||
}
|
||||
|
||||
Log.Trace("Send: " + json);
|
||||
|
||||
webSocket.Send(json);
|
||||
}
|
||||
|
||||
private long GetNextRequestId()
|
||||
{
|
||||
return Interlocked.Increment(ref _lastRequestId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event invocator for the OrderFilled event
|
||||
/// </summary>
|
||||
/// <param name="e">The OrderEvent</param>
|
||||
private void OnOrderSubmit(BinanceOrderSubmitEventArgs e)
|
||||
{
|
||||
var brokerId = e.BrokerId;
|
||||
var order = e.Order;
|
||||
if (CachedOrderIDs.ContainsKey(order.Id))
|
||||
{
|
||||
CachedOrderIDs[order.Id].BrokerId.Clear();
|
||||
CachedOrderIDs[order.Id].BrokerId.Add(brokerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
order.BrokerId.Add(brokerId);
|
||||
CachedOrderIDs.TryAdd(order.Id, order);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Brokerages/Binance/BinanceBrokerageFactory.cs
Normal file
88
Brokerages/Binance/BinanceBrokerageFactory.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Util;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory method to create binance Websockets brokerage
|
||||
/// </summary>
|
||||
public class BinanceBrokerageFactory : BrokerageFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory constructor
|
||||
/// </summary>
|
||||
public BinanceBrokerageFactory() : base(typeof(BinanceBrokerage))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not required
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// provides brokerage connection data
|
||||
/// </summary>
|
||||
public override Dictionary<string, string> BrokerageData => new Dictionary<string, string>
|
||||
{
|
||||
{ "binance-api-key", Config.Get("binance-api-key")},
|
||||
{ "binance-api-secret", Config.Get("binance-api-secret")}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The brokerage model
|
||||
/// </summary>
|
||||
/// <param name="orderProvider">The order provider</param>
|
||||
public override IBrokerageModel GetBrokerageModel(IOrderProvider orderProvider) => new BinanceBrokerageModel();
|
||||
|
||||
/// <summary>
|
||||
/// Create the Brokerage instance
|
||||
/// </summary>
|
||||
/// <param name="job"></param>
|
||||
/// <param name="algorithm"></param>
|
||||
/// <returns></returns>
|
||||
public override IBrokerage CreateBrokerage(Packets.LiveNodePacket job, IAlgorithm algorithm)
|
||||
{
|
||||
var required = new[] { "binance-api-secret", "binance-api-key" };
|
||||
|
||||
foreach (var item in required)
|
||||
{
|
||||
if (string.IsNullOrEmpty(job.BrokerageData[item]))
|
||||
{
|
||||
throw new Exception($"BinanceBrokerageFactory.CreateBrokerage: Missing {item} in config.json");
|
||||
}
|
||||
}
|
||||
|
||||
var brokerage = new BinanceBrokerage(
|
||||
job.BrokerageData["binance-api-key"],
|
||||
job.BrokerageData["binance-api-secret"],
|
||||
algorithm,
|
||||
Composer.Instance.GetExportedValueByTypeName<IDataAggregator>(Config.Get("data-aggregator", "QuantConnect.Lean.Engine.DataFeeds.AggregationManager")));
|
||||
Composer.Instance.AddPart<IDataQueueHandler>(brokerage);
|
||||
|
||||
return brokerage;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Brokerages/Binance/BinanceOrderSubmitEventArgs.cs
Normal file
46
Brokerages/Binance/BinanceOrderSubmitEventArgs.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 QuantConnect.Orders;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a binance submit order event data
|
||||
/// </summary>
|
||||
public class BinanceOrderSubmitEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Order Event Constructor.
|
||||
/// </summary>
|
||||
/// <param name="brokerId">Binance order id returned from brokerage</param>
|
||||
/// <param name="order">Order for this order placement</param>
|
||||
public BinanceOrderSubmitEventArgs(string brokerId, Order order)
|
||||
{
|
||||
BrokerId = brokerId;
|
||||
Order = order;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Original brokerage id
|
||||
/// </summary>
|
||||
public string BrokerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The lean order
|
||||
/// </summary>
|
||||
public Order Order { get; set; }
|
||||
}
|
||||
}
|
||||
590
Brokerages/Binance/BinanceRestApiClient.cs
Normal file
590
Brokerages/Binance/BinanceRestApiClient.cs
Normal file
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
* 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 Newtonsoft.Json.Linq;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using QuantConnect.Securities;
|
||||
using QuantConnect.Util;
|
||||
using RestSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
/// <summary>
|
||||
/// Binance REST API implementation
|
||||
/// </summary>
|
||||
public class BinanceRestApiClient : IDisposable
|
||||
{
|
||||
private const string RestApiUrl = "https://api.binance.com";
|
||||
private const string UserDataStreamEndpoint = "/api/v3/userDataStream";
|
||||
|
||||
private readonly SymbolPropertiesDatabaseSymbolMapper _symbolMapper;
|
||||
private readonly ISecurityProvider _securityProvider;
|
||||
private readonly IRestClient _restClient;
|
||||
private readonly RateGate _restRateLimiter = new RateGate(10, TimeSpan.FromSeconds(1));
|
||||
private readonly object _listenKeyLocker = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires each time an order is filled
|
||||
/// </summary>
|
||||
public event EventHandler<BinanceOrderSubmitEventArgs> OrderSubmit;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires each time an order is filled
|
||||
/// </summary>
|
||||
public event EventHandler<OrderEvent> OrderStatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when an error is encountered in the brokerage
|
||||
/// </summary>
|
||||
public event EventHandler<BrokerageMessageEvent> Message;
|
||||
|
||||
/// <summary>
|
||||
/// Key Header
|
||||
/// </summary>
|
||||
public readonly string KeyHeader = "X-MBX-APIKEY";
|
||||
|
||||
/// <summary>
|
||||
/// The api secret
|
||||
/// </summary>
|
||||
protected string ApiSecret;
|
||||
|
||||
/// <summary>
|
||||
/// The api key
|
||||
/// </summary>
|
||||
protected string ApiKey;
|
||||
|
||||
/// <summary>
|
||||
/// Represents UserData Session listen key
|
||||
/// </summary>
|
||||
public string SessionId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BinanceRestApiClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="symbolMapper">The symbol mapper.</param>
|
||||
/// <param name="securityProvider">The holdings provider.</param>
|
||||
/// <param name="apiKey">The Binance API key</param>
|
||||
/// <param name="apiSecret">The The Binance API secret</param>
|
||||
public BinanceRestApiClient(SymbolPropertiesDatabaseSymbolMapper symbolMapper, ISecurityProvider securityProvider, string apiKey, string apiSecret)
|
||||
{
|
||||
_symbolMapper = symbolMapper;
|
||||
_securityProvider = securityProvider;
|
||||
_restClient = new RestClient(RestApiUrl);
|
||||
ApiKey = apiKey;
|
||||
ApiSecret = apiSecret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all open positions
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Holding> GetAccountHoldings()
|
||||
{
|
||||
return new List<Holding>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total account cash balance for specified account type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Messages.AccountInformation GetCashBalance()
|
||||
{
|
||||
var queryString = $"timestamp={GetNonce()}";
|
||||
var endpoint = $"/api/v3/account?{queryString}&signature={AuthenticationToken(queryString)}";
|
||||
var request = new RestRequest(endpoint, Method.GET);
|
||||
request.AddHeader(KeyHeader, ApiKey);
|
||||
|
||||
var response = ExecuteRestRequest(request);
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"BinanceBrokerage.GetCashBalance: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<Messages.AccountInformation>(response.Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all orders not yet closed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Messages.OpenOrder> GetOpenOrders()
|
||||
{
|
||||
var queryString = $"timestamp={GetNonce()}";
|
||||
var endpoint = $"/api/v3/openOrders?{queryString}&signature={AuthenticationToken(queryString)}";
|
||||
var request = new RestRequest(endpoint, Method.GET);
|
||||
request.AddHeader(KeyHeader, ApiKey);
|
||||
|
||||
var response = ExecuteRestRequest(request);
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"BinanceBrokerage.GetCashBalance: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<Messages.OpenOrder[]>(response.Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Places a new order and assigns a new broker ID to the order
|
||||
/// </summary>
|
||||
/// <param name="order">The order to be placed</param>
|
||||
/// <returns>True if the request for a new order has been placed, false otherwise</returns>
|
||||
public bool PlaceOrder(Order order)
|
||||
{
|
||||
// supported time in force values {GTC, IOC, FOK}
|
||||
// use GTC as LEAN doesn't support others yet
|
||||
IDictionary<string, object> body = new Dictionary<string, object>()
|
||||
{
|
||||
{ "symbol", _symbolMapper.GetBrokerageSymbol(order.Symbol) },
|
||||
{ "quantity", Math.Abs(order.Quantity).ToString(CultureInfo.InvariantCulture) },
|
||||
{ "side", ConvertOrderDirection(order.Direction) }
|
||||
};
|
||||
|
||||
switch (order.Type)
|
||||
{
|
||||
case OrderType.Limit:
|
||||
body["type"] = (order.Properties as BinanceOrderProperties)?.PostOnly == true
|
||||
? "LIMIT_MAKER"
|
||||
: "LIMIT";
|
||||
body["price"] = ((LimitOrder) order).LimitPrice.ToString(CultureInfo.InvariantCulture);
|
||||
// timeInForce is not required for LIMIT_MAKER
|
||||
if (Equals(body["type"], "LIMIT"))
|
||||
body["timeInForce"] = "GTC";
|
||||
break;
|
||||
case OrderType.Market:
|
||||
body["type"] = "MARKET";
|
||||
break;
|
||||
case OrderType.StopLimit:
|
||||
var ticker = GetTickerPrice(order);
|
||||
var stopPrice = ((StopLimitOrder) order).StopPrice;
|
||||
if (order.Direction == OrderDirection.Sell)
|
||||
{
|
||||
body["type"] = stopPrice <= ticker ? "STOP_LOSS_LIMIT" : "TAKE_PROFIT_LIMIT";
|
||||
}
|
||||
else
|
||||
{
|
||||
body["type"] = stopPrice <= ticker ? "TAKE_PROFIT_LIMIT" : "STOP_LOSS_LIMIT";
|
||||
}
|
||||
body["timeInForce"] = "GTC";
|
||||
body["stopPrice"] = stopPrice.ToString(CultureInfo.InvariantCulture);
|
||||
body["price"] = ((StopLimitOrder) order).LimitPrice.ToString(CultureInfo.InvariantCulture);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"BinanceBrokerage.ConvertOrderType: Unsupported order type: {order.Type}");
|
||||
}
|
||||
|
||||
const string endpoint = "/api/v3/order";
|
||||
body["timestamp"] = GetNonce();
|
||||
body["signature"] = AuthenticationToken(body.ToQueryString());
|
||||
var request = new RestRequest(endpoint, Method.POST);
|
||||
request.AddHeader(KeyHeader, ApiKey);
|
||||
request.AddParameter(
|
||||
"application/x-www-form-urlencoded",
|
||||
Encoding.UTF8.GetBytes(body.ToQueryString()),
|
||||
ParameterType.RequestBody
|
||||
);
|
||||
|
||||
var response = ExecuteRestRequest(request);
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
var raw = JsonConvert.DeserializeObject<Messages.NewOrder>(response.Content);
|
||||
|
||||
if (string.IsNullOrEmpty(raw?.Id))
|
||||
{
|
||||
var errorMessage = $"Error parsing response from place order: {response.Content}";
|
||||
OnOrderEvent(new OrderEvent(
|
||||
order,
|
||||
DateTime.UtcNow,
|
||||
OrderFee.Zero,
|
||||
"Binance Order Event")
|
||||
{ Status = OrderStatus.Invalid, Message = errorMessage });
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, (int)response.StatusCode, errorMessage));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OnOrderSubmit(raw, order);
|
||||
return true;
|
||||
}
|
||||
|
||||
var message = $"Order failed, Order Id: {order.Id} timestamp: {order.Time} quantity: {order.Quantity} content: {response.Content}";
|
||||
OnOrderEvent(new OrderEvent(
|
||||
order,
|
||||
DateTime.UtcNow,
|
||||
OrderFee.Zero,
|
||||
"Binance Order Event")
|
||||
{ Status = OrderStatus.Invalid });
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, message));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the order with the specified ID
|
||||
/// </summary>
|
||||
/// <param name="order">The order to cancel</param>
|
||||
/// <returns>True if the request was submitted for cancellation, false otherwise</returns>
|
||||
public bool CancelOrder(Order order)
|
||||
{
|
||||
var success = new List<bool>();
|
||||
IDictionary<string, object> body = new Dictionary<string, object>()
|
||||
{
|
||||
{ "symbol", _symbolMapper.GetBrokerageSymbol(order.Symbol) }
|
||||
};
|
||||
foreach (var id in order.BrokerId)
|
||||
{
|
||||
if (body.ContainsKey("signature"))
|
||||
{
|
||||
body.Remove("signature");
|
||||
}
|
||||
body["orderId"] = id;
|
||||
body["timestamp"] = GetNonce();
|
||||
body["signature"] = AuthenticationToken(body.ToQueryString());
|
||||
|
||||
var request = new RestRequest("/api/v3/order", Method.DELETE);
|
||||
request.AddHeader(KeyHeader, ApiKey);
|
||||
request.AddParameter(
|
||||
"application/x-www-form-urlencoded",
|
||||
Encoding.UTF8.GetBytes(body.ToQueryString()),
|
||||
ParameterType.RequestBody
|
||||
);
|
||||
|
||||
var response = ExecuteRestRequest(request);
|
||||
success.Add(response.StatusCode == HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
var canceled = false;
|
||||
if (success.All(a => a))
|
||||
{
|
||||
OnOrderEvent(new OrderEvent(order,
|
||||
DateTime.UtcNow,
|
||||
OrderFee.Zero,
|
||||
"Binance Order Event")
|
||||
{ Status = OrderStatus.Canceled });
|
||||
|
||||
canceled = true;
|
||||
}
|
||||
return canceled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the history for the requested security
|
||||
/// </summary>
|
||||
/// <param name="request">The historical data request</param>
|
||||
/// <returns>An enumerable of bars covering the span specified in the request</returns>
|
||||
public IEnumerable<Messages.Kline> GetHistory(Data.HistoryRequest request)
|
||||
{
|
||||
var resolution = ConvertResolution(request.Resolution);
|
||||
var resolutionInMs = (long)request.Resolution.ToTimeSpan().TotalMilliseconds;
|
||||
var symbol = _symbolMapper.GetBrokerageSymbol(request.Symbol);
|
||||
var startMs = (long)Time.DateTimeToUnixTimeStamp(request.StartTimeUtc) * 1000;
|
||||
var endMs = (long)Time.DateTimeToUnixTimeStamp(request.EndTimeUtc) * 1000;
|
||||
var endpoint = $"/api/v3/klines?symbol={symbol}&interval={resolution}&limit=1000";
|
||||
|
||||
while (endMs - startMs >= resolutionInMs)
|
||||
{
|
||||
var timeframe = $"&startTime={startMs}&endTime={endMs}";
|
||||
|
||||
var restRequest = new RestRequest(endpoint + timeframe, Method.GET);
|
||||
var response = ExecuteRestRequest(restRequest);
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"BinanceBrokerage.GetHistory: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
var klines = JsonConvert.DeserializeObject<object[][]>(response.Content)
|
||||
.Select(entries => new Messages.Kline(entries))
|
||||
.ToList();
|
||||
|
||||
startMs = klines.Last().OpenTime + resolutionInMs;
|
||||
|
||||
foreach (var kline in klines)
|
||||
{
|
||||
yield return kline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check User Data stream listen key is alive
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool SessionKeepAlive()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SessionId))
|
||||
{
|
||||
throw new Exception("BinanceBrokerage:UserStream. listenKey wasn't allocated or has been refused.");
|
||||
}
|
||||
|
||||
var ping = new RestRequest(UserDataStreamEndpoint, Method.PUT);
|
||||
ping.AddHeader(KeyHeader, ApiKey);
|
||||
ping.AddParameter(
|
||||
"application/x-www-form-urlencoded",
|
||||
Encoding.UTF8.GetBytes($"listenKey={SessionId}"),
|
||||
ParameterType.RequestBody
|
||||
);
|
||||
|
||||
var pong = ExecuteRestRequest(ping);
|
||||
return pong.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the session
|
||||
/// </summary>
|
||||
public void StopSession()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SessionId))
|
||||
{
|
||||
throw new Exception("BinanceBrokerage:UserStream. listenKey wasn't allocated or has been refused.");
|
||||
}
|
||||
|
||||
var request = new RestRequest(UserDataStreamEndpoint, Method.DELETE);
|
||||
request.AddHeader(KeyHeader, ApiKey);
|
||||
request.AddParameter(
|
||||
"application/x-www-form-urlencoded",
|
||||
Encoding.UTF8.GetBytes($"listenKey={SessionId}"),
|
||||
ParameterType.RequestBody
|
||||
);
|
||||
ExecuteRestRequest(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the current tickers price
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Messages.PriceTicker[] GetTickers()
|
||||
{
|
||||
const string endpoint = "/api/v3/ticker/price";
|
||||
var req = new RestRequest(endpoint, Method.GET);
|
||||
var response = ExecuteRestRequest(req);
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"BinanceBrokerage.GetTick: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<Messages.PriceTicker[]>(response.Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start user data stream
|
||||
/// </summary>
|
||||
public void CreateListenKey()
|
||||
{
|
||||
var request = new RestRequest(UserDataStreamEndpoint, Method.POST);
|
||||
request.AddHeader(KeyHeader, ApiKey);
|
||||
|
||||
var response = ExecuteRestRequest(request);
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"BinanceBrokerage.StartSession: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
var content = JObject.Parse(response.Content);
|
||||
lock (_listenKeyLocker)
|
||||
{
|
||||
SessionId = content.Value<string>("listenKey");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_restRateLimiter.DisposeSafely();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If an IP address exceeds a certain number of requests per minute
|
||||
/// HTTP 429 return code is used when breaking a request rate limit.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
private IRestResponse ExecuteRestRequest(IRestRequest request)
|
||||
{
|
||||
const int maxAttempts = 10;
|
||||
var attempts = 0;
|
||||
IRestResponse response;
|
||||
|
||||
do
|
||||
{
|
||||
if (!_restRateLimiter.WaitToProceed(TimeSpan.Zero))
|
||||
{
|
||||
Log.Trace("Brokerage.OnMessage(): " + new BrokerageMessageEvent(BrokerageMessageType.Warning, "RateLimit",
|
||||
"The API request has been rate limited. To avoid this message, please reduce the frequency of API calls."));
|
||||
|
||||
_restRateLimiter.WaitToProceed();
|
||||
}
|
||||
|
||||
response = _restClient.Execute(request);
|
||||
// 429 status code: Too Many Requests
|
||||
} while (++attempts < maxAttempts && (int)response.StatusCode == 429);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private decimal GetTickerPrice(Order order)
|
||||
{
|
||||
var security = _securityProvider.GetSecurity(order.Symbol);
|
||||
var tickerPrice = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice;
|
||||
if (tickerPrice == 0)
|
||||
{
|
||||
var brokerageSymbol = _symbolMapper.GetBrokerageSymbol(order.Symbol);
|
||||
var tickers = GetTickers();
|
||||
var ticker = tickers.FirstOrDefault(t => t.Symbol == brokerageSymbol);
|
||||
if (ticker == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"BinanceBrokerage: Unable to resolve currency conversion pair: {order.Symbol}");
|
||||
}
|
||||
tickerPrice = ticker.Price;
|
||||
}
|
||||
return tickerPrice;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp in milliseconds
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private long GetNonce()
|
||||
{
|
||||
return (long)(Time.TimeStamp() * 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a signature for signed endpoints
|
||||
/// </summary>
|
||||
/// <param name="payload">the body of the request</param>
|
||||
/// <returns>a token representing the request params</returns>
|
||||
private string AuthenticationToken(string payload)
|
||||
{
|
||||
using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(ApiSecret)))
|
||||
{
|
||||
return hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)).ToHexString();
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConvertOrderDirection(OrderDirection orderDirection)
|
||||
{
|
||||
if (orderDirection == OrderDirection.Buy || orderDirection == OrderDirection.Sell)
|
||||
{
|
||||
return orderDirection.ToString().LazyToUpper();
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"BinanceBrokerage.ConvertOrderDirection: Unsupported order direction: {orderDirection}");
|
||||
}
|
||||
|
||||
|
||||
private readonly Dictionary<Resolution, string> _knownResolutions = new Dictionary<Resolution, string>()
|
||||
{
|
||||
{ Resolution.Minute, "1m" },
|
||||
{ Resolution.Hour, "1h" },
|
||||
{ Resolution.Daily, "1d" }
|
||||
};
|
||||
|
||||
private string ConvertResolution(Resolution resolution)
|
||||
{
|
||||
if (_knownResolutions.ContainsKey(resolution))
|
||||
{
|
||||
return _knownResolutions[resolution];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"BinanceBrokerage.ConvertResolution: Unsupported resolution type: {resolution}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event invocator for the OrderFilled event
|
||||
/// </summary>
|
||||
/// <param name="newOrder">The brokerage order submit result</param>
|
||||
/// <param name="order">The lean order</param>
|
||||
private void OnOrderSubmit(Messages.NewOrder newOrder, Order order)
|
||||
{
|
||||
try
|
||||
{
|
||||
OrderSubmit?.Invoke(
|
||||
this,
|
||||
new BinanceOrderSubmitEventArgs(newOrder.Id, order));
|
||||
|
||||
// Generate submitted event
|
||||
OnOrderEvent(new OrderEvent(
|
||||
order,
|
||||
Time.UnixMillisecondTimeStampToDateTime(newOrder.TransactionTime),
|
||||
OrderFee.Zero,
|
||||
"Binance Order Event")
|
||||
{ Status = OrderStatus.Submitted }
|
||||
);
|
||||
Log.Trace($"Order submitted successfully - OrderId: {order.Id}");
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Log.Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event invocator for the OrderFilled event
|
||||
/// </summary>
|
||||
/// <param name="e">The OrderEvent</param>
|
||||
private void OnOrderEvent(OrderEvent e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Brokerage.OnOrderEvent(): " + e);
|
||||
|
||||
OrderStatusChanged?.Invoke(this, e);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Log.Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event invocator for the Message event
|
||||
/// </summary>
|
||||
/// <param name="e">The error</param>
|
||||
protected virtual void OnMessage(BrokerageMessageEvent e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.Type == BrokerageMessageType.Error)
|
||||
{
|
||||
Log.Error("Brokerage.OnMessage(): " + e);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Trace("Brokerage.OnMessage(): " + e);
|
||||
}
|
||||
|
||||
Message?.Invoke(this, e);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Log.Error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Brokerages/Binance/BinanceUtil.cs
Normal file
60
Brokerages/Binance/BinanceUtil.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.Orders;
|
||||
using RestSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
/// <summary>
|
||||
/// Binance utility methods
|
||||
/// </summary>
|
||||
public class BinanceUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert binance status string value to native Lean OrderStatus
|
||||
/// </summary>
|
||||
/// <param name="status">The Binance order status value</param>
|
||||
/// <returns>Lean order status</returns>
|
||||
public static OrderStatus ConvertOrderStatus(string status)
|
||||
{
|
||||
switch (status.LazyToUpper())
|
||||
{
|
||||
case "NEW":
|
||||
return OrderStatus.New;
|
||||
case "PARTIALLY_FILLED":
|
||||
return OrderStatus.PartiallyFilled;
|
||||
case "FILLED":
|
||||
return OrderStatus.Filled;
|
||||
case "PENDING_CANCEL":
|
||||
return OrderStatus.CancelPending;
|
||||
case "CANCELED":
|
||||
return OrderStatus.Canceled;
|
||||
case "REJECTED":
|
||||
case "EXPIRED":
|
||||
return OrderStatus.Invalid;
|
||||
default:
|
||||
return Orders.OrderStatus.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Brokerages/Binance/BinanceWebSocketWrapper.cs
Normal file
44
Brokerages/Binance/BinanceWebSocketWrapper.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper class for a Binance websocket connection
|
||||
/// </summary>
|
||||
public class BinanceWebSocketWrapper : WebSocketClientWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique Id for the connection
|
||||
/// </summary>
|
||||
public string ConnectionId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The handler for the connection
|
||||
/// </summary>
|
||||
public IConnectionHandler ConnectionHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BinanceWebSocketWrapper"/> class.
|
||||
/// </summary>
|
||||
public BinanceWebSocketWrapper(IConnectionHandler connectionHandler)
|
||||
{
|
||||
ConnectionId = Guid.NewGuid().ToString();
|
||||
ConnectionHandler = connectionHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
209
Brokerages/Binance/Messages.cs
Normal file
209
Brokerages/Binance/Messages.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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.Orders;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace QuantConnect.Brokerages.Binance.Messages
|
||||
{
|
||||
#pragma warning disable 1591
|
||||
|
||||
public class AccountInformation
|
||||
{
|
||||
public Balance[] Balances { get; set; }
|
||||
|
||||
public class Balance
|
||||
{
|
||||
public string Asset { get; set; }
|
||||
public decimal Free { get; set; }
|
||||
public decimal Locked { get; set; }
|
||||
public decimal Amount => Free + Locked;
|
||||
}
|
||||
}
|
||||
|
||||
public class PriceTicker
|
||||
{
|
||||
public string Symbol { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
}
|
||||
|
||||
public class Order
|
||||
{
|
||||
[JsonProperty("orderId")]
|
||||
public string Id { get; set; }
|
||||
public string Symbol { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal StopPrice { get; set; }
|
||||
[JsonProperty("origQty")]
|
||||
public decimal OriginalAmount { get; set; }
|
||||
[JsonProperty("executedQty")]
|
||||
public decimal ExecutedAmount { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Side { get; set; }
|
||||
|
||||
public decimal Quantity => string.Equals(Side, "buy", StringComparison.OrdinalIgnoreCase) ? OriginalAmount : -OriginalAmount;
|
||||
}
|
||||
|
||||
public class OpenOrder : Order
|
||||
{
|
||||
public long Time { get; set; }
|
||||
}
|
||||
|
||||
public class NewOrder : Order
|
||||
{
|
||||
[JsonProperty("transactTime")]
|
||||
public long TransactionTime { get; set; }
|
||||
}
|
||||
|
||||
public enum EventType
|
||||
{
|
||||
None,
|
||||
OrderBook,
|
||||
Trade,
|
||||
Execution
|
||||
}
|
||||
|
||||
public class ErrorMessage
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
[JsonProperty("msg")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class BestBidAskQuote
|
||||
{
|
||||
[JsonProperty("u")]
|
||||
public long OrderBookUpdateId { get; set; }
|
||||
|
||||
[JsonProperty("s")]
|
||||
public string Symbol { get; set; }
|
||||
|
||||
[JsonProperty("b")]
|
||||
public decimal BestBidPrice { get; set; }
|
||||
|
||||
[JsonProperty("B")]
|
||||
public decimal BestBidSize { get; set; }
|
||||
|
||||
[JsonProperty("a")]
|
||||
public decimal BestAskPrice { get; set; }
|
||||
|
||||
[JsonProperty("A")]
|
||||
public decimal BestAskSize { get; set; }
|
||||
}
|
||||
|
||||
public class BaseMessage
|
||||
{
|
||||
public virtual EventType @Event { get; } = EventType.None;
|
||||
|
||||
[JsonProperty("e")]
|
||||
public string EventName { get; set; }
|
||||
|
||||
[JsonProperty("E")]
|
||||
public long Time { get; set; }
|
||||
|
||||
[JsonProperty("s")]
|
||||
public string Symbol { get; set; }
|
||||
}
|
||||
|
||||
public class Trade : BaseMessage
|
||||
{
|
||||
public override EventType @Event => EventType.Trade;
|
||||
|
||||
[JsonProperty("T")]
|
||||
public new long Time { get; set; }
|
||||
|
||||
[JsonProperty("p")]
|
||||
public decimal Price { get; private set; }
|
||||
|
||||
[JsonProperty("q")]
|
||||
public decimal Quantity { get; private set; }
|
||||
}
|
||||
|
||||
public class Execution : BaseMessage
|
||||
{
|
||||
public override EventType @Event => EventType.Execution;
|
||||
|
||||
[JsonProperty("i")]
|
||||
public string OrderId { get; set; }
|
||||
|
||||
[JsonProperty("t")]
|
||||
public string TradeId { get; set; }
|
||||
|
||||
[JsonProperty("I")]
|
||||
public string Ignore { get; set; }
|
||||
|
||||
[JsonProperty("x")]
|
||||
public string ExecutionType { get; private set; }
|
||||
|
||||
[JsonProperty("X")]
|
||||
public string OrderStatus { get; private set; }
|
||||
|
||||
[JsonProperty("T")]
|
||||
public long TransactionTime { get; set; }
|
||||
|
||||
[JsonProperty("L")]
|
||||
public decimal LastExecutedPrice { get; set; }
|
||||
|
||||
[JsonProperty("l")]
|
||||
public decimal LastExecutedQuantity { get; set; }
|
||||
|
||||
[JsonProperty("S")]
|
||||
public string Side { get; set; }
|
||||
|
||||
[JsonProperty("n")]
|
||||
public decimal Fee { get; set; }
|
||||
|
||||
[JsonProperty("N")]
|
||||
public string FeeCurrency { get; set; }
|
||||
|
||||
public OrderDirection Direction => Side.Equals("BUY", StringComparison.OrdinalIgnoreCase) ? OrderDirection.Buy : OrderDirection.Sell;
|
||||
}
|
||||
|
||||
public class Kline
|
||||
{
|
||||
public long OpenTime { get; }
|
||||
public decimal Open { get; }
|
||||
public decimal Close { get; }
|
||||
public decimal High { get; }
|
||||
public decimal Low { get; }
|
||||
public decimal Volume { get; }
|
||||
|
||||
public Kline() { }
|
||||
|
||||
public Kline(long msts, decimal close)
|
||||
{
|
||||
OpenTime = msts;
|
||||
Open = Close = High = Low = close;
|
||||
Volume = 0;
|
||||
}
|
||||
|
||||
public Kline(object[] entries)
|
||||
{
|
||||
OpenTime = Convert.ToInt64(entries[0], CultureInfo.InvariantCulture);
|
||||
Open = ((string)entries[1]).ToDecimal();
|
||||
High = ((string)entries[2]).ToDecimal();
|
||||
Low = ((string)entries[3]).ToDecimal();
|
||||
Close = ((string)entries[4]).ToDecimal();
|
||||
Volume = ((string)entries[5]).ToDecimal();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
@@ -379,7 +379,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
}
|
||||
|
||||
var symbol = _symbolMapper.GetLeanSymbol(update.Symbol);
|
||||
var symbol = _symbolMapper.GetLeanSymbol(update.Symbol, SecurityType.Crypto, Market.Bitfinex);
|
||||
var fillPrice = update.ExecPrice;
|
||||
var fillQuantity = update.ExecAmount;
|
||||
var direction = fillQuantity < 0 ? OrderDirection.Sell : OrderDirection.Buy;
|
||||
@@ -399,6 +399,15 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
: OrderStatus.PartiallyFilled;
|
||||
}
|
||||
|
||||
if (_algorithm.BrokerageModel.AccountType == AccountType.Cash &&
|
||||
order.Direction == OrderDirection.Buy)
|
||||
{
|
||||
// fees are debited in the base currency, so we have to subtract them from the filled quantity
|
||||
fillQuantity -= orderFee.Value.Amount;
|
||||
|
||||
orderFee = new ModifiedFillQuantityOrderFee(orderFee.Value);
|
||||
}
|
||||
|
||||
var orderEvent = new OrderEvent
|
||||
(
|
||||
order.Id, symbol, updTime, status,
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
{
|
||||
var holding = new Holding
|
||||
{
|
||||
Symbol = _symbolMapper.GetLeanSymbol(position.Symbol),
|
||||
Symbol = _symbolMapper.GetLeanSymbol(position.Symbol, SecurityType.Crypto, Market.Bitfinex),
|
||||
AveragePrice = position.BasePrice,
|
||||
Quantity = position.Amount,
|
||||
UnrealizedPnL = position.ProfitLoss,
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
/// </summary>
|
||||
public partial class BitfinexBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
|
||||
{
|
||||
private readonly BitfinexSymbolMapper _symbolMapper = new BitfinexSymbolMapper();
|
||||
private readonly SymbolPropertiesDatabaseSymbolMapper _symbolMapper = new SymbolPropertiesDatabaseSymbolMapper(Market.Bitfinex);
|
||||
|
||||
#region IBrokerage
|
||||
/// <summary>
|
||||
@@ -201,7 +201,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
order.Quantity = item.Amount;
|
||||
order.BrokerId = new List<string> { item.Id.ToStringInvariant() };
|
||||
order.Symbol = _symbolMapper.GetLeanSymbol(item.Symbol);
|
||||
order.Symbol = _symbolMapper.GetLeanSymbol(item.Symbol, SecurityType.Crypto, Market.Bitfinex);
|
||||
order.Time = Time.UnixMillisecondTimeStampToDateTime(item.MtsCreate);
|
||||
order.Status = ConvertOrderStatus(item);
|
||||
order.Price = item.Price;
|
||||
@@ -444,8 +444,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
{
|
||||
var symbol = dataConfig.Symbol;
|
||||
if (symbol.Value.Contains("UNIVERSE") ||
|
||||
!_symbolMapper.IsKnownLeanSymbol(symbol) ||
|
||||
symbol.SecurityType != _symbolMapper.GetLeanSecurityType(symbol.Value))
|
||||
!_symbolMapper.IsKnownLeanSymbol(symbol))
|
||||
{
|
||||
return Enumerable.Empty<BaseData>().GetEnumerator();
|
||||
}
|
||||
|
||||
@@ -48,13 +48,13 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
private volatile int _subscribeErrorCode;
|
||||
private readonly object _locker = new object();
|
||||
private readonly BitfinexBrokerage _brokerage;
|
||||
private readonly BitfinexSymbolMapper _symbolMapper;
|
||||
private readonly ISymbolMapper _symbolMapper;
|
||||
private readonly RateGate _connectionRateLimiter = new RateGate(5, TimeSpan.FromMinutes(1));
|
||||
private readonly ConcurrentDictionary<Symbol, List<BitfinexWebSocketWrapper>> _subscriptionsBySymbol = new ConcurrentDictionary<Symbol, List<BitfinexWebSocketWrapper>>();
|
||||
private readonly ConcurrentDictionary<BitfinexWebSocketWrapper, List<Channel>> _channelsByWebSocket = new ConcurrentDictionary<BitfinexWebSocketWrapper, List<Channel>>();
|
||||
private readonly ConcurrentDictionary<int, Channel> _channels = new ConcurrentDictionary<int, Channel>();
|
||||
private readonly ConcurrentDictionary<BitfinexWebSocketWrapper, BitfinexWebSocketChannels> _channelsByWebSocket = new ConcurrentDictionary<BitfinexWebSocketWrapper, BitfinexWebSocketChannels>();
|
||||
private readonly ConcurrentDictionary<Symbol, DefaultOrderBook> _orderBooks = new ConcurrentDictionary<Symbol, DefaultOrderBook>();
|
||||
private readonly IReadOnlyDictionary<TickType, string> _tickType2ChannelName = new Dictionary<TickType, string>() {
|
||||
private readonly IReadOnlyDictionary<TickType, string> _tickType2ChannelName = new Dictionary<TickType, string>
|
||||
{
|
||||
{ TickType.Trade, "trades"},
|
||||
{ TickType.Quote, "book"}
|
||||
};
|
||||
@@ -64,7 +64,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitfinexSubscriptionManager"/> class.
|
||||
/// </summary>
|
||||
public BitfinexSubscriptionManager(BitfinexBrokerage brokerage, string wssUrl, BitfinexSymbolMapper symbolMapper)
|
||||
public BitfinexSubscriptionManager(BitfinexBrokerage brokerage, string wssUrl, ISymbolMapper symbolMapper)
|
||||
{
|
||||
_brokerage = brokerage;
|
||||
_wssUrl = wssUrl;
|
||||
@@ -101,7 +101,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
return v;
|
||||
});
|
||||
|
||||
Log.Trace($"BitfinexBrokerage.Subscribe(): Sent subscribe for {symbol.Value}.");
|
||||
Log.Trace($"BitfinexBrokerage.Subscribe(): Sent subscribe for {symbol.Value}/{tickType}.");
|
||||
|
||||
if (_onSubscribeEvent.WaitOne(TimeSpan.FromSeconds(10)) && _subscribeErrorCode == 0)
|
||||
{
|
||||
@@ -109,7 +109,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Trace($"BitfinexBrokerage.Subscribe(): Could not subscribe to {symbol.Value}.");
|
||||
Log.Trace($"BitfinexBrokerage.Subscribe(): Could not subscribe to {symbol.Value}/{tickType}.");
|
||||
states.Add(false);
|
||||
}
|
||||
}
|
||||
@@ -130,24 +130,24 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
/// <param name="tickType">Type of tick data</param>
|
||||
protected override bool Unsubscribe(IEnumerable<Symbol> symbols, TickType tickType)
|
||||
{
|
||||
string channelName = ChannelNameFromTickType(tickType);
|
||||
var channelName = ChannelNameFromTickType(tickType);
|
||||
var states = new List<bool>(symbols.Count());
|
||||
foreach (var symbol in symbols)
|
||||
{
|
||||
List<BitfinexWebSocketWrapper> subscriptions;
|
||||
if (_subscriptionsBySymbol.TryGetValue(symbol, out subscriptions))
|
||||
{
|
||||
for (int i = subscriptions.Count - 1; i >= 0; i--)
|
||||
for (var i = subscriptions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var webSocket = subscriptions[i];
|
||||
_onUnsubscribeEvent.Reset();
|
||||
try
|
||||
{
|
||||
Channel channel = new Channel(channelName, symbol);
|
||||
List<Channel> channels;
|
||||
var channel = new Channel(channelName, symbol);
|
||||
BitfinexWebSocketChannels channels;
|
||||
if (_channelsByWebSocket.TryGetValue(webSocket, out channels) && channels.Contains(channel))
|
||||
{
|
||||
UnsubscribeChannel(webSocket, channel);
|
||||
UnsubscribeChannel(webSocket, channels, channel);
|
||||
|
||||
if (_onUnsubscribeEvent.WaitOne(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
@@ -199,9 +199,10 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
return webSocket;
|
||||
}
|
||||
|
||||
private void UnsubscribeChannel(IWebSocket webSocket, Channel channel)
|
||||
private void UnsubscribeChannel(IWebSocket webSocket, BitfinexWebSocketChannels channels, Channel channel)
|
||||
{
|
||||
int channelId = _channels.First(c => c.Value.Equals(channel)).Key;
|
||||
var channelId = channels.GetChannelId(channel);
|
||||
|
||||
webSocket.Send(JsonConvert.SerializeObject(new
|
||||
{
|
||||
@event = "unsubscribe",
|
||||
@@ -211,19 +212,12 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
private BitfinexWebSocketWrapper GetFreeWebSocket(Channel channel)
|
||||
{
|
||||
int count;
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
foreach (var kvp in _channelsByWebSocket)
|
||||
{
|
||||
if (kvp.Value.Count < MaximumSubscriptionsPerSocket)
|
||||
{
|
||||
kvp.Value.Add(channel);
|
||||
|
||||
count = _channelsByWebSocket.Sum(x => x.Value.Count);
|
||||
Log.Trace($"BitfinexSubscriptionManager.GetFreeWebSocket(): Channel added: Total channels:{count}");
|
||||
|
||||
return kvp.Key;
|
||||
}
|
||||
}
|
||||
@@ -242,10 +236,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
_channelsByWebSocket.TryAdd(webSocket, new List<Channel> { channel });
|
||||
|
||||
count = _channelsByWebSocket.Sum(x => x.Value.Count);
|
||||
Log.Trace($"BitfinexSubscriptionManager.GetFreeWebSocket(): Channel added: Total channels:{count}");
|
||||
_channelsByWebSocket.TryAdd(webSocket, new BitfinexWebSocketChannels());
|
||||
}
|
||||
|
||||
webSocket.Initialize(_wssUrl);
|
||||
@@ -332,7 +323,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
Log.Trace($"BitfinexSubscriptionManager.OnReconnectRequested(): Reconnected: IsOpen:{webSocket.IsOpen} [Id: {connectionHandler.ConnectionId}]");
|
||||
|
||||
List<Channel> channels;
|
||||
BitfinexWebSocketChannels channels;
|
||||
lock (_locker)
|
||||
{
|
||||
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
|
||||
@@ -343,7 +334,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
Log.Trace($"BitfinexSubscriptionManager.OnReconnectRequested(): Resubscribing channels. [Id: {connectionHandler.ConnectionId}]");
|
||||
|
||||
foreach (var channel in channels)
|
||||
foreach (var channel in channels.Values)
|
||||
{
|
||||
webSocket.Send(JsonConvert.SerializeObject(new
|
||||
{
|
||||
@@ -380,7 +371,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
// trade execution
|
||||
case "te":
|
||||
OnUpdate(channel, token[2].ToObject<string[]>());
|
||||
OnUpdate(webSocket, channel, token[2].ToObject<string[]>());
|
||||
break;
|
||||
|
||||
// ignored -- trades already handled in "te" message
|
||||
@@ -400,6 +391,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
if (token[1][0].Type == JTokenType.Array)
|
||||
{
|
||||
OnSnapshot(
|
||||
webSocket,
|
||||
channel,
|
||||
token[1].ToObject<string[][]>()
|
||||
);
|
||||
@@ -408,6 +400,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
{
|
||||
// pass channel id as separate arg
|
||||
OnUpdate(
|
||||
webSocket,
|
||||
channel,
|
||||
token[1].ToObject<string[]>()
|
||||
);
|
||||
@@ -416,7 +409,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
else if (token is JObject)
|
||||
{
|
||||
var raw = token.ToObject<Messages.BaseMessage>();
|
||||
var raw = token.ToObject<BaseMessage>();
|
||||
switch (raw.Event.ToLowerInvariant())
|
||||
{
|
||||
case "subscribed":
|
||||
@@ -457,15 +450,25 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSubscribe(BitfinexWebSocketWrapper webSocket, Messages.ChannelSubscription data)
|
||||
private void OnSubscribe(BitfinexWebSocketWrapper webSocket, ChannelSubscription data)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
var channel = new Channel(data.Channel, _symbolMapper.GetLeanSymbol(data.Symbol));
|
||||
var channel = new Channel(data.Channel, _symbolMapper.GetLeanSymbol(data.Symbol, SecurityType.Crypto, Market.Bitfinex));
|
||||
|
||||
BitfinexWebSocketChannels channels;
|
||||
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
|
||||
{
|
||||
_onSubscribeEvent.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
channels.TryAdd(data.ChannelId, channel);
|
||||
|
||||
Log.Trace($"BitfinexSubscriptionManager.OnSubscribe(): Channel subscribed: Id:{data.ChannelId} {channel.Symbol}/{channel.Name}");
|
||||
|
||||
_channels.AddOrUpdate(data.ChannelId, channel);
|
||||
_onSubscribeEvent.Set();
|
||||
|
||||
webSocket.ConnectionHandler.EnableMonitoring(true);
|
||||
@@ -478,23 +481,27 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnsubscribe(BitfinexWebSocketWrapper webSocket, Messages.ChannelUnsubscribing data)
|
||||
private void OnUnsubscribe(BitfinexWebSocketWrapper webSocket, ChannelUnsubscribing data)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
BitfinexWebSocketChannels channels;
|
||||
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel;
|
||||
if (!_channels.TryRemove(data.ChannelId, out channel)) return;
|
||||
if (!channels.TryRemove(data.ChannelId, out channel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onUnsubscribeEvent.Set();
|
||||
|
||||
List<Channel> channels;
|
||||
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels)) return;
|
||||
|
||||
channels.Remove(channel);
|
||||
|
||||
if (channels.Count(c => c.Symbol.Equals(channel.Symbol)) == 0)
|
||||
if (channels.Values.Count(c => c.Symbol.Equals(channel.Symbol)) == 0)
|
||||
{
|
||||
List<BitfinexWebSocketWrapper> subscriptions;
|
||||
if (_subscriptionsBySymbol.TryGetValue(channel.Symbol, out subscriptions))
|
||||
@@ -508,7 +515,10 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
}
|
||||
|
||||
if (channels.Count != 0) return;
|
||||
if (channels.Count != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_channelsByWebSocket.TryRemove(webSocket, out channels);
|
||||
}
|
||||
@@ -523,7 +533,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSnapshot(int channelId, string[][] entries)
|
||||
private void OnSnapshot(BitfinexWebSocketWrapper webSocket, int channelId, string[][] entries)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -531,7 +541,13 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (!_channels.TryGetValue(channelId, out channel))
|
||||
BitfinexWebSocketChannels channels;
|
||||
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channels.TryGetValue(channelId, out channel))
|
||||
{
|
||||
_brokerage.OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, $"Message received from unknown channel Id {channelId}"));
|
||||
return;
|
||||
@@ -592,7 +608,7 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUpdate(int channelId, string[] entries)
|
||||
private void OnUpdate(BitfinexWebSocketWrapper webSocket, int channelId, string[] entries)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -600,7 +616,13 @@ namespace QuantConnect.Brokerages.Bitfinex
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (!_channels.TryGetValue(channelId, out channel))
|
||||
BitfinexWebSocketChannels channels;
|
||||
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channels.TryGetValue(channelId, out channel))
|
||||
{
|
||||
_brokerage.OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, $"Message received from unknown channel Id {channelId}"));
|
||||
return;
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Securities;
|
||||
|
||||
namespace QuantConnect.Brokerages.Bitfinex
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the mapping between Lean symbols and Bitfinex symbols.
|
||||
/// </summary>
|
||||
public class BitfinexSymbolMapper : ISymbolMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of known Bitfinex symbols.
|
||||
/// https://api.bitfinex.com/v1/symbols
|
||||
/// </summary>
|
||||
public static readonly HashSet<string> KnownTickers =
|
||||
new HashSet<string>(SymbolPropertiesDatabase
|
||||
.FromDataFolder()
|
||||
.GetSymbolPropertiesList(Market.Bitfinex, SecurityType.Crypto)
|
||||
.Select(x => x.Key.Symbol));
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Lean symbol instance to an Bitfinex symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">A Lean symbol instance</param>
|
||||
/// <returns>The Bitfinex symbol</returns>
|
||||
public string GetBrokerageSymbol(Symbol symbol)
|
||||
{
|
||||
if (symbol == null || string.IsNullOrWhiteSpace(symbol.Value))
|
||||
throw new ArgumentException("Invalid symbol: " + (symbol == null ? "null" : symbol.ToString()));
|
||||
|
||||
if (symbol.ID.SecurityType != SecurityType.Crypto)
|
||||
throw new ArgumentException("Invalid security type: " + symbol.ID.SecurityType);
|
||||
|
||||
var brokerageSymbol = ConvertLeanSymbolToBitfinexSymbol(symbol.Value);
|
||||
|
||||
if (!IsKnownBrokerageSymbol(brokerageSymbol))
|
||||
throw new ArgumentException("Unknown symbol: " + symbol.Value);
|
||||
|
||||
return brokerageSymbol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an Bitfinex symbol to a Lean symbol instance
|
||||
/// </summary>
|
||||
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
|
||||
/// <param name="securityType">The security type</param>
|
||||
/// <param name="market">The market</param>
|
||||
/// <param name="expirationDate">Expiration date of the security(if applicable)</param>
|
||||
/// <param name="strike">The strike of the security (if applicable)</param>
|
||||
/// <param name="optionRight">The option right of the security (if applicable)</param>
|
||||
/// <returns>A new Lean Symbol instance</returns>
|
||||
public Symbol GetLeanSymbol(string brokerageSymbol, SecurityType securityType, string market, DateTime expirationDate = default(DateTime), decimal strike = 0, OptionRight optionRight = 0)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(brokerageSymbol))
|
||||
throw new ArgumentException($"Invalid Bitfinex symbol: {brokerageSymbol}");
|
||||
|
||||
if (!IsKnownBrokerageSymbol(brokerageSymbol))
|
||||
throw new ArgumentException($"Unknown Bitfinex symbol: {brokerageSymbol}");
|
||||
|
||||
if (securityType != SecurityType.Crypto)
|
||||
throw new ArgumentException($"Invalid security type: {securityType}");
|
||||
|
||||
if (market != Market.Bitfinex)
|
||||
throw new ArgumentException($"Invalid market: {market}");
|
||||
|
||||
return Symbol.Create(ConvertBitfinexSymbolToLeanSymbol(brokerageSymbol), GetBrokerageSecurityType(brokerageSymbol), Market.Bitfinex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an Bitfinex symbol to a Lean symbol instance
|
||||
/// </summary>
|
||||
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
|
||||
/// <returns>A new Lean Symbol instance</returns>
|
||||
public Symbol GetLeanSymbol(string brokerageSymbol)
|
||||
{
|
||||
var securityType = GetBrokerageSecurityType(brokerageSymbol);
|
||||
return GetLeanSymbol(brokerageSymbol, securityType, Market.Bitfinex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the security type for an Bitfinex symbol
|
||||
/// </summary>
|
||||
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
|
||||
/// <returns>The security type</returns>
|
||||
public SecurityType GetBrokerageSecurityType(string brokerageSymbol)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(brokerageSymbol))
|
||||
throw new ArgumentException($"Invalid Bitfinex symbol: {brokerageSymbol}");
|
||||
|
||||
if (!IsKnownBrokerageSymbol(brokerageSymbol))
|
||||
throw new ArgumentException($"Unknown Bitfinex symbol: {brokerageSymbol}");
|
||||
|
||||
return SecurityType.Crypto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the security type for a Lean symbol
|
||||
/// </summary>
|
||||
/// <param name="leanSymbol">The Lean symbol</param>
|
||||
/// <returns>The security type</returns>
|
||||
public SecurityType GetLeanSecurityType(string leanSymbol)
|
||||
{
|
||||
return GetBrokerageSecurityType(ConvertLeanSymbolToBitfinexSymbol(leanSymbol));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the symbol is supported by Bitfinex
|
||||
/// </summary>
|
||||
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
|
||||
/// <returns>True if Bitfinex supports the symbol</returns>
|
||||
public bool IsKnownBrokerageSymbol(string brokerageSymbol)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(brokerageSymbol))
|
||||
return false;
|
||||
|
||||
// Strip leading 't' char
|
||||
return KnownTickers.Contains(brokerageSymbol.Substring(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the symbol is supported by Bitfinex
|
||||
/// </summary>
|
||||
/// <param name="symbol">The Lean symbol</param>
|
||||
/// <returns>True if Bitfinex supports the symbol</returns>
|
||||
public bool IsKnownLeanSymbol(Symbol symbol)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(symbol?.Value) || symbol.Value.Length <= 3)
|
||||
return false;
|
||||
|
||||
var bitfinexSymbol = ConvertLeanSymbolToBitfinexSymbol(symbol.Value);
|
||||
|
||||
return IsKnownBrokerageSymbol(bitfinexSymbol) && GetBrokerageSecurityType(bitfinexSymbol) == symbol.ID.SecurityType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an Bitfinex symbol to a Lean symbol string
|
||||
/// </summary>
|
||||
private static string ConvertBitfinexSymbolToLeanSymbol(string bitfinexSymbol)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(bitfinexSymbol) || !bitfinexSymbol.StartsWith("t"))
|
||||
throw new ArgumentException($"Invalid Bitfinex symbol: {bitfinexSymbol}");
|
||||
|
||||
// Strip leading 't' char
|
||||
return bitfinexSymbol.Substring(1).ToUpperInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Lean symbol string to an Bitfinex symbol
|
||||
/// </summary>
|
||||
private static string ConvertLeanSymbolToBitfinexSymbol(string leanSymbol)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(leanSymbol))
|
||||
throw new ArgumentException($"Invalid Lean symbol: {leanSymbol}");
|
||||
|
||||
// Prepend 't' for Trading pairs
|
||||
return "t" + leanSymbol.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Brokerages/Bitfinex/BitfinexWebSocketChannels.cs
Normal file
47
Brokerages/Bitfinex/BitfinexWebSocketChannels.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using QuantConnect.Data;
|
||||
|
||||
namespace QuantConnect.Brokerages.Bitfinex
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the channel mappings for a WebSocket connection
|
||||
/// </summary>
|
||||
public class BitfinexWebSocketChannels : ConcurrentDictionary<int, Channel>
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the dictionary contains a specific channel.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel</param>
|
||||
/// <returns>true if the channel was found</returns>
|
||||
public bool Contains(Channel channel)
|
||||
{
|
||||
return Values.Contains(channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the channel id for the given channel.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel</param>
|
||||
/// <returns>The channel id</returns>
|
||||
public int GetChannelId(Channel channel)
|
||||
{
|
||||
return this.First(c => c.Value.Equals(channel)).Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,11 @@ namespace QuantConnect.Brokerages
|
||||
/// </summary>
|
||||
public virtual bool AccountInstantlyUpdated => false;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the brokerage account's base currency
|
||||
/// </summary>
|
||||
public virtual string AccountBaseCurrency { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the history for the requested security
|
||||
/// </summary>
|
||||
|
||||
@@ -59,8 +59,6 @@ namespace QuantConnect.Brokerages.Fxcm
|
||||
private readonly Dictionary<string, AutoResetEvent> _mapRequestsToAutoResetEvents = new Dictionary<string, AutoResetEvent>();
|
||||
private readonly HashSet<string> _pendingHistoryRequests = new HashSet<string>();
|
||||
|
||||
private string _fxcmAccountCurrency = Currencies.USD;
|
||||
|
||||
private void LoadInstruments()
|
||||
{
|
||||
// Note: requestTradingSessionStatus() MUST be called just after login
|
||||
@@ -109,7 +107,8 @@ namespace QuantConnect.Brokerages.Fxcm
|
||||
AutoResetEvent autoResetEvent;
|
||||
lock (_locker)
|
||||
{
|
||||
_currentRequest = _gateway.requestOpenOrders(_accountId);
|
||||
_currentRequest = _gateway.requestOpenOrders(null);
|
||||
|
||||
autoResetEvent = new AutoResetEvent(false);
|
||||
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
|
||||
}
|
||||
@@ -246,7 +245,7 @@ namespace QuantConnect.Brokerages.Fxcm
|
||||
}
|
||||
|
||||
// get account base currency
|
||||
_fxcmAccountCurrency = message.getParameter("BASE_CRNCY").getValue();
|
||||
AccountBaseCurrency = message.getParameter("BASE_CRNCY").getValue();
|
||||
|
||||
_mapRequestsToAutoResetEvents[_currentRequest].Set();
|
||||
_mapRequestsToAutoResetEvents.Remove(_currentRequest);
|
||||
|
||||
@@ -431,7 +431,7 @@ namespace QuantConnect.Brokerages.Fxcm
|
||||
|
||||
//Adds the account currency to the cashbook.
|
||||
cashBook.Add(new CashAmount(Convert.ToDecimal(_accounts[_accountId].getCashOutstanding()),
|
||||
_fxcmAccountCurrency));
|
||||
AccountBaseCurrency));
|
||||
|
||||
// include cash balances from currency swaps for open Forex positions
|
||||
foreach (var trade in _openPositions.Values)
|
||||
|
||||
@@ -26,7 +26,6 @@ using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using RestSharp;
|
||||
using System.Text.RegularExpressions;
|
||||
using QuantConnect.Configuration;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Orders.Fees;
|
||||
@@ -45,13 +44,14 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<long, GDAXFill> FillSplit { get; set; }
|
||||
private readonly string _passPhrase;
|
||||
private const string SymbolMatching = "ETH|LTC|BTC|BCH|XRP|EOS|XLM|ETC|ZRX";
|
||||
private readonly IAlgorithm _algorithm;
|
||||
private readonly CancellationTokenSource _canceller = new CancellationTokenSource();
|
||||
private readonly ConcurrentDictionary<Symbol, DefaultOrderBook> _orderBooks = new ConcurrentDictionary<Symbol, DefaultOrderBook>();
|
||||
private readonly bool _isDataQueueHandler;
|
||||
protected readonly IDataAggregator _aggregator;
|
||||
|
||||
private readonly SymbolPropertiesDatabaseSymbolMapper _symbolMapper = new SymbolPropertiesDatabaseSymbolMapper(Market.GDAX);
|
||||
|
||||
// GDAX has different rate limits for public and private endpoints
|
||||
// https://docs.gdax.com/#rate-limits
|
||||
internal enum GdaxEndpointType { Public, Private }
|
||||
@@ -165,7 +165,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
{
|
||||
var message = JsonConvert.DeserializeObject<Messages.Snapshot>(data);
|
||||
|
||||
var symbol = ConvertProductId(message.ProductId);
|
||||
var symbol = _symbolMapper.GetLeanSymbol(message.ProductId, SecurityType.Crypto, Market.GDAX);
|
||||
|
||||
DefaultOrderBook orderBook;
|
||||
if (!_orderBooks.TryGetValue(symbol, out orderBook))
|
||||
@@ -220,7 +220,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
{
|
||||
var message = JsonConvert.DeserializeObject<Messages.L2Update>(data);
|
||||
|
||||
var symbol = ConvertProductId(message.ProductId);
|
||||
var symbol = _symbolMapper.GetLeanSymbol(message.ProductId, SecurityType.Crypto, Market.GDAX);
|
||||
|
||||
var orderBook = _orderBooks[symbol];
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
|
||||
private void EmitFillOrderEvent(Messages.Fill fill, Order order)
|
||||
{
|
||||
var symbol = ConvertProductId(fill.ProductId);
|
||||
var symbol = _symbolMapper.GetLeanSymbol(fill.ProductId, SecurityType.Crypto, Market.GDAX);
|
||||
|
||||
if (!FillSplit.ContainsKey(order.Id))
|
||||
{
|
||||
@@ -327,7 +327,9 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
/// <returns></returns>
|
||||
public Tick GetTick(Symbol symbol)
|
||||
{
|
||||
var req = new RestRequest($"/products/{ConvertSymbol(symbol)}/ticker", Method.GET);
|
||||
var brokerageSymbol = _symbolMapper.GetBrokerageSymbol(symbol);
|
||||
|
||||
var req = new RestRequest($"/products/{brokerageSymbol}/ticker", Method.GET);
|
||||
var response = ExecuteRestRequest(req, GdaxEndpointType.Public);
|
||||
if (response.StatusCode != System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
@@ -366,7 +368,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
/// </summary>
|
||||
private void EmitTradeTick(Messages.Matched message)
|
||||
{
|
||||
var symbol = ConvertProductId(message.ProductId);
|
||||
var symbol = _symbolMapper.GetLeanSymbol(message.ProductId, SecurityType.Crypto, Market.GDAX);
|
||||
|
||||
_aggregator.Update(new Tick
|
||||
{
|
||||
@@ -387,20 +389,25 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
var pendingSymbols = new List<Symbol>();
|
||||
foreach (var item in fullList)
|
||||
{
|
||||
if (!IsSubscribeAvailable(item))
|
||||
if (_symbolMapper.IsKnownLeanSymbol(item))
|
||||
{
|
||||
pendingSymbols.Add(item);
|
||||
}
|
||||
else if (item.SecurityType == SecurityType.Crypto)
|
||||
{
|
||||
Log.Error($"Unknown GDAX symbol: {item.Value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//todo: refactor this outside brokerage
|
||||
//alternative service: http://openexchangerates.org/latest.json
|
||||
PollTick(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingSymbols.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
var products = pendingSymbols
|
||||
.Select(s => s.Value.Substring(0, 3) + "-" + s.Value.Substring(3)).ToArray();
|
||||
.Select(s => _symbolMapper.GetBrokerageSymbol(s))
|
||||
.ToArray();
|
||||
|
||||
var payload = new
|
||||
{
|
||||
@@ -484,11 +491,6 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSubscribeAvailable(Symbol symbol)
|
||||
{
|
||||
return Regex.IsMatch(symbol.Value, SymbolMatching);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends current subscriptions
|
||||
/// </summary>
|
||||
@@ -497,7 +499,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
if (WebSocket.IsOpen)
|
||||
{
|
||||
var products = symbols
|
||||
.Select(s => s.Value.Substring(0, 3) + "-" + s.Value.Substring(3))
|
||||
.Select(s => _symbolMapper.GetBrokerageSymbol(s))
|
||||
.ToArray();
|
||||
|
||||
var payload = new
|
||||
@@ -539,7 +541,12 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"GDAXBrokerage.FillMonitorAction(): request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
|
||||
OnMessage(new BrokerageMessageEvent(
|
||||
BrokerageMessageType.Warning,
|
||||
-1,
|
||||
$"GDAXBrokerage.FillMonitorAction(): request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}"));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var fills = JsonConvert.DeserializeObject<List<Messages.Fill>>(response.Content);
|
||||
|
||||
@@ -129,26 +129,6 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
throw new NotSupportedException($"GDAXBrokerage.ConvertOrderType: Unsupported order type:{orderType.ToStringInvariant()}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a product id to a symbol
|
||||
/// </summary>
|
||||
/// <param name="productId">gdax format product id</param>
|
||||
/// <returns>Symbol</returns>
|
||||
public static Symbol ConvertProductId(string productId)
|
||||
{
|
||||
return Symbol.Create(productId.Replace("-", ""), SecurityType.Crypto, Market.GDAX);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a symbol to a product id
|
||||
/// </summary>
|
||||
/// <param name="symbol">Th symbol</param>
|
||||
/// <returns>gdax product id</returns>
|
||||
protected static string ConvertSymbol(Symbol symbol)
|
||||
{
|
||||
return $"{symbol.Value.Substring(0, 3).ToUpperInvariant()}-{symbol.Value.Substring(3, 3).ToUpperInvariant()}";
|
||||
}
|
||||
|
||||
private static Orders.OrderStatus ConvertOrderStatus(Messages.Order order)
|
||||
{
|
||||
if (order.FilledSize != 0 && order.FilledSize != order.Size)
|
||||
|
||||
@@ -35,6 +35,14 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
{
|
||||
private const int MaxDataPointsPerHistoricalRequest = 300;
|
||||
|
||||
// These are the only currencies accepted for fiat deposits
|
||||
private static readonly HashSet<string> FiatCurrencies = new List<string>
|
||||
{
|
||||
Currencies.EUR,
|
||||
Currencies.GBP,
|
||||
Currencies.USD
|
||||
}.ToHashSet();
|
||||
|
||||
#region IBrokerage
|
||||
/// <summary>
|
||||
/// Checks if the websocket connection is connected or in the process of connecting
|
||||
@@ -64,7 +72,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
(order as StopMarketOrder)?.StopPrice ?? 0;
|
||||
}
|
||||
|
||||
payload.product_id = ConvertSymbol(order.Symbol);
|
||||
payload.product_id = _symbolMapper.GetBrokerageSymbol(order.Symbol);
|
||||
|
||||
if (_algorithm.BrokerageModel.AccountType == AccountType.Margin)
|
||||
{
|
||||
@@ -186,6 +194,16 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
return success.All(a => a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects the client to the broker's remote servers
|
||||
/// </summary>
|
||||
public override void Connect()
|
||||
{
|
||||
base.Connect();
|
||||
|
||||
AccountBaseCurrency = GetAccountBaseCurrency();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the websockets connection
|
||||
/// </summary>
|
||||
@@ -244,7 +262,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
|
||||
order.Quantity = item.Side == "sell" ? -item.Size : item.Size;
|
||||
order.BrokerId = new List<string> { item.Id };
|
||||
order.Symbol = ConvertProductId(item.ProductId);
|
||||
order.Symbol = _symbolMapper.GetLeanSymbol(item.ProductId, SecurityType.Crypto, Market.GDAX);
|
||||
order.Time = DateTime.UtcNow;
|
||||
order.Status = ConvertOrderStatus(item);
|
||||
order.Price = item.Price;
|
||||
@@ -321,6 +339,13 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!_symbolMapper.IsKnownLeanSymbol(request.Symbol))
|
||||
{
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidSymbol",
|
||||
$"Unknown symbol: {request.Symbol.Value}, no history returned"));
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (request.EndTimeUtc < request.StartTimeUtc)
|
||||
{
|
||||
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidDateRange",
|
||||
@@ -349,7 +374,7 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
/// <param name="request">The history request instance</param>
|
||||
private IEnumerable<TradeBar> GetHistoryFromCandles(HistoryRequest request)
|
||||
{
|
||||
var productId = ConvertSymbol(request.Symbol);
|
||||
var productId = _symbolMapper.GetBrokerageSymbol(request.Symbol);
|
||||
var granularity = Convert.ToInt32(request.Resolution.ToTimeSpan().TotalSeconds);
|
||||
|
||||
var startTime = request.StartTimeUtc;
|
||||
@@ -433,6 +458,31 @@ namespace QuantConnect.Brokerages.GDAX
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the account base currency
|
||||
/// </summary>
|
||||
private string GetAccountBaseCurrency()
|
||||
{
|
||||
var req = new RestRequest("/accounts", Method.GET);
|
||||
GetAuthenticationToken(req);
|
||||
var response = ExecuteRestRequest(req, GdaxEndpointType.Private);
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"GDAXBrokerage.GetAccountBaseCurrency(): request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
foreach (var item in JsonConvert.DeserializeObject<Messages.Account[]>(response.Content))
|
||||
{
|
||||
if (FiatCurrencies.Contains(item.Currency))
|
||||
{
|
||||
return item.Currency;
|
||||
}
|
||||
}
|
||||
|
||||
return Currencies.USD;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Event arguments class for the <see cref="InteractiveBrokersClient.AccountSummary"/> event
|
||||
/// </summary>
|
||||
public class AccountSummaryEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The request's identifier.
|
||||
/// </summary>
|
||||
public int RequestId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The account id.
|
||||
/// </summary>
|
||||
public string Account { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The account's attribute being received.
|
||||
/// </summary>
|
||||
public string Tag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The account's attribute's value.
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The currency on which the value is expressed.
|
||||
/// </summary>
|
||||
public string Currency { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AccountSummaryEventArgs"/> class
|
||||
/// </summary>
|
||||
public AccountSummaryEventArgs(int reqId, string account, string tag, string value, string currency)
|
||||
{
|
||||
RequestId = reqId;
|
||||
Account = account;
|
||||
Tag = tag;
|
||||
Value = value;
|
||||
Currency = currency;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Brokerages/InteractiveBrokers/Client/FamilyCodesEventArgs.cs
Normal file
39
Brokerages/InteractiveBrokers/Client/FamilyCodesEventArgs.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 System;
|
||||
using IBApi;
|
||||
|
||||
namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Event arguments class for the <see cref="InteractiveBrokersClient.FamilyCodes"/> event
|
||||
/// </summary>
|
||||
public class FamilyCodesEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// A comma-separated string with the managed account ids.
|
||||
/// </summary>
|
||||
public FamilyCode[] FamilyCodes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FamilyCodesEventArgs"/> class
|
||||
/// </summary>
|
||||
public FamilyCodesEventArgs(FamilyCode[] familyCodes)
|
||||
{
|
||||
FamilyCodes = familyCodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,11 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
/// </summary>
|
||||
public event EventHandler ConnectionClosed;
|
||||
|
||||
/// <summary>
|
||||
/// AccountSummary event handler
|
||||
/// </summary>
|
||||
public event EventHandler<AccountSummaryEventArgs> AccountSummary;
|
||||
|
||||
/// <summary>
|
||||
/// AccountSummaryEnd event handler
|
||||
/// </summary>
|
||||
@@ -145,6 +150,16 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
/// </summary>
|
||||
public event EventHandler ConnectAck;
|
||||
|
||||
/// <summary>
|
||||
/// ManagedAccounts event handler
|
||||
/// </summary>
|
||||
public event EventHandler<ManagedAccountsEventArgs> ManagedAccounts;
|
||||
|
||||
/// <summary>
|
||||
/// FamilyCodes event handler
|
||||
/// </summary>
|
||||
public event EventHandler<FamilyCodesEventArgs> FamilyCodes;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -258,6 +273,19 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
OnConnectionClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives the account information.
|
||||
/// </summary>
|
||||
/// <param name="reqId">The request's identifier.</param>
|
||||
/// <param name="account">The account id</param>
|
||||
/// <param name="tag">The account's attribute being received.</param>
|
||||
/// <param name="value">The account's attribute's value.</param>
|
||||
/// <param name="currency">The currency on which the value is expressed.</param>
|
||||
public override void accountSummary(int reqId, string account, string tag, string value, string currency)
|
||||
{
|
||||
OnAccountSummary(new AccountSummaryEventArgs(reqId, account, tag, value, currency));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called once all account information for a given reqAccountSummary() request are received.
|
||||
/// </summary>
|
||||
@@ -456,6 +484,24 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
OnConnectAck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives a comma-separated string with the managed account ids. Occurs automatically on initial API client connection.
|
||||
/// </summary>
|
||||
/// <param name="accountList">A comma-separated string with the managed account ids.</param>
|
||||
public override void managedAccounts(string accountList)
|
||||
{
|
||||
OnManagedAccounts(new ManagedAccountsEventArgs(accountList));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns array of family codes
|
||||
/// </summary>
|
||||
/// <param name="familyCodes">An array of family codes.</param>
|
||||
public override void familyCodes(FamilyCode[] familyCodes)
|
||||
{
|
||||
OnFamilyCodes(new FamilyCodesEventArgs(familyCodes));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Invocators
|
||||
@@ -508,6 +554,14 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
ConnectionClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AccountSummary event invocator
|
||||
/// </summary>
|
||||
protected virtual void OnAccountSummary(AccountSummaryEventArgs e)
|
||||
{
|
||||
AccountSummary?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AccountSummaryEnd event invocator
|
||||
/// </summary>
|
||||
@@ -652,6 +706,22 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
ConnectAck?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ManagedAccounts event invocator
|
||||
/// </summary>
|
||||
protected virtual void OnManagedAccounts(ManagedAccountsEventArgs e)
|
||||
{
|
||||
ManagedAccounts?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FamilyCodes event invocator
|
||||
/// </summary>
|
||||
protected virtual void OnFamilyCodes(FamilyCodesEventArgs e)
|
||||
{
|
||||
FamilyCodes?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace QuantConnect.Brokerages.InteractiveBrokers.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Event arguments class for the <see cref="InteractiveBrokersClient.ManagedAccounts"/> event
|
||||
/// </summary>
|
||||
public class ManagedAccountsEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// A comma-separated string with the managed account ids.
|
||||
/// </summary>
|
||||
public string AccountList { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManagedAccountsEventArgs"/> class
|
||||
/// </summary>
|
||||
public ManagedAccountsEventArgs(string accountList)
|
||||
{
|
||||
AccountList = accountList;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,6 @@
|
||||
"MXP": "6M",
|
||||
"RUR": "6R",
|
||||
"ZAR": "6Z",
|
||||
"BRR": "BTC"
|
||||
"BRR": "BTC",
|
||||
"DA": "DC"
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ using QuantConnect.Orders.TimeInForces;
|
||||
using QuantConnect.Securities.Option;
|
||||
using Bar = QuantConnect.Data.Market.Bar;
|
||||
using HistoryRequest = QuantConnect.Data.HistoryRequest;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
{
|
||||
@@ -55,14 +54,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
// Existing orders created in TWS can *only* be cancelled/modified when connected with ClientId = 0
|
||||
private const int ClientId = 0;
|
||||
|
||||
// next valid order id for this client
|
||||
// next valid order id (or request id, or ticker id) for this client
|
||||
private int _nextValidId;
|
||||
private readonly object _nextValidIdLocker = new object();
|
||||
|
||||
// next valid request id for queries
|
||||
private int _nextRequestId;
|
||||
private int _nextTickerId;
|
||||
|
||||
private readonly int _port;
|
||||
private readonly string _account;
|
||||
private readonly string _host;
|
||||
@@ -131,6 +126,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
private readonly bool _enableDelayedStreamingData = Config.GetBool("ib-enable-delayed-streaming-data");
|
||||
|
||||
private volatile bool _isDisposeCalled;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if we're currently connected to the broker
|
||||
/// </summary>
|
||||
@@ -272,6 +269,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
_client.OpenOrder += HandleOpenOrder;
|
||||
_client.OpenOrderEnd += HandleOpenOrderEnd;
|
||||
_client.UpdateAccountValue += HandleUpdateAccountValue;
|
||||
_client.AccountSummary += HandleAccountSummary;
|
||||
_client.ManagedAccounts += HandleManagedAccounts;
|
||||
_client.FamilyCodes += HandleFamilyCodes;
|
||||
_client.ExecutionDetails += HandleExecutionDetails;
|
||||
_client.CommissionReport += HandleCommissionReport;
|
||||
_client.Error += HandleError;
|
||||
@@ -398,7 +398,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
{
|
||||
var orderId = Parse.Int(id);
|
||||
|
||||
_requestInformation[orderId] = "CancelOrder: " + order;
|
||||
_requestInformation[orderId] = $"[Id={orderId}] CancelOrder: " + order;
|
||||
|
||||
CheckRateLimiting();
|
||||
|
||||
@@ -605,9 +605,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
var manualResetEvent = new ManualResetEvent(false);
|
||||
|
||||
var requestId = GetNextRequestId();
|
||||
var requestId = GetNextId();
|
||||
|
||||
_requestInformation[requestId] = "GetExecutions: " + symbol;
|
||||
_requestInformation[requestId] = $"[Id={requestId}] GetExecutions: " + symbol;
|
||||
|
||||
// define our event handlers
|
||||
EventHandler<IB.RequestEndEventArgs> clientOnExecutionDataEnd = (sender, args) =>
|
||||
@@ -727,6 +727,11 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
if (!_client.Connected) throw new Exception("InteractiveBrokersBrokerage.Connect(): Connection returned but was not in connected state.");
|
||||
|
||||
// request account information for logging purposes
|
||||
_client.ClientSocket.reqAccountSummary(GetNextId(), "All", "AccountType");
|
||||
_client.ClientSocket.reqManagedAccts();
|
||||
_client.ClientSocket.reqFamilyCodes();
|
||||
|
||||
if (IsFinancialAdvisor)
|
||||
{
|
||||
if (!DownloadFinancialAdvisorAccount(_account))
|
||||
@@ -905,8 +910,15 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_isDisposeCalled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Trace("InteractiveBrokersBrokerage.Dispose(): Disposing of IB resources.");
|
||||
|
||||
_isDisposeCalled = true;
|
||||
|
||||
if (_client != null)
|
||||
{
|
||||
Disconnect();
|
||||
@@ -941,7 +953,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
if (needsNewId)
|
||||
{
|
||||
// the order ids are generated for us by the SecurityTransactionManaer
|
||||
var id = GetNextBrokerageOrderId();
|
||||
var id = GetNextId();
|
||||
order.BrokerId.Add(id.ToStringInvariant());
|
||||
ibOrderId = id;
|
||||
}
|
||||
@@ -955,7 +967,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
throw new ArgumentException("Expected order with populated BrokerId for updating orders.");
|
||||
}
|
||||
|
||||
_requestInformation[ibOrderId] = $"IBPlaceOrder: {order.Symbol.Value} ({contract})";
|
||||
_requestInformation[ibOrderId] = $"[Id={ibOrderId}] IBPlaceOrder: {order.Symbol.Value} ({GetContractDescription(contract)} )";
|
||||
|
||||
CheckRateLimiting();
|
||||
|
||||
@@ -972,7 +984,12 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
private static string GetUniqueKey(Contract contract)
|
||||
{
|
||||
return $"{contract} {contract.LastTradeDateOrContractMonth.ToStringInvariant()} {contract.Strike.ToStringInvariant()} {contract.Right}";
|
||||
return $"{contract.ToString().ToUpperInvariant()} {contract.LastTradeDateOrContractMonth.ToStringInvariant()} {contract.Strike.ToStringInvariant()} {contract.Right}";
|
||||
}
|
||||
|
||||
private static string GetContractDescription(Contract contract)
|
||||
{
|
||||
return $"{contract} {contract.PrimaryExch ?? string.Empty} {contract.LastTradeDateOrContractMonth.ToStringInvariant()} {contract.Strike.ToStringInvariant()} {contract.Right}";
|
||||
}
|
||||
|
||||
private string GetPrimaryExchange(Contract contract, Symbol symbol)
|
||||
@@ -1033,10 +1050,13 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
{
|
||||
const int timeout = 60; // sec
|
||||
|
||||
ContractDetails details = null;
|
||||
var requestId = GetNextRequestId();
|
||||
var requestId = GetNextId();
|
||||
|
||||
_requestInformation[requestId] = $"GetContractDetails: {symbol.Value} ({contract})";
|
||||
var contractDetailsList = new List<ContractDetails>();
|
||||
|
||||
Log.Trace($"InteractiveBrokersBrokerage.GetContractDetails(): {symbol.Value} ({contract})");
|
||||
|
||||
_requestInformation[requestId] = $"[Id={requestId}] GetContractDetails: {symbol.Value} ({contract})";
|
||||
|
||||
var manualResetEvent = new ManualResetEvent(false);
|
||||
|
||||
@@ -1044,12 +1064,26 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
EventHandler<IB.ContractDetailsEventArgs> clientOnContractDetails = (sender, args) =>
|
||||
{
|
||||
// ignore other requests
|
||||
if (args.RequestId != requestId) return;
|
||||
details = args.ContractDetails;
|
||||
var uniqueKey = GetUniqueKey(contract);
|
||||
if (args.RequestId != requestId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var details = args.ContractDetails;
|
||||
contractDetailsList.Add(details);
|
||||
|
||||
var uniqueKey = GetUniqueKey(details.Contract);
|
||||
_contractDetails.TryAdd(uniqueKey, details);
|
||||
manualResetEvent.Set();
|
||||
Log.Trace("InteractiveBrokersBrokerage.GetContractDetails(): clientOnContractDetails event: " + uniqueKey);
|
||||
|
||||
Log.Trace($"InteractiveBrokersBrokerage.GetContractDetails(): clientOnContractDetails event: {uniqueKey}");
|
||||
};
|
||||
|
||||
EventHandler<IB.RequestEndEventArgs> clientOnContractDetailsEnd = (sender, args) =>
|
||||
{
|
||||
if (args.RequestId == requestId)
|
||||
{
|
||||
manualResetEvent.Set();
|
||||
}
|
||||
};
|
||||
|
||||
EventHandler<IB.ErrorEventArgs> clientOnError = (sender, args) =>
|
||||
@@ -1061,6 +1095,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
};
|
||||
|
||||
_client.ContractDetails += clientOnContractDetails;
|
||||
_client.ContractDetailsEnd += clientOnContractDetailsEnd;
|
||||
_client.Error += clientOnError;
|
||||
|
||||
CheckRateLimiting();
|
||||
@@ -1075,32 +1110,21 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
// be sure to remove our event handlers
|
||||
_client.Error -= clientOnError;
|
||||
_client.ContractDetailsEnd -= clientOnContractDetailsEnd;
|
||||
_client.ContractDetails -= clientOnContractDetails;
|
||||
|
||||
return details;
|
||||
}
|
||||
Log.Trace($"InteractiveBrokersBrokerage.GetContractDetails(): contracts found: {contractDetailsList.Count}");
|
||||
|
||||
private string GetFuturesContractExchange(Contract contract, string ticker)
|
||||
{
|
||||
// searching for available contracts on different exchanges
|
||||
var contractDetails = FindContracts(contract, ticker);
|
||||
|
||||
var exchanges = _futuresExchanges.Values.Reverse().ToArray();
|
||||
|
||||
// sorting list of available contracts by exchange priority, taking the top 1
|
||||
return contractDetails
|
||||
.Select(details => details.Contract.Exchange)
|
||||
.OrderByDescending(e => Array.IndexOf(exchanges, e))
|
||||
.FirstOrDefault();
|
||||
return contractDetailsList.FirstOrDefault();
|
||||
}
|
||||
|
||||
public IEnumerable<ContractDetails> FindContracts(Contract contract, string ticker)
|
||||
{
|
||||
const int timeout = 60; // sec
|
||||
|
||||
var requestId = GetNextRequestId();
|
||||
var requestId = GetNextId();
|
||||
|
||||
_requestInformation[requestId] = $"FindContracts: {ticker} ({contract})";
|
||||
_requestInformation[requestId] = $"[Id={requestId}] FindContracts: {ticker} ({GetContractDescription(contract)})";
|
||||
|
||||
var manualResetEvent = new ManualResetEvent(false);
|
||||
var contractDetails = new List<ContractDetails>();
|
||||
@@ -1317,6 +1341,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
/// </summary>
|
||||
private void HandleUpdateAccountValue(object sender, IB.UpdateAccountValueEventArgs e)
|
||||
{
|
||||
//Log.Trace($"HandleUpdateAccountValue(): Key:{e.Key} Value:{e.Value} Currency:{e.Currency} AccountName:{e.AccountName}");
|
||||
|
||||
try
|
||||
{
|
||||
_accountData.AccountProperties[e.Currency + ":" + e.Key] = e.Value;
|
||||
@@ -1329,6 +1355,12 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
OnAccountChanged(new AccountEvent(e.Currency, cashBalance));
|
||||
}
|
||||
|
||||
// IB does not explicitly return the account base currency, but we can find out using exchange rates returned
|
||||
if (e.Key == AccountValueKeys.ExchangeRate && e.Currency != "BASE" && e.Value.ToDecimal() == 1)
|
||||
{
|
||||
AccountBaseCurrency = e.Currency;
|
||||
}
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
@@ -1837,6 +1869,14 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
_futuresExchanges[symbol.ID.Market] :
|
||||
symbol.ID.Market;
|
||||
|
||||
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
|
||||
symbol.ID.Market,
|
||||
symbol.ID.Symbol,
|
||||
SecurityType.Future,
|
||||
Currencies.USD);
|
||||
|
||||
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
|
||||
|
||||
contract.IncludeExpired = includeExpired;
|
||||
}
|
||||
|
||||
@@ -2091,7 +2131,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
$"An existing position or open order for an unsupported security type was found: {contract}. " +
|
||||
$"An existing position or open order for an unsupported security type was found: {GetContractDescription(contract)}. " +
|
||||
"Please manually close the position or cancel the order before restarting the algorithm.");
|
||||
}
|
||||
}
|
||||
@@ -2217,10 +2257,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the threading issues of creating an IB order ID
|
||||
/// Handles the threading issues of creating an IB OrderId/RequestId/TickerId
|
||||
/// </summary>
|
||||
/// <returns>The new IB ID</returns>
|
||||
private int GetNextBrokerageOrderId()
|
||||
/// <returns>The new IB OrderId/RequestId/TickerId</returns>
|
||||
private int GetNextId()
|
||||
{
|
||||
lock (_nextValidIdLocker)
|
||||
{
|
||||
@@ -2229,16 +2269,6 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
}
|
||||
}
|
||||
|
||||
private int GetNextRequestId()
|
||||
{
|
||||
return Interlocked.Increment(ref _nextRequestId);
|
||||
}
|
||||
|
||||
private int GetNextTickerId()
|
||||
{
|
||||
return Interlocked.Increment(ref _nextTickerId);
|
||||
}
|
||||
|
||||
private void HandleBrokerTime(object sender, IB.CurrentTimeUtcEventArgs e)
|
||||
{
|
||||
// keep track of clock drift
|
||||
@@ -2306,10 +2336,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
return false;
|
||||
}
|
||||
|
||||
var id = GetNextTickerId();
|
||||
var id = GetNextId();
|
||||
var contract = CreateContract(subscribeSymbol, false);
|
||||
|
||||
_requestInformation[id] = $"Subscribe: {symbol.Value} ({contract})";
|
||||
_requestInformation[id] = $"[Id={id}] Subscribe: {symbol.Value} ({GetContractDescription(contract)})";
|
||||
|
||||
CheckRateLimiting();
|
||||
|
||||
@@ -2329,7 +2359,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
_subscribedSymbols[symbol] = id;
|
||||
_subscribedTickers[id] = new SubscriptionEntry { Symbol = subscribeSymbol };
|
||||
|
||||
Log.Trace($"InteractiveBrokersBrokerage.Subscribe(): Subscribe Processed: {symbol.Value} ({contract}) # {id}");
|
||||
Log.Trace($"InteractiveBrokersBrokerage.Subscribe(): Subscribe Processed: {symbol.Value} ({GetContractDescription(contract)}) # {id}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2723,6 +2753,18 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
}
|
||||
else if (securityType == SecurityType.Future)
|
||||
{
|
||||
string market;
|
||||
if (_symbolPropertiesDatabase.TryGetMarket(lookupName, securityType, out market))
|
||||
{
|
||||
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
|
||||
market,
|
||||
lookupName,
|
||||
securityType,
|
||||
Currencies.USD);
|
||||
|
||||
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
|
||||
}
|
||||
|
||||
// processing request
|
||||
var results = FindContracts(contract, contract.Symbol);
|
||||
|
||||
@@ -2817,7 +2859,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
var startTime = request.Resolution == Resolution.Daily ? request.StartTimeUtc.Date : request.StartTimeUtc;
|
||||
var endTime = request.Resolution == Resolution.Daily ? request.EndTimeUtc.Date : request.EndTimeUtc;
|
||||
|
||||
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Submitting request: {request.Symbol.Value} ({contract}): {request.Resolution}/{request.TickType} {startTime} UTC -> {endTime} UTC");
|
||||
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Submitting request: {request.Symbol.Value} ({GetContractDescription(contract)}): {request.Resolution}/{request.TickType} {startTime} UTC -> {endTime} UTC");
|
||||
|
||||
DateTimeZone exchangeTimeZone;
|
||||
if (!_symbolExchangeTimeZones.TryGetValue(request.Symbol, out exchangeTimeZone))
|
||||
@@ -2862,7 +2904,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
yield return bar;
|
||||
}
|
||||
|
||||
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Download completed: {request.Symbol.Value} ({contract})");
|
||||
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Download completed: {request.Symbol.Value} ({GetContractDescription(contract)})");
|
||||
}
|
||||
|
||||
private IEnumerable<TradeBar> GetHistory(
|
||||
@@ -2888,9 +2930,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
{
|
||||
var pacing = false;
|
||||
var historyPiece = new List<TradeBar>();
|
||||
var historicalTicker = GetNextTickerId();
|
||||
var historicalTicker = GetNextId();
|
||||
|
||||
_requestInformation[historicalTicker] = $"GetHistory: {request.Symbol.Value} ({contract})";
|
||||
_requestInformation[historicalTicker] = $"[Id={historicalTicker}] GetHistory: {request.Symbol.Value} ({GetContractDescription(contract)})";
|
||||
|
||||
EventHandler<IB.HistoricalDataEventArgs> clientOnHistoricalData = (sender, args) =>
|
||||
{
|
||||
@@ -3023,7 +3065,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
var result = _ibAutomater.GetLastStartResult();
|
||||
CheckIbAutomaterError(result, false);
|
||||
|
||||
if (!result.HasError)
|
||||
if (!result.HasError && !_isDisposeCalled)
|
||||
{
|
||||
// IBGateway was closed by the v978+ automatic logoff or it was closed manually (less likely)
|
||||
Log.Trace("InteractiveBrokersBrokerage.OnIbAutomaterExited(): IBGateway close detected, restarting IBAutomater and reconnecting...");
|
||||
@@ -3047,6 +3089,24 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAccountSummary(object sender, IB.AccountSummaryEventArgs e)
|
||||
{
|
||||
Log.Trace($"InteractiveBrokersBrokerage.HandleAccountSummary(): Request id: {e.RequestId}, Account: {e.Account}, Tag: {e.Tag}, Value: {e.Value}, Currency: {e.Currency}");
|
||||
}
|
||||
|
||||
private void HandleFamilyCodes(object sender, IB.FamilyCodesEventArgs e)
|
||||
{
|
||||
foreach (var familyCode in e.FamilyCodes)
|
||||
{
|
||||
Log.Trace($"InteractiveBrokersBrokerage.HandleFamilyCodes(): Account id: {familyCode.AccountID}, Family code: {familyCode.FamilyCodeStr}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleManagedAccounts(object sender, IB.ManagedAccountsEventArgs e)
|
||||
{
|
||||
Log.Trace($"InteractiveBrokersBrokerage.HandleManagedAccounts(): Account list: {e.AccountList}");
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<Symbol, int> _subscribedSymbols = new ConcurrentDictionary<Symbol, int>();
|
||||
private readonly ConcurrentDictionary<int, SubscriptionEntry> _subscribedTickers = new ConcurrentDictionary<int, SubscriptionEntry>();
|
||||
private readonly Dictionary<Symbol, Symbol> _underlyings = new Dictionary<Symbol, Symbol>();
|
||||
@@ -3062,8 +3122,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
private static class AccountValueKeys
|
||||
{
|
||||
public const string CashBalance = "CashBalance";
|
||||
// public const string AccruedCash = "AccruedCash";
|
||||
// public const string NetLiquidationByCurrency = "NetLiquidationByCurrency";
|
||||
public const string ExchangeRate = "ExchangeRate";
|
||||
}
|
||||
|
||||
// these are fatal errors from IB
|
||||
@@ -3084,5 +3143,4 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
|
||||
105, 106, 107, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 129, 131, 132, 133, 134, 135, 136, 137, 140, 141, 146, 147, 148, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 163, 167, 168, 201, 313,314,315,325,328,329,334,335,336,337,338,339,340,341,342,343,345,347,348,349,350,352,353,355,356,358,359,360,361,362,363,364,367,368,369,370,371,372,373,374,375,376,377,378,379,380,382,383,387,388,389,390,391,392,393,394,395,396,397,398,400,401,402,403,405,406,407,408,409,410,411,412,413,417,418,419,421,423,424,427,428,429,433,434,435,436,437,439,440,441,442,443,444,445,446,447,448,449,10002,10006,10007,10008,10009,10010,10011,10012,10014,10020,2102
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ using System.Linq;
|
||||
using NodaTime;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Packets;
|
||||
@@ -61,11 +60,7 @@ namespace QuantConnect.Brokerages.Oanda
|
||||
if (environment != Environment.Trade && environment != Environment.Practice)
|
||||
throw new NotSupportedException("Oanda Environment not supported: " + environment);
|
||||
|
||||
// Use v20 REST API only if you have a v20 account
|
||||
// Use v1 REST API if your account id contains only digits(ie. 2534253) as it is a legacy account
|
||||
_api = IsLegacyAccount(accountId) ? (OandaRestApiBase)
|
||||
new OandaRestApiV1(_symbolMapper, orderProvider, securityProvider, aggregator, environment, accessToken, accountId, agent) :
|
||||
new OandaRestApiV20(_symbolMapper, orderProvider, securityProvider, aggregator, environment, accessToken, accountId, agent);
|
||||
_api = new OandaRestApiV20(_symbolMapper, orderProvider, securityProvider, aggregator, environment, accessToken, accountId, agent);
|
||||
|
||||
// forward events received from API
|
||||
_api.OrderStatusChanged += (sender, orderEvent) => OnOrderEvent(orderEvent);
|
||||
@@ -83,6 +78,11 @@ namespace QuantConnect.Brokerages.Oanda
|
||||
get { return _api.IsConnected; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the brokerage account's base currency
|
||||
/// </summary>
|
||||
public override string AccountBaseCurrency => _api.AccountBaseCurrency;
|
||||
|
||||
/// <summary>
|
||||
/// Connects the client to the broker's remote servers
|
||||
/// </summary>
|
||||
@@ -322,11 +322,5 @@ namespace QuantConnect.Brokerages.Oanda
|
||||
{
|
||||
return _api.DownloadQuoteBars(symbol, startTimeUtc, endTimeUtc, resolution, requestedTimeZone);
|
||||
}
|
||||
|
||||
private static bool IsLegacyAccount(string accountId)
|
||||
{
|
||||
long value;
|
||||
return long.TryParse(accountId, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user