Compare commits

..

46 Commits
9385 ... 9701

Author SHA1 Message Date
Mathieu Paquette
854b987cd0 feat(ToolBox\IQFeed): prevent unordered ticks to be processed (#4884)
closes #4649
2020-10-27 20:30:46 -03:00
Juan José D'Ambrosio
a26414d273 Add decimal places as parameters to get dividends with arbitrary precision (#4883)
* Add decimal places as parameters to get dividends with arbitrary precision

 
Also, increase precision when generating strings from factor files rows.

* Add xml documentation entries for new optional arguments.
2020-10-27 20:30:17 -03:00
Alexandre Catarino
84264ca7ef Adds CustomBuyingPowerModelAlgorithm (#4824)
* Adds CustomBuyingPowerModelAlgorithm

This algorithms is an example on how to implement a custom buying power model.

In this particular case, it shows how to override `HasSufficientBuyingPowerForOrder` in order to place orders without sufficient buying power according to the default model.

* Upgrades CustomModelsAlgorithm to Include CustomBuyingPowerModel

The custom buying power model overrides `HasSufficientBuyingPowerForOrderResult` but it doesn't change the trades and, consequently, the regression statistics.
2020-10-21 17:27:30 -07:00
Stefano Raggi
b2ed398687 Allow account currency to be overridden by the algorithm (#4856)
* Allow account currency to be overridden by the algorithm

* Fix failing unit test

* Address review

- Revert removal of call check in SecurityPortfolioManager.SetAccountCurrency
- Revert changes to unit tests
- IBrokerage.AccountBaseCurrency now defaults to null
- BrokerageSetupHandler will not change the algorithm's account currency if the brokerage returns null, allowing the algorithm to call SetAccountCurrency in Initialize
2020-10-20 14:17:16 -03:00
Martin-Molinero
e8c316cbcf Fix Toolbox tickers parsing (#4876)
- Fix ToolBox tickers parsing, adding unit test.
2020-10-20 11:27:41 -03:00
Colton Sellers
724e52c0b3 Make StartDate relative to Algorithm TimeZone in Live mode (#4871) 2020-10-19 15:44:08 -03:00
Jared
4252c79e45 Create QuantConnect-Platform-2.0.0.yaml
Initial commit of QuantConnect Platform Yaml.
2020-10-18 17:46:13 -07:00
Martin-Molinero
cbb40dfa43 Ignore composer ThreadAbort Exception (#4870)
- Composer inner task will not log exception if it's of type Thread
  abort, which means we are shutting down.
2020-10-16 10:37:14 -03:00
Colton Sellers
8792fa2600 Standardize API.cs to use JSON Objects (#4868)
* Standardize API to use JSON Objects

* Address review
2020-10-15 20:24:55 -03:00
Louis Szeto
20e9fd7899 bug-#4846-Fail on restart investing after liquidation on MaximumDrawdownPercentPortfolio.py (#4847)
* Fail on restart investing after liquidation

I added a line so that the trailing high value could be rebalanced and the investment process won't be stop by high value always more than current value by drawdown percent.

* Update MaximumDrawdownPercentPortfolio.py

* Fix for MaximumDrawdownPercentPortfolio

- Fix C# MaximumDrawdownPercentPortfolio to reset portfolio value after
  liquidation. Only reset once we have actually adjusted some targets.
  Updating regression algorithms.

Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-10-15 13:34:08 -03:00
Colton Sellers
e05a6bffd0 Bug 4835 api tests failing (#4838)
* Remove F# Project, Splits, and Dividends Tests

* Separate tests that require external accounts; read from config

* Removal of non supported "prices" endpoint test

* Removal of unsupported API functions

* Address review

* NOP GetLastPrice for removal of Prices endpoint

* Post rebase fix

* Rebase fix 2

* remove /r/n from eof for api tests

* Reflect similar refactors to NodeTests

* Fix for live algorithm API testing

* Address Review
2020-10-15 13:31:53 -03:00
Colton Sellers
c2f0fdc47a Bug #4839 Docker Bash Script Hotfix (#4861)
* fix IFS issue in bash docker scripts

* Add default image to research config
2020-10-14 13:20:01 -03:00
Michael Handschuh
b1b8da1e17 Fixes Market Simulated Automatic Option Assignment (#4853)
* Add underlying holdings to regression result handler details log

When debugging option exercise/assignment issues it's useful to see the
underlying holdings at the time the option contract fill event is processed.

Also adds the full symbol string to the top of the order event section.
The Symbol.Value was being logged via OrderEvent.ToString(), but it wasn't
the full SecurityIdentifier - by including the full SID string it makes it
easier to correlate fills over symbol rename boundaries.

* Fix automatic option assignment from market simulation

During the recent OptionExerciseOrder.Quantity refactor, this case was missed.
Additionally, it was realized that there were no regression tests covering the
automatic assignment via the market conditions simulation. This change introduces
a regression algorithm that covers the automatic assignment of put/call options.

* Update BasicOptionAssignmentSimulation._rand to be non-static

If this value is static then we reuse the same Random instance for ALL regression
tests, thereby defeating the purpose of using a well known seed number. This means
we get different results based on the order execution of preceding algorithms.
By making this an instance variable each algorithm will start with the same seed
value, ensuring consistent runs between regression tests, either run as a suite or
running a single algorithm in isolation.
2020-10-13 19:39:25 -03:00
Stefano Raggi
01a0454c57 Fix Bitfinex Liquidate error with AccountType.Cash (#4852)
* Move updating of cashbook for fees out of SecurityPortfolioModel

* Update BitfinexBrokerage to handle fees in base currency
2020-10-13 15:34:44 -03:00
Stefano Raggi
90e2c48404 Remove Oanda API v1 (deprecated) (#4833) 2020-10-12 16:48:42 -03:00
Aaron Janeiro Stone
888c443264 Moves cash brokerage/IExecutionModel test to post-init (#4826) 2020-10-12 11:14:20 -03:00
adam-may
03efc1b735 Remove internal usages of implicit operator in Indicator code (#4844)
* Convert usages of implicit operator in IndicatorBase and IndicatorDataPoint

* Reverting changes to example code
2020-10-12 10:31:05 -03:00
Martin-Molinero
6ef2ead929 Do not update price scale for fillforward data & IsFillForward flag fix (#4836)
* Do not update price scale for fillforward data

- Do no update price scale for fill forward data. FillForward data
  should keep using the prev scale for which it was created. Adding unit tests
- When cloning do not lose IsFillForward flag state, affects
QuoteBars/Ticks, does not affect TradeBars since they perform a memberwise clone.
Adding unit tests

* Auxiliaries shouldn't really affect on applied price factor scale.

Despite we can receeive FillForward'ed data points, corresponding
Auxiliaries for them are not FillForward so we do meet the condition
and then refresh price factor. As a result all futher FF data points are scaled too.

* Regression algorithm to check that FillForward'ed data points arrived with last real price factor

* Add trade for regression algorithm

- Minot tweaks and adding trade for new regression algorithm.
- Updating AddOptionContractExpiresRegressionAlgorithm because it is
  using the symbol for which new data was added.

Co-authored-by: Adalyat Nazirov <aenazirov@gmail.com>
2020-10-09 18:09:30 -03:00
Martin-Molinero
bfd319c91e OptionChain and OptionContract improvements (#4804)
* OptionChain and OptionContract improvements

- QCAlgorithm.AddUniverse will return the added Universe instance.
- Adding new OptionChainedUniverseSelectionModel will monitor a Universe changes
  and will spwan new OptionChainUniverse from it's selections. Adding
  regression test Py/C#.
- Adding new OptionContractUniverse that will own option contracts and
  their underlying symbol. Adding regression test
- Fix double notification for security changes, bug seen in updated
  UniverseSelectionRegressionAlgorithm
- Remove UniverseSelection special handling for Option and Future chains
- Fix DataManager not removing SubscriptionDataConfigs for Subscriptions
  which finished before being removed from the universe
- Refactor detection of user added Universe so that they do not get
  removed after calling the UniverseSelectionModel

* Add check for option underlying price is set

* Address reviews

- Adding python regression algorithm for
  `AddOptionContractFromUniverseRegressionAlgorithm`
  and `AddOptionContractExpiresRegressionAlgorithm`
- Rename QCAlgorithm new api method to `AddChainedOptionUniverse`

* Fix universe refresh bug

- Fix bug where a universe selection refresh would cause option or
  future chain universes from being removed. Adding regression algorithm
  reproducing the issue.

* Rename new option universe Algorithm API method

- Rename new option universe Algorith API method from
  AddChainedOptionUniverse to AddUniverseOptions
- Rebase and update regression test order hash because of
  option expiration message changed
2020-10-09 10:52:50 -03:00
Stefano Raggi
5f61456df8 Set Account Base Currency from Brokerage in Live Mode (#4806)
* Add property IBrokerage.AccountBaseCurrency

* Set AccountCurrency to brokerage AccountBaseCurrency

* Remove USD AccountCurrency check

* Fix Oanda account base currency

* Fix currency symbol in CashBook.ToString()

* Fix unit tests

* Address review

* Add DebugMessage when changing account currency

* Add debug message for brokerage account base currency

* Fix currency symbol in equity chart and runtime statistics

* Update unit tests
2020-10-09 09:58:01 -03:00
Michael Handschuh
a46a551c03 Include Order.Tag/OrderEvent.Message in their ToString, Fix default tag values (#4797)
* Improve information tracked in regression's {algorithm}.{lang}.details.log

The details.log file aims at providing a diff-able document that quickly and
easily provides actionable information. Since many regression algorithms use
the algorithm's debug/error messaging facilities to log various pieces of algo
state. This document also support a configuration option: regression-high-fidelity-logging'
that logs EVERY piece of data, again, with the aim of providing an easily diff-able
documenbt to quickly highlight actionable information. I may have missed omse key
pieces of information here, but now that the entire QC knows about this regression
tool, if additional information is required then hopefully it's easy enough at this
point to extend the RegressionResultHandler to suit our needs.

The RegressionResultHandler was initially implemented to provide a concise log of
all orders. This was achieved by simply using the Order.ToString method. While
testing/investigating OptionExerciseOrder behavior, it became evident that more
information was required to properly identify the source of potential failures or
differences between previous regression test runs. This change adds logging for
almost every IResultHandler method and additionally attempts to capture the
actual portfolio impact of every OrderEvent. This is accomplished by logging
the portfolio's TotalPortfolioValue, Cash properties and the security's
SecurityHolding.Quantity property.

This change also standardizes the timestamps used to folloow the ISO-8601 format.

When using the RegressionResultHandler, it is highly recommeded to also disable
'forward-console-message' configuration option to ensure algorithm Debug/Error
message logging is done synchronously to ensure correct ordering with respect to
log messages via Log.Debug/Trace/Error.

* Fix typo in options OrderTests test case name

* Update SymbolRepresentation.GenerationOptionTickerOSI to extension method

Far more convenient as an extension method

* Improve R# default code formatting rules

Many of these rule changes focus on improving the readability of code,
with a particular emphasis on multi-line constructs, chained method calls
and multi-line method invocations/declarations.

* Add braces, use string interpolation and limit long lines

* Refactor OptionExerciseOrder.Quantity to indicate change in #contracts

For all other order types, the Order.Quantity indicates the change in the algorithm's
holdings upon order execution for the order's symbol. For OptionExerciseOrder, this
convention was broken. It appears as though only exercise was initially implemented,
in which case only long positions were supported and a code comment indicated that
only positive values of quantity were acceptable, indicating the number of contracts
to exercise. At a later date, assignment simulation was added and utilized a negative
order quantity. This caused some major inconsistencies in how models view exercise
orders compared to all other order types. This change brings OptionExerciseOrder.Quantity
into alignment with the other order types by making it represent the change in holdings
quantity upon order execution.

This change was originally going to be much larger, but in order to minimize risks and to
make for an easier review experience, the additional changes will be committed separately
and pushed in their own PR. Some of the issues identified include:
* Manual Exercise (especially for OTM) is not covered
* Margin Calculations (in particular taking into account opposing contracts held)
* IBrokerage.OptionPositionAssigned is raised for exercise (later filtered by tx handler)

Fixes OptionPortfolioModelTests to use exercise model to properly model exercise of
non-account quote currency option contract.

* Include Order.Tag/OrderEvent.Message in their ToString, Fix default tag values

There was inconsistencies in what we were checking for. The order constructors
default the tag parameter to an empty string but Order.CreateOrder checks for
a null string. Additionally, the order constructors (limit,stopmarket,stoplimit)
would check for an empty string and if so, apply a default order tag.

This change cleans these checks up using string.IsNullOrEmpty and also removes the
check from Order.CreateOrder since we're passing the tag into the various order
constructors.
2020-10-08 21:54:54 -03:00
Michael Handschuh
cf9b547e2e Refactor OptionExerciseOrder.Quantity to be consistent with other Order types (#4796)
* Improve information tracked in regression's {algorithm}.{lang}.details.log

The details.log file aims at providing a diff-able document that quickly and
easily provides actionable information. Since many regression algorithms use
the algorithm's debug/error messaging facilities to log various pieces of algo
state. This document also support a configuration option: regression-high-fidelity-logging'
that logs EVERY piece of data, again, with the aim of providing an easily diff-able
documenbt to quickly highlight actionable information. I may have missed omse key
pieces of information here, but now that the entire QC knows about this regression
tool, if additional information is required then hopefully it's easy enough at this
point to extend the RegressionResultHandler to suit our needs.

The RegressionResultHandler was initially implemented to provide a concise log of
all orders. This was achieved by simply using the Order.ToString method. While
testing/investigating OptionExerciseOrder behavior, it became evident that more
information was required to properly identify the source of potential failures or
differences between previous regression test runs. This change adds logging for
almost every IResultHandler method and additionally attempts to capture the
actual portfolio impact of every OrderEvent. This is accomplished by logging
the portfolio's TotalPortfolioValue, Cash properties and the security's
SecurityHolding.Quantity property.

This change also standardizes the timestamps used to folloow the ISO-8601 format.

When using the RegressionResultHandler, it is highly recommeded to also disable
'forward-console-message' configuration option to ensure algorithm Debug/Error
message logging is done synchronously to ensure correct ordering with respect to
log messages via Log.Debug/Trace/Error.

* Fix typo in options OrderTests test case name

* Update SymbolRepresentation.GenerationOptionTickerOSI to extension method

Far more convenient as an extension method

* Improve R# default code formatting rules

Many of these rule changes focus on improving the readability of code,
with a particular emphasis on multi-line constructs, chained method calls
and multi-line method invocations/declarations.

* Add braces, use string interpolation and limit long lines

* Refactor OptionExerciseOrder.Quantity to indicate change in #contracts

For all other order types, the Order.Quantity indicates the change in the algorithm's
holdings upon order execution for the order's symbol. For OptionExerciseOrder, this
convention was broken. It appears as though only exercise was initially implemented,
in which case only long positions were supported and a code comment indicated that
only positive values of quantity were acceptable, indicating the number of contracts
to exercise. At a later date, assignment simulation was added and utilized a negative
order quantity. This caused some major inconsistencies in how models view exercise
orders compared to all other order types. This change brings OptionExerciseOrder.Quantity
into alignment with the other order types by making it represent the change in holdings
quantity upon order execution.

This change was originally going to be much larger, but in order to minimize risks and to
make for an easier review experience, the additional changes will be committed separately
and pushed in their own PR. Some of the issues identified include:
* Manual Exercise (especially for OTM) is not covered
* Margin Calculations (in particular taking into account opposing contracts held)
* IBrokerage.OptionPositionAssigned is raised for exercise (later filtered by tx handler)

Fixes OptionPortfolioModelTests to use exercise model to properly model exercise of
non-account quote currency option contract.
2020-10-08 20:05:20 -03:00
Colton Sellers
faa4e91e04 Test 4249 fix regression algorithms executed together (#4832)
* Fix for running regressions algorithms together

* Remove unneeded changes
2020-10-08 19:59:06 -03:00
Michael Handschuh
cc83f19528 Rename QuantConnect.API to QuantConnect.Api (#4830)
Also renames API folders to Api
2020-10-08 19:35:57 -03:00
Reginald Louis
54af12b06a Fix DirectoryNotFoundException on linux/mac (#4829) 2020-10-07 20:19:01 -03:00
Michael Handschuh
1d1c8f5f82 Don't raise IBrokerage.OptionPositionAssigned on exercise (#4801) 2020-10-07 14:40:34 -03:00
Colton Sellers
3966c0e91f Market Hours Database Adjustment (#4818)
* Fix Entries

* Fix options dates
2020-10-06 20:45:03 -03:00
Gerardo Salazar
28160e1301 Fixes generation of explicit "null" value in Smart Insider Transactions/Intentions (#4817)
* Adds new unit tests covering changes and testing for old case

  * JsonConvert.SerializeObject would convert a `null` value into a
    literal string of "null" when writing to a file via the ToLine
    abstract method. We opt for an empty string whenever the underlying
    value is null so that the parsing works correctly later in the
    data loading cycle.
2020-10-06 10:20:59 -03:00
Adalyat Nazirov
f4679785a5 Pass command line parameters values as string (#4819)
* avoid preliminary typing command lines arguments

* unit tests: Config.Get can parse and cast values

* unit tests: parse command line args and return string values

* unit test: parameter attribute converter

* merge&parse unit test
2020-10-06 10:19:56 -03:00
Martin-Molinero
79b9009452 ObjectStore delete will delete file (#4816)
- LocalObjectStore.Delete() will also delete file from the local object
  store path if present, this will avoid the issue where restarting the
  object store will re load the same deleted file. Adding unit test.
  Issue https://github.com/QuantConnect/Lean/issues/4811
2020-10-05 20:19:27 -03:00
Gerardo Salazar
e5b5f80d9d Fixes "0" group code value for FixturesAndAppliances (#4820)
* Fixes "0" group code value for FixturesAndAppliances

* Address review: fixes wrong variable name
2020-10-05 20:12:27 -03:00
Stefano Raggi
027fde8f09 Fix IBAutomater restarting after Dispose (#4814)
* Fix IBAutomater restarting after Dispose

* Address review
2020-10-05 12:34:02 -03:00
Martin-Molinero
c7ccd60bf2 Fix unit test stack overflow (#4803)
- PortfolioLooper: implement IDisposable and handle the clean up of
  resources. Remove unused methods.
- BaseResultHandler will reset Console on exit
2020-10-01 14:49:43 -03:00
adam-may
b30bb3fcf5 Bug #4790 - AddBusinessDays ignores holidays when DateTime being compared has time specified (#4791)
Co-authored-by: Adam May <adam.may@lifetrading.com.au>
2020-10-01 09:56:08 -03:00
Gerardo Salazar
40a87eb056 Fixes issue where BidPrice/AskPrice were not adjusted for Quote Ticks (#4789)
* Fixes issue where BidPrice/AskPrice were not adjusted for Quote Ticks

  * Previously, ticks would have their prices (Tick.Value) adjusted whenever
    TickType == TickType.Quote, but would not have their
    BidPrice/AskPrice fields adjusted, thus potentially being orders
    of magnitude such as 4x from the actual Bid/Ask prices.

    This commit applies the pricing scaling factor in a critical
    path where Ticks are adjusted to their scaled price. This issue
    only applied to Resolution.Tick && SecurityType.Equity data.

* Refactors Extensions Tick Scale extension method

  * Adjusts unit test to dispose of resources and assert history count

* Replaces use of FileSystemDataFeed for NullDataFeed in Adjustment test

* Adds regression algorithm testing BidPrice & AskPrice adjustment

* Address review: remove SecurityType check on TickType.Trade adjustments
2020-09-30 19:43:20 -03:00
Jatin Kumar
035b29fdf5 Update readme.md (#4793)
* Update readme.md

American English sadly.
2020-09-30 13:17:59 -07:00
michael-sena
f2fc1aae9e Append the full stacktrace to the algorithm loading exception message. (#4775)
* Append the full stacktrace to the algorithm loading exception message.

* Remove exception message loader duplication

Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-09-30 12:42:21 -03:00
Martin-Molinero
ff5fc5db5d HistoryRequestFactory Hour ExtendedMarketHours (#4786)
- `HistoryRequestFactory` will not sure extended market hours for hour
  resolution when determining the start time using quantity of bars.
  Adding regression test
2020-09-29 18:16:27 -03:00
Gerardo Salazar
40c3062348 Improves Report Generator stability and fixes various bugs (#4759)
* Improves stability and fixes various bugs

  * Adds unit tests covering changes
  * Adds COVID-19 crisis plots
  * Adjusts styling of crisis plots for more pleasant viewing
  * Fixes bug where null BacktestResult caused application to crash
  * Order JSON bug fixes and stability improvements
  * MaxDrawdownReportElement now produces results for Live
  * Replaced Estimated Capacity w/ Days Live
  * Added Live marker to sharpe ratio
  * Added support for MOO and MOC orders in PortfolioLooper

* Address review: adds new unit tests and cleans up code

  * Bug fix: use LastFillTime instead of Order.Time for MOO and MOC

* Address review: Fixes tests and cleans up code
2020-09-29 10:03:26 -03:00
michael-sena
371f2cd469 Upgrade NLog from 4.4.11 to 4.4.13 (#4772) 2020-09-28 21:52:22 -03:00
Aaron Janeiro Stone
090ceb131e Install instructions: Non-Windows (#4777)
* Added warning for paths in non-Windows

As per https://github.com/dotnet/msbuild/issues/4216

* reserved character instructions - clarity
2020-09-28 21:51:21 -03:00
nazbrok
6885fd130b fix decimal conversion for exponential number (#4750) 2020-09-28 21:50:58 -03:00
Adalyat Nazirov
a450cea8d0 keep actual exchange hours even it different from hours database (#4781) 2020-09-28 20:22:08 -03:00
Stefano Raggi
934128cfa0 Binance Brokerage implementation (#4688)
* Binance Brokerage skeleton

* Market hours

* Implement Symbol Mapper

- known symbols available on /api/v1/exchangeInfo
- fiat currencies are pegged

* Implement GetCashBalance

* Implement GetAccountHoldings

- there are no pre-existing currency swaps
- cash balances are pulled and stored in the cashbook

* Implement GetOpenOrders

* Manage orders: PlaceOrder

* Manage orders: UpdateOrder

Update operation is not supported

* Manage orders: CancelOrder

* Messaging: order book

* Messaging: trades

* Messaging: combine streams

- connect to fake /ws/open channel on init
- case by channel name, but not event type

* Messaging: order depth updates

- ticker symbol is not enough as it pushes updates only once a second, this would be a very incomplete data stream
- fetch ticker snapshot if lastUpdateId == 0
- follow Binance instructions for keeping local orderbook fresh

* Messaging: user data streaming

- Request userDataStream endpoint to get listenKey
- keep listenkey alive
- handle order close event
- handle order fill event

* DataDownloader: get history

- we can aggregate minute candles for higher resolutions

* fix data stream

* Tests: FeeModel tests

* Tests: base brokerage tests

* Tests: download history

* Tests: symbol mapper

* Support StopLimit andd StopMarket orders

* StopMarket orders disabled

Take profit and Stop loss orders are not supported for any symbols (tested with BTCUSDT, ETHUSDT)

* Tests: StopLimit order

* Tests: crypto parsing

* Reissue user data listen key

* comment custom currency limitation

* rework websocket connections

* implement delayed subscription

* adapt ignore message

* add license banner

* use better suited exception type

* avoid message double parsing

* support custom fee values

* extract BinanceApiClient to manage the request/response between lean and binance

* use api events to invoke brokerage events

* do not allow to terminate session if it wasn't allocated.

* update binance exchange info

* tool to add or update binance exchange info

* ExchangeInfo basic test

* Rebase + Resharp

* Binance brokerage updates

- Fix sign bug in sell order fills
- Fix bug in GetHistory
- Remove duplicate symbol from symbol properties db

* Remove unused code

* Revert removal of account currency check

* Update symbols properties database

* Address review

* Address review

- Upgrade API endpoints from v1 to v3
- Updated sub/unsub for new subscription manager
- Subscribe best bid/ask quotes instead of full order book
- Added handling of websocket error messages
- Cleanup + refactor

* Update symbol properties database

* Remove list from symbol mapper

* Fix symbol mapper tests

* Address review

- Fix resubscribe after reconnect
- Fix quote tick edge case

* Fix EnsureCurrencyDataFeed for non-tradeable currencies

* Fix check in EnsureCurrencyDataFeed

* Reuse base class subscribe on reconnect

Co-authored-by: Adalyat Nazirov <aenazirov@gmail.com>
Co-authored-by: Martin-Molinero <martin@quantconnect.com>
2020-09-28 15:57:10 -03:00
Michael Handschuh
c7a74306fb Bug 4731 Fix Option Expiration Order Tags and Order Event IsAssignment Flag (#4743)
* Add OrderRight.GetExerciseDirection(isShort) extension

Returns the OrderDirection resulting from exercise/assignment of a particular
option right

See: BUG #4731

* Fix option exercise/assignment order tags and order event messages

The algorithm manager was doing work to determine whether or not the option ended
in exercise or assignment at expiration. This decision should be left for the exercise
model to decide -- from the algorithm manager's perspective, all that matters is that
the option was expired. The DefaultExerciseModel was updated to properly track whether
the option expired with automatic assignment or exercise, dependending on whether or
not we wrote or bought the option (held liability or right, respectively). Updated unit
tests to check for order event counts and order event messages for option exercise cases.

Fixes: #4731

* Fix typo in algorithm documentation

* Update regression tests order hash

Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-09-28 15:29:09 -03:00
Aaron Janeiro Stone
cfacc755fa Removal of depreciated module called in test, addition of __init__ (#4769)
* Removal of depreciated module called in test, addition of __init__ to module.

* Delete __init__.py

Currently unnecessary
2020-09-28 11:36:02 -03:00
324 changed files with 13173 additions and 5073 deletions

View 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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -119,4 +119,4 @@ namespace QuantConnect.Algorithm.CSharp
{"OrderListHash", "491919591"}
};
}
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View 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"}
};
}
}

View File

@@ -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>

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View 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"}
};
}
}

View File

@@ -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" />

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -142,8 +142,17 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AddAlphaModelAlgorithm.cs" />
<Compile Include="CustomBuyingPowerModelAlgorithm.cs" />
<Compile Include="AddOptionContractExpiresRegressionAlgorithm.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" />

View 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"}
};
}
}

View File

@@ -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.");
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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"}
};
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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!")

View File

@@ -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

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

View File

@@ -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):

View File

@@ -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 *

View File

@@ -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

View File

@@ -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

View File

@@ -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 *

View File

@@ -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" />
@@ -83,11 +85,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" />

View File

@@ -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" />

View File

@@ -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);
}

View File

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

View File

@@ -227,7 +227,7 @@ namespace QuantConnect.Algorithm
{
foreach (var indicator in indicators)
{
Plot(chart, indicator.Name, indicator);
Plot(chart, indicator.Name, indicator.Current.Value);
}
}

View File

@@ -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>

View File

@@ -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."
);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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" />

View 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);
}
}
}
}

View 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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -15,7 +15,6 @@
using System;
using Newtonsoft.Json;
using QuantConnect.API;
using QuantConnect.Configuration;
using QuantConnect.Logging;
using QuantConnect.Orders;

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
}

View File

@@ -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");
}
}
}

View File

@@ -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}");

View File

@@ -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));

View 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),
Time.UnixMillisecondTimeStampToDateTime(trade.Time),
trade.Price,
trade.Quantity);
break;
}
}
else if (objData["u"] != null)
{
var quote = objData.ToObject<BestBidAskQuote>();
EmitQuoteTick(
_symbolMapper.GetLeanSymbol(quote.Symbol),
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;
}
}
}

View 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;
}
}
}
}

View 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 BinanceSymbolMapper _symbolMapper = new BinanceSymbolMapper();
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);
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);
}
}
}
}

View 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;
}
}
}

View 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; }
}
}

View 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 BinanceSymbolMapper _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(BinanceSymbolMapper 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);
}
}
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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.Binance
{
/// <summary>
/// Provides the mapping between Lean symbols and Binance symbols.
/// </summary>
public class BinanceSymbolMapper : ISymbolMapper
{
/// <summary>
/// The list of known Binance symbols.
/// </summary>
public static readonly HashSet<string> KnownTickers =
new HashSet<string>(SymbolPropertiesDatabase
.FromDataFolder()
.GetSymbolPropertiesList(Market.Binance, SecurityType.Crypto)
.Select(x => x.Key.Symbol));
/// <summary>
/// Converts a Lean symbol instance to an Binance symbol
/// </summary>
/// <param name="symbol">A Lean symbol instance</param>
/// <returns>The Binance 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.SecurityType != SecurityType.Crypto)
throw new ArgumentException("Invalid security type: " + symbol.SecurityType);
var brokerageSymbol = ConvertLeanSymbolToBrokerageSymbol(symbol.Value);
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException("Unknown symbol: " + symbol.Value);
return brokerageSymbol;
}
/// <summary>
/// Converts an Binance symbol to a Lean symbol instance
/// </summary>
/// <param name="brokerageSymbol">The Binance 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 Binance symbol: {brokerageSymbol}");
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException($"Unknown Binance symbol: {brokerageSymbol}");
if (securityType != SecurityType.Crypto)
throw new ArgumentException($"Invalid security type: {securityType}");
if (market != Market.Binance)
throw new ArgumentException($"Invalid market: {market}");
return Symbol.Create(ConvertBrokerageSymbolToLeanSymbol(brokerageSymbol), GetBrokerageSecurityType(brokerageSymbol), Market.Binance);
}
/// <summary>
/// Converts an Binance symbol to a Lean symbol instance
/// </summary>
/// <param name="brokerageSymbol">The Binance symbol</param>
/// <returns>A new Lean Symbol instance</returns>
public Symbol GetLeanSymbol(string brokerageSymbol)
{
var securityType = GetBrokerageSecurityType(brokerageSymbol);
return GetLeanSymbol(brokerageSymbol, securityType, Market.Binance);
}
/// <summary>
/// Returns the security type for an Binance symbol
/// </summary>
/// <param name="brokerageSymbol">The Binance symbol</param>
/// <returns>The security type</returns>
public SecurityType GetBrokerageSecurityType(string brokerageSymbol)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
throw new ArgumentException($"Invalid Binance symbol: {brokerageSymbol}");
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException($"Unknown Binance 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(ConvertLeanSymbolToBrokerageSymbol(leanSymbol));
}
/// <summary>
/// Checks if the symbol is supported by Binance
/// </summary>
/// <param name="brokerageSymbol">The Binance symbol</param>
/// <returns>True if Binance supports the symbol</returns>
public bool IsKnownBrokerageSymbol(string brokerageSymbol)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
return false;
return KnownTickers.Contains(brokerageSymbol);
}
/// <summary>
/// Checks if the symbol is supported by Binance
/// </summary>
/// <param name="symbol">The Lean symbol</param>
/// <returns>True if Binance supports the symbol</returns>
public bool IsKnownLeanSymbol(Symbol symbol)
{
if (string.IsNullOrWhiteSpace(symbol?.Value) || symbol.Value.Length <= 3)
return false;
var binanceSymbol = ConvertLeanSymbolToBrokerageSymbol(symbol.Value);
return IsKnownBrokerageSymbol(binanceSymbol) && GetBrokerageSecurityType(binanceSymbol) == symbol.SecurityType;
}
/// <summary>
/// Converts an Binance symbol to a Lean symbol string
/// </summary>
private static string ConvertBrokerageSymbolToLeanSymbol(string binanceSymbol)
{
if (string.IsNullOrWhiteSpace(binanceSymbol))
throw new ArgumentException($"Invalid Binance symbol: {binanceSymbol}");
// return as it is due to Binance has similar Symbol format
return binanceSymbol.LazyToUpper();
}
/// <summary>
/// Converts a Lean symbol string to an Binance symbol
/// </summary>
private static string ConvertLeanSymbolToBrokerageSymbol(string leanSymbol)
{
if (string.IsNullOrWhiteSpace(leanSymbol))
throw new ArgumentException($"Invalid Lean symbol: {leanSymbol}");
// return as it is due to Binance has similar Symbol format
return leanSymbol.LazyToUpper();
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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
}

View File

@@ -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,

View File

@@ -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>

View File

@@ -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
@@ -246,7 +244,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);

View File

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

View File

@@ -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
@@ -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>
@@ -433,6 +451,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>

View File

@@ -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
{
@@ -131,6 +130,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>
@@ -905,8 +906,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();
@@ -1317,6 +1325,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 +1339,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)
{
@@ -3023,7 +3039,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...");
@@ -3062,8 +3078,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

View File

@@ -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);
}
}
}

View File

@@ -28,7 +28,6 @@ using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Securities;
using QuantConnect.Util;
using Timer = System.Timers.Timer;
namespace QuantConnect.Brokerages.Oanda
{
@@ -38,8 +37,8 @@ namespace QuantConnect.Brokerages.Oanda
public abstract class OandaRestApiBase : Brokerage, IDataQueueHandler
{
private static readonly TimeSpan SubscribeDelay = TimeSpan.FromMilliseconds(250);
private ManualResetEvent _refreshEvent = new ManualResetEvent(false);
private CancellationTokenSource _streamingCancellationTokenSource = new CancellationTokenSource();
private readonly ManualResetEvent _refreshEvent = new ManualResetEvent(false);
private readonly CancellationTokenSource _streamingCancellationTokenSource = new CancellationTokenSource();
private bool _isConnected;
@@ -67,11 +66,6 @@ namespace QuantConnect.Brokerages.Oanda
/// </summary>
protected IEnumerable<Symbol> SubscribedSymbols => _subscriptionManager.GetSubscribedSymbols();
/// <summary>
/// A lock object used to synchronize access to subscribed symbols
/// </summary>
protected readonly object LockerSubscriptions = new object();
/// <summary>
/// The symbol mapper
/// </summary>
@@ -151,7 +145,7 @@ namespace QuantConnect.Brokerages.Oanda
Aggregator = aggregator;
_subscriptionManager = new EventBasedDataQueueHandlerSubscriptionManager();
_subscriptionManager.SubscribeImpl += (s, t) => Refresh();
_subscriptionManager.UnsubscribeImpl += (s, t) => Refresh(); ;
_subscriptionManager.UnsubscribeImpl += (s, t) => Refresh();
PricingConnectionHandler = new DefaultConnectionHandler { MaximumIdleTimeSpan = TimeSpan.FromSeconds(20) };
PricingConnectionHandler.ConnectionLost += OnPricingConnectionLost;
@@ -183,7 +177,7 @@ namespace QuantConnect.Brokerages.Oanda
var symbolsToSubscribe = SubscribedSymbols;
// restart streaming session
SubscribeSymbols(symbolsToSubscribe);
} while (!_streamingCancellationTokenSource.IsCancellationRequested);
},
TaskCreationOptions.LongRunning
@@ -280,6 +274,8 @@ namespace QuantConnect.Brokerages.Oanda
/// </summary>
public override void Connect()
{
AccountBaseCurrency = GetAccountBaseCurrency();
// Register to the event session to receive events.
StartTransactionStream();
@@ -302,6 +298,11 @@ namespace QuantConnect.Brokerages.Oanda
_isConnected = false;
}
/// <summary>
/// Gets the account base currency
/// </summary>
public abstract string GetAccountBaseCurrency();
/// <summary>
/// Gets the list of available tradable instruments/products from Oanda
/// </summary>

File diff suppressed because it is too large Load Diff

View File

@@ -82,6 +82,16 @@ namespace QuantConnect.Brokerages.Oanda
_apiStreaming = new DefaultApi(basePathStreaming);
}
/// <summary>
/// Gets the account base currency
/// </summary>
public override string GetAccountBaseCurrency()
{
var response = _apiRest.GetAccount(Authorization, AccountId);
return response.Account.Currency;
}
/// <summary>
/// Gets the list of available tradable instruments/products from Oanda
/// </summary>

View File

@@ -1,168 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represents the Oanda Account.
/// </summary>
public class Account
{
public bool HasAccountId;
private int _accountId;
public int accountId
{
get { return _accountId; }
set
{
_accountId = value;
HasAccountId = true;
}
}
public bool HasAccountName;
private string _accountName;
public string accountName
{
get { return _accountName; }
set
{
_accountName = value;
HasAccountName = true;
}
}
public bool HasAccountCurrency;
private string _accountCurrency;
public string accountCurrency
{
get { return _accountCurrency; }
set
{
_accountCurrency = value;
HasAccountCurrency = true;
}
}
public bool HasMarginRate;
private string _marginRate;
public string marginRate
{
get { return _marginRate; }
set
{
_marginRate = value;
HasMarginRate = true;
}
}
[IsOptional]
public bool HasBalance;
private string _balance;
public string balance
{
get { return _balance; }
set
{
_balance = value;
HasBalance = true;
}
}
[IsOptional]
public bool HasUnrealizedPl;
private string _unrealizedPl;
public string unrealizedPl
{
get { return _unrealizedPl; }
set
{
_unrealizedPl = value;
HasUnrealizedPl = true;
}
}
[IsOptional]
public bool HasRealizedPl;
private string _realizedPl;
public string realizedPl
{
get { return _realizedPl; }
set
{
_realizedPl = value;
HasRealizedPl = true;
}
}
[IsOptional]
public bool HasMarginUsed;
private string _marginUsed;
public string marginUsed
{
get { return _marginUsed; }
set
{
_marginUsed = value;
HasMarginUsed = true;
}
}
[IsOptional]
public bool HasMarginAvail;
private string _marginAvail;
public string marginAvail
{
get { return _marginAvail; }
set
{
_marginAvail = value;
HasMarginAvail = true;
}
}
[IsOptional]
public bool HasOpenTrades;
private string _openTrades;
public string openTrades
{
get { return _openTrades; }
set
{
_openTrades = value;
HasOpenTrades = true;
}
}
[IsOptional]
public bool HasOpenOrders;
private string _openOrders;
public string openOrders
{
get { return _openOrders; }
set
{
_openOrders = value;
HasOpenOrders = true;
}
}
}
#pragma warning restore 1591
}

View File

@@ -1,48 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
public struct Candle
{
public string time { get; set; }
public int volume { get; set; }
public bool complete { get; set; }
// Midpoint candles
public double openMid { get; set; }
public double highMid { get; set; }
public double lowMid { get; set; }
public double closeMid { get; set; }
// Bid/Ask candles
public double openBid { get; set; }
public double highBid { get; set; }
public double lowBid { get; set; }
public double closeBid { get; set; }
public double openAsk { get; set; }
public double highAsk { get; set; }
public double lowAsk { get; set; }
public double closeAsk { get; set; }
}
#pragma warning restore 1591
}

View File

@@ -1,33 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represents the web response when creating a new account with Oanda.
/// </summary>
public class AccountResponse
{
public string username;
public string password;
public int accountId;
}
#pragma warning restore 1591
}

View File

@@ -1,34 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represents the web response when querying the list of accounts belong to one Oanda user.
/// </summary>
public class AccountsResponse
{
public List<Account> accounts;
}
#pragma warning restore 1591
}

View File

@@ -1,32 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
public class CandlesResponse
{
public string instrument;
public string granularity;
public List<Candle> candles;
}
#pragma warning restore 1591
}

View File

@@ -1,33 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represent web response for the list of active/tradable instruments provided by Oanda.
/// </summary>
public class InstrumentsResponse
{
public List<Instrument> instruments;
}
#pragma warning restore 1591
}

View File

@@ -1,34 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represents the web response of the current active orders from Oanda.
/// </summary>
public class OrdersResponse
{
public List<Order> orders;
public string nextPage;
}
#pragma warning restore 1591
}

View File

@@ -1,33 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represents the current active positions from Oanda.
/// </summary>
public class PositionsResponse
{
public List<Position> positions;
}
#pragma warning restore 1591
}

View File

@@ -1,40 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represents the post order response from Oanda.
/// </summary>
public class PostOrderResponse : Response
{
public string instrument { get; set; }
public string time { get; set; }
public double? price { get; set; }
public Order orderOpened { get; set; }
public TradeData tradeOpened { get; set; }
public List<Transaction> tradesClosed { get; set; }
public Transaction tradeReduced { get; set; }
}
#pragma warning restore 1591
}

View File

@@ -1,34 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represent the web response of the current price of active instruments from Oanda.
/// </summary>
public class PricesResponse
{
public long time;
public List<Price> prices;
}
#pragma warning restore 1591
}

View File

@@ -1,33 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
public class RateStreamResponse : IHeartbeat
{
public Heartbeat heartbeat;
public Price tick;
public bool IsHeartbeat()
{
return (heartbeat != null);
}
}
#pragma warning restore 1591
}

View File

@@ -1,100 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.ComponentModel;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications.Requests
{
#pragma warning disable 1591
public class CandlesRequest : Request
{
public CandlesRequest()
{
}
public SmartProperty<string> instrument;
[IsOptional]
[DefaultValue(EGranularity.S5)]
public SmartProperty<EGranularity> granularity;
[IsOptional]
[DefaultValue(500)]
public SmartProperty<int> count;
[IsOptional]
public SmartProperty<string> start;
[IsOptional]
public SmartProperty<string> end;
[IsOptional]
[DefaultValue(ECandleFormat.bidask)]
public SmartProperty<ECandleFormat> candleFormat;
[IsOptional]
//[DefaultValue(true)]
public SmartProperty<bool> includeFirst;
[IsOptional]
public SmartProperty<string> dailyAlignment;
[IsOptional]
public SmartProperty<string> weeklyAlignment;
public override string EndPoint
{
get { return "candles"; }
}
}
public enum ECandleFormat
{
bidask,
midpoint
}
public enum EGranularity
{
S5,
S10,
S15,
S30,
M1,
M2,
M3,
M5,
M10,
M15,
M30,
H1,
H2,
H3,
H4,
H6,
H8,
H12,
D,
W,
M
}
#pragma warning restore 1591
}

View File

@@ -1,111 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Reflection;
using System.Text;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications.Requests
{
#pragma warning disable 1591
public interface ISmartProperty
{
bool HasValue { get; set; }
void SetValue(object obj);
}
// Functionally very similar to System.Nullable, could possibly just replace this
public struct SmartProperty<T> : ISmartProperty
{
private T _value;
public bool HasValue { get; set; }
public T Value
{
get { return _value; }
set
{
_value = value;
HasValue = true;
}
}
public static implicit operator SmartProperty<T>(T value)
{
return new SmartProperty<T>() { Value = value };
}
public static implicit operator T(SmartProperty<T> value)
{
return value._value;
}
public void SetValue(object obj)
{
SetValue((T)obj);
}
public void SetValue(T value)
{
Value = value;
}
public override string ToString()
{
// This is ugly, but c'est la vie for now
if (_value is bool)
{ // bool values need to be lower case to be parsed correctly
return _value.ToString().ToLowerInvariant();
}
return _value.ToString();
}
}
public abstract class Request
{
public abstract string EndPoint { get; }
public string GetRequestString()
{
var result = new StringBuilder();
result.Append(EndPoint);
bool firstJoin = true;
foreach (var declaredField in this.GetType().GetTypeInfo().DeclaredFields)
{
var prop = declaredField.GetValue(this);
var smartProp = prop as ISmartProperty;
if (smartProp != null && smartProp.HasValue)
{
if (firstJoin)
{
result.Append("?");
firstJoin = false;
}
else
{
result.Append("&");
}
result.Append(declaredField.Name + "=" + prop);
}
}
return result.ToString();
}
}
#pragma warning restore 1591
}

View File

@@ -1,58 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Reflection;
using System.Text;
using QuantConnect.Brokerages.Oanda.RestV1.Framework;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represents the Restful web response from Oanda.
/// </summary>
public class Response
{
public override string ToString()
{
// use reflection to display all the properties that have non default values
StringBuilder result = new StringBuilder();
var props = this.GetType().GetTypeInfo().DeclaredProperties;
result.AppendLine("{");
foreach (var prop in props)
{
if (prop.Name != "Content" && prop.Name != "Subtitle" && prop.Name != "Title" && prop.Name != "UniqueId")
{
object value = prop.GetValue(this);
bool valueIsNull = value == null;
object defaultValue = Common.GetDefault(prop.PropertyType);
bool defaultValueIsNull = defaultValue == null;
if ((valueIsNull != defaultValueIsNull) // one is null when the other isn't
|| (!valueIsNull && (value.ToString() != defaultValue.ToString()))) // both aren't null, so compare as strings
{
result.AppendLine(prop.Name + " : " + prop.GetValue(this));
}
}
}
result.AppendLine("}");
return result.ToString();
}
}
#pragma warning restore 1591
}

View File

@@ -1,34 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System.Collections.Generic;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications
{
#pragma warning disable 1591
/// <summary>
/// Represent the Active Trades Web Response.
/// </summary>
public class TradesResponse
{
public List<TradeData> trades;
public string nextPage;
}
#pragma warning restore 1591
}

View File

@@ -1,36 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represents a single event when subscribed to the streaming events.
/// </summary>
public class Event : IHeartbeat
{
public Heartbeat heartbeat { get; set; }
public Transaction transaction { get; set; }
public bool IsHeartbeat()
{
return (heartbeat != null);
}
}
#pragma warning restore 1591
}

View File

@@ -1,31 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represent a Heartbeat for an <see cref="Event"/> class.
/// </summary>
public class Heartbeat
{
public string time { get; set; }
}
#pragma warning restore 1591
}

View File

@@ -1,31 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represents the interface for the HeartBeat and RateStreamResponse class.
/// </summary>
public interface IHeartbeat
{
bool IsHeartbeat();
}
#pragma warning restore 1591
}

View File

@@ -1,140 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represents whether a property is optional.
/// </summary>
public class IsOptionalAttribute : Attribute
{
public override string ToString()
{
return "Is Optional";
}
}
/// <summary>
/// Represents maximum value of a property.
/// </summary>
public class MaxValueAttribute : Attribute
{
public object Value { get; set; }
public MaxValueAttribute(int i)
{
Value = i;
}
}
/// <summary>
/// Represents minimum value of a property.
/// </summary>
public class MinValueAttribute : Attribute
{
public object Value { get; set; }
public MinValueAttribute(int i)
{
Value = i;
}
}
/// <summary>
/// Represents a financial instrument / product provided by Oanda.
/// </summary>
public class Instrument
{
public bool HasInstrument;
private string _instrument;
public string instrument
{
get { return _instrument; }
set
{
_instrument = value;
HasInstrument = true;
}
}
public bool HasdisplayName;
private string _displayName;
public string displayName
{
get { return _displayName; }
set
{
_displayName = value;
HasdisplayName = true;
}
}
public bool Haspip;
private string _pip;
public string pip
{
get { return _pip; }
set
{
_pip = value;
Haspip = true;
}
}
[IsOptional]
public bool HaspipLocation;
private int _pipLocation;
public int pipLocation
{
get { return _pipLocation; }
set
{
_pipLocation = value;
HaspipLocation = true;
}
}
[IsOptional]
public bool HasextraPrecision;
private int _extraPrecision;
public int extraPrecision
{
get { return _extraPrecision; }
set
{
_extraPrecision = value;
HasextraPrecision = true;
}
}
public bool HasmaxTradeUnits;
private int _maxTradeUnits;
public int maxTradeUnits
{
get { return _maxTradeUnits; }
set
{
_maxTradeUnits = value;
HasmaxTradeUnits = true;
}
}
}
#pragma warning restore 1591
}

View File

@@ -1,45 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represents an order on Oanda.
/// </summary>
public class Order : Response
{
public long id { get; set; }
public string instrument { get; set; }
public int units { get; set; }
public string side { get; set; }
public string type { get; set; }
public string time { get; set; }
public double price { get; set; }
public double takeProfit { get; set; }
public double stopLoss { get; set; }
public string expiry { get; set; }
public double upperBound { get; set; }
public double lowerBound { get; set; }
public int trailingStop { get; set; }
}
#pragma warning restore 1591
}

View File

@@ -1,34 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represent a Position in Oanda.
/// </summary>
public class Position
{
public string side { get; set; }
public string instrument { get; set; }
public int units { get; set; }
public double avgPrice { get; set; }
}
#pragma warning restore 1591
}

View File

@@ -1,63 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represents the Price object creating Orders for each instrument.
/// </summary>
public class Price
{
public enum State
{
Default,
Increasing,
Decreasing
};
public string instrument { get; set; }
public string time;
public double bid { get; set; }
public double ask { get; set; }
public string status;
public State state = State.Default;
public void update( Price update )
{
if ( this.bid > update.bid )
{
state = State.Decreasing;
}
else if ( this.bid < update.bid )
{
state = State.Increasing;
}
else
{
state = State.Default;
}
this.bid = update.bid;
this.ask = update.ask;
this.time = update.time;
}
}
#pragma warning restore 1591
}

View File

@@ -1,42 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2012-2013 OANDA Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using QuantConnect.Brokerages.Oanda.RestV1.DataType.Communications;
namespace QuantConnect.Brokerages.Oanda.RestV1.DataType
{
#pragma warning disable 1591
/// <summary>
/// Represents a Trade Data object containing the details of a trade.
/// </summary>
public class TradeData : Response
{
public long id { get; set; }
public int units { get; set; }
public string side { get; set; }
public string instrument { get; set; }
public string time { get; set; }
public double price { get; set; }
public double takeProfit { get; set; }
public double stopLoss { get; set; }
public int trailingStop { get; set; }
public double trailingAmount { get; set; }
}
#pragma warning restore 1591
}

Some files were not shown because too many files have changed in this diff Show More