Compare commits

..

78 Commits
9385 ... 9856

Author SHA1 Message Date
Stefano Raggi
e98b31fc4c Update CoinApi symbol mapping + symbol properties database (#4888)
* Update crypto entries in SPBD

- Update values for GDAX and Bitfinex, with latest values from exchanges
- Update symbols with >3 letters
- [Fix] Remove  same base-quote entries

* Update unit tests

Increment in precision given smaller lot sizes in SPDB crypto entries.

* Add GDAX symbol properties downloader (unit test)

* Update GDAX symbol properties database

* Add BrokerageSymbol to symbol properties database

* Update GDAX symbol properties

* Address review

- Rename BrokerageSymbol to MarketTicker

* Add GDAX symbol mapper

* Update GDAXBrokerage to use symbol mapper

* Fix GDAX brokerage unit tests

* Replace GDAXSymbolMapper with SymbolPropertiesDatabaseSymbolMapper

* Address review

- use Symbol key in dictionaries

* Rename BrokerageSymbol to MarketTicker

* Update unit test

* Add Bitfinex symbol properties downloader (unit test)

* Add Bitfinex symbol market ticker to downloader

* Update Bitfinex symbol properties database

* Update BitfinexBrokerage to use SymbolPropertiesDatabaseSymbolMapper

* Remove Bitfinex test symbols from db

* Update Binance symbol properties database

* Update BinanceBrokerage to use SymbolPropertiesDatabaseSymbolMapper

* Add missing Binance test case

* Update symbol properties database

- gdax, bitfinex, binance

* Update CoinApi symbol mapper for new SPDB

* Exclude Bitfinex BCH pre-2018-fork in CoinApiSymbolMapper

* Remove unused properties

* Add CoinApi mappings

Co-authored-by: JJD <jjdambrosio@gmail.com>
2020-11-23 19:15:31 -03:00
Stefano Raggi
d2eae2f652 IB Brokerage Updates (#4945)
* Update C# API DLL up to  Oct 30, 2020

- a8e66989e5

* Add extra account information logging after connect
2020-11-23 11:53:28 -03:00
Gerardo Salazar
0f0a2bc9a8 Fixes weeklies parsing, causing certain futures to be inaccessible in QCAlgorithm (#4936)
* Fixes weeklies parsing, causing certain futures to be inaccessible in Algorithm

  The FuturesExpiryFunction expects the contract month of the Future,
  not the expiration. As a result, the contract gets filtered as a
  weekly contract, rather than as a standard due to the discrepancy
  between the expiry dates when the contract month differs from the
  expiry date's month.

  A very important fact to note is that futures can and do expire prior
  to the contract month. BZ,(brent crude financial futures) expire two
  months prior to the contract month, CL one month prior, etc.

  There has been an addition that contains a "reverse" futures expiry function
  lookup table. We use this to lookup the contract month to re-calculate
  the Future expiry.

  This PR also fixes dairy and adds extra expiry dates. Dairy can have
  an expiry *after* the contract month, so a new path was added to the
  SymbolRepresentation to ensure that these contracts are loaded
  correctly.

* Address review: Adds tests and fixes bug in SymbolRepresentation

  * Updates SID comment on `Date` property to reflect fact that we use
    future expiry for its value

  * Fixes bug in SymbolRepresentation where expiration day would always
    be 01 when parsing a contract with an expiration after the contract
    month

  * Fixes bug in SybmolRepresentation where expiration year would be
    four digits long when parsing a contract with an expiration after
    the contract month

  * Fixes some bad dairy expiry dates

  * Adds tests for SymbolRepresentation and the futures filtering for
    standard contracts

  * Renames method used to extract delta between contract month and
    expiry date

* Removes GH comment and restores Futures contract month expiry param
2020-11-20 11:58:49 -03:00
Martin-Molinero
8ad81dca71 Update readme.md (#4949) 2020-11-20 09:12:23 -03:00
James Kardatzke
f965f34a3f Added Quiver API token variable to Config (#4946)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Delete QuiverWikipedia.cs

* Delete QuiverWallStreetBets.cs

* Delete QuiverDataDownloader.cs

* Delete QuiverWallStreetBetsDataDownloader.cs

* Delete QuiverWikipediaDataDownloader.cs

* Delete QuiverDataAlgorithm.cs

* Delete QuiverHouse.cs

* Delete QuiverPoliticalBeta.cs

* Delete QuiverSenate.cs

* Delete QuiverHouseDataDownloader.cs

* Delete QuiverPoliticalBetaDataDownloader.cs

* Delete QuiverSenateDataDownloader.cs
2020-11-18 20:32:03 -03:00
Colton Sellers
06a7d54c38 run_docker_notebook bat hotfix (#4943) 2020-11-17 21:40:29 -03:00
Colton Sellers
7174fcb9d7 RoundDown fix for PeriodCountConsolidatorBase (#4940)
* Limit rounding interval to 1 day, add SubtractRoundDown and AddRoundUp functions

* fix error message

* Only subtract round down if period is greater than a day

* Tests

* Comment clarification

* Pivot solution to simple fix

* fix error message

* Remove RoundDown/Up limitations; add remarks

* address review
2020-11-17 21:40:10 -03:00
IlshatGaripov
29e9d678f2 Bug 4815 iex web socket library (#4914)
* Fixes 4815 by loading the requested assembly from different folder.

# Conflicts:
#	ToolBox/Program.cs

* Upgrades System.Collections.Immutable to Version=1.2.5.0

* Creates a prototype for SSE streaming in IEXDataQueueHandler.

* Revert the changes in Tick.cs

* Implements a logic in IEXDataQueueHandler that updates the data-feed subscription after Subscribe/Unsubscribe

* Implements IEXCouldSubscribeMoreThan100Symbols - which fails and other small fixes.

* Implements DoForEach LinqExtensions

* Implements IEXEventSourceCollection that wraps all logic that is SSE-subscriptions and symbol-limits-per-connection concerned.

* Changes:

1) Fixes to address review.
2) Makes IexMarketPercent in QuoteSSE nullable as null values are assigned to in this field in data object received before the traing session start.
3) Deprecates helper Subscribe/Unsubscribe in IEXDataQueueHandler and IEXCouldSubscribe test.

* Fixes:

1) _refreshEvent.Reset() order was not correct - should be called before UpdateSubscription
2) ProcessJsonObject- leaves only the functionality to emit ticks.
3) IEXEventSourceCollection - replaces int counter with CountdownEvent to improve the logic - in particular, need a mechanism that would not allow the repeated call to continue until the first one is completed

* Refines the logic with parsing a data snapshot.

* Fixes few more bugs:

1) Logic in ProcessJsonObject
2) Logic in UpdateSubscription - need to introduce additional ManualResetEvent to implement the intended logic - otherwise the logic is not suitable for general case

* Introduce rate-gate limit in IEXEventSourceCollection:

because when subscribing to a bunch of shares (more than 200 for example)
the violation of rate gate policy may occur, which described in API docs asRequest Limits

IEX Cloud only applies request limits per IP address to ensure system stability.
We limit requests to 100 per second per IP measured in milliseconds, so no more than 1 request per 10 milliseconds.
SSE endpoints are limited to 50 symbols per connection. You can make multiple connections if you need to consume more than 50 symbols.:

* Few additional fixes done after real time testing

* Adds xml-docs in stream response object + renaming a file.

* Fixes:

1) Additional StreamResponseStocksUS parsing issues, that can happen outside of regular exchange hours.
2) Cancel clientUpdateThread by means of CancellationTokenSource
3) Replace BuildSymbolsQuery by string.Join

* Fixes:

1) Changing Log Trace -> Debug
2) Adds ConfigureAwait(false) to async method call
3) Removes direct reference to System.Net.Http

* Removes a task and manual reset event in IEXEventSourceCollection

* Additions:

1) IEXEventSourceCollectionSubscribes test
2) GetSnpStocksArray() helper method
3) Installs packages in QC.tests : HtmlAgilityPack & LaunchDarkly.EventSource

* IEX history provider fixes :

1) Tiny bug in ProcessJsonObject - use continue instead of return as execution is inside the for-each block)
2) Adds period variable for the historical data retrieved
3) Fixing from  if (date.Date < start.Date || date.Date > end.Date)  conditional check -->  if (date < start || date > end)  for more precise sorting.

* Changes:

1) Removes HtmlAgilityPack and SNP scraper
2) Uses hard coded symbols instead

* Bug fix:

 - at certain hours (example: before pre-market open or on holidays) IEX may send no data on subscription - when trying to connect during those hours Message handler may not be fired - need to place the counter signal to client.Opened to be informed of successful connect.

* Implements:

1) IEXEventSourceCollectionSubscriptionThoroughTest and MockedIEXEventSourceCollection
2) Makes changes to IEXEventSourceCollection accordingly to allow the thorough testing.

* Fixes formatting issue in StreamResponseStocksUS

* Small fix for a new tests:

- Change RemovedClientSymbols to keep not clients itself, but symbols array, because clients are being disposed right further

* Enables extended logging in Toolbox.

* Fixing IEX historical data fetcher bugs:

1) Bug in IEXDataDownloader.cs - HistoryRequest not precisely correct.
2) Enables day-by-day daily bar downloading in IEXDataQueueHandler.
Motivation: Suppose we need data for some interval in the past - from-date=20170915-00:00:00 --to-date=20171103-00:00:00.
With current behavior IEX would have to download all the historical data from =20170915-00:00:00  up to this day.
3) Extends SynchronizingHistoryProvider

* Enables async fashion historical data download

* More fixes to IEXCouldGetHistory test.

* Reverts day-by-day daily bar downloading and other fixes.

* Removes needless packages & references

* Fix package reference

* To address review

* Sort out zero price ticks:

after testing on real-time algo 30 min before the market open now - IEX may send updates for many securities with zero lastPrice, lastSize - fix to sort such entries out

* Workaround for missing QuoteTicks timestamps:

Since we don't have a stamp for quote tick updates (only for trades) we calculate the average delay between trade tick's time stamp and local time, and
assuming that delay in average is the same for quote updates - just assign the local machine time adjusted for this average

* Simplifies the things.

* Changes:

1) Deprecates quote updates for IEX stocks.
2) Reduce the stream updates to reduce costs to ->
# Stock Quotes every 1 second (per symbol? )
# Can be up to 54,000 messages per symbol per day
https://iexcloud.io/docs/api/#how-messages-work

* Fixes:

1) IEXDataQueueHandler: give an error message on extended market hours or tick resolution subscription request. As they are not really well supported by IEX.
2) Few small fixes in IEXEventSourceCollection, including additional condition for when the subscription remains irrevocable.
2020-11-16 21:10:14 -03:00
Colton Sellers
098ac7d0a9 Wider range of offsets on Month tests (#4935) 2020-11-16 15:15:32 -08:00
Stefano Raggi
6cb4411f6e Update IBAutomater to v1.0.31 (#4932)
- add window handler for unsupported IBGateway versions
2020-11-16 11:36:34 -03:00
James Kardatzke
46ef9a9dbb Quiver Quantitative Integration (#4869)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Delete QuiverHouseDataDownloader.cs

* Delete QuiverSenateDataDownloader.cs

* Delete QuiverPoliticalBetaDataDownloader.cs

* Add files via upload

* Delete QuiverHouse.cs

* Delete QuiverSenate.cs

* Delete QuiverPoliticalBeta.cs

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Delete QuiverDataAlgorithm.cs

* Add files via upload

* Add files via upload

* Add files via upload

* Addresses self review: Cleans up code and adds new unit tests

  * Adds Quiver* C# files to project
  * Adds new unit test for QuiverCongress
  * Adds Python algorithm example

* Address self reviews

- Adding some missing xml docs
- Removing unrequired imports.
- Minor rename from Date to ReportDate
- Live trading will throw InvalidOperationException

* Fixes for example algorithms

Co-authored-by: Gerardo Salazar <gsalaz9800@gmail.com>
Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-11-13 20:07:17 -03:00
Colton Sellers
d8d8134437 Support Offset in DateRules MonthStart/End WeekStart/End (#4916)
* Add offset capabilities and related tests

* Add tests for Weekend Offsets on Symbol DateRules

* Unify Iterator Behavior

* Refactor and consolidate functions to reduce duplicate code

* Positive offset values only

* Address review

* Expand Month tests to include Forex and Crypto cases

* Ensure order of days in schedule

* Refactor and unify behavior

* More edge cases and tuning

* Address review + more tests
2020-11-13 15:38:34 -03:00
Jasper van Merle
9a30c9bd5f Enable logging when building and uploading stubs (#4933)
* Enable Twine logging

* Enable setup.py logging
2020-11-12 17:42:09 -08:00
Jasper van Merle
f76a0efb0e Use new generator to generate stubs and publish to PyPI (#4899)
* Use new generator to generate stubs and publish to PyPI

* Hide twine stdout and stderr to prevent token from leaking
2020-11-12 13:27:00 -08:00
Colton Sellers
7238fcd0f3 Bug 4809 Docker Run Script Fixes and Improvements (#4922)
* Refactor and hotfix run script

Removed dependency on `realpath`, notably absent from macOS and some debian distros. Replaced with an equivalent bash function.

Cleaned up prompt response handling, replaced var tests with param expressions.

Removed potentially uneeded calls to sudo (again absent on some systems and most users are part of the docker group anyway). Instead, test if it's needed and get preauth with sudo -v.

Poll for running and stopped Lean containers, and prompt before replacing them. Would fail prior.

Uppercased variabled. Sorry.

* Revert docker output redirection to stderr

* Adjustments to maintain all functionality

* Mimic behavior across run scripts

* Update readme to reflect Docker script changes

* Ignore results storage

* Correct print statement

* Mirror changes on research docker scripts

* Make executable

* Handle already running container and sudo privs

* Fix for issues found in testing

* Address Review

* Small adjustments found in linux testing

* Doc improvement

* Add auto update option for Docker images

* Fix image var reference

Co-authored-by: Peter Kazazes <peter@peterk.co>
2020-11-11 15:48:02 -03:00
Martin-Molinero
91e8393aac DividedEventProvider distribution computation (#4828)
* DividedEventProvider distribution computation

- Update regression algorithm which was using a different reference
  price when calculating the dividend
- Adjust divided event provider to compute distribution using factor
  file reference price, if not 0. Adding unit tests
- For equities, only emit auxiliary data points for
  TradeBar configurations, not for QuoteBars, nor internal.

* Address reviews

- Split and Dividend event provider will throw an exception when there
  is no reference price available. Updating `wm` factor file which was
  missing references price and regression algorithms using WM.
- Updating unit tests asserting new exception
2020-11-11 15:47:51 -03:00
Stefano Raggi
5771635265 Fix Base Account Currency message for PaperBrokerage (#4929)
- the displayed message was not showing the default currency (USD)
2020-11-10 17:46:35 -03:00
Stefano Raggi
914486fdb6 Fix OutOfRangeException in Bitfinex subscriptions (#4926) 2020-11-09 16:42:22 -03:00
Martin-Molinero
61fda8b62c Protobuf will use recyclable memory stream (#4921)
* Protobuf will use recyclable memory stream

- Serialization will reuse recyclable memory streams
- Remove serialization of exchange and sale condition for ticks.
  Updating unit tests.
- There is no need to serialize BaseData.EndTime, covered by unit tests.

* Tick will keep a parsed sale condition property

* Readd tick exchange. Json ignore ParsedSaleCondition
2020-11-06 20:59:25 -03:00
Stefano Raggi
124ac3b98e Fix FxcmBrokerage GetOpenOrders returning empty (#4917) 2020-11-06 10:35:11 -03:00
Colton Sellers
9dca43bccb Expand Filtering of Contracts by type to Futures (#4891)
* Implement future type filter

* Filter weeklys test

* Fix for test, contracts were being filtered out by new type filter

* Share core contract filtering logic in new base class

* Catch Future symbols we don't have Expiry functions for

* Expand tests for new filtering

* Address review

* Small doc change

* Compare Date component for expiry

* Clarifying comment
2020-11-04 20:55:33 -03:00
Alexandre Catarino
6efeee07dc Implements Equity Fill Model (#4913)
* Implements Equity Fill Model

This commit sets the base to create a new equity fill model and the `EquityFillModel` is just a copy of `FillModel`.

* Adds Summary to FillModelPythonWrapper.GetPricesInternal

Adds summary to FillModelPythonWrapper.GetPricesInternal with remarks that it's a temporarily method to help the refactoring of fill models.
2020-11-04 15:51:41 -03:00
Andreas Sundebo
c452dd3726 Feature 4893 performance improvements (#4895)
* Change GetSubscriptionDataConfigs to return IEnumerable<SubscriptionDataConfig>

* Move localTime outside loop

* Remove legacy code

* Remove unnecessary OrderBy

* Reorder conditions to reduce number of times Contains() is called

* Revert "Remove unnecessary OrderBy"

This reverts commit 85383b062e.

* Revert "Change GetSubscriptionDataConfigs to return IEnumerable<SubscriptionDataConfig>"

This reverts commit cbd97c9f36.
2020-11-03 19:35:36 -03:00
Stefano Raggi
c602fd0a3f Fix ambiguous future symbol error in IB brokerage (#4912)
* Fix ambiguous future symbol error in IB brokerage

* Address review

- Fix symbol mapping
2020-11-03 14:44:17 -03:00
Stefano Raggi
104071cda5 Do not terminate algorithm for exceptions in GDAX Fill Monitor (#4910)
* Do not terminate algorithm for exceptions in GDAX Fill Monitor

* Address review

- Only emit warning for REST API errors, other errors are fatal
2020-11-02 21:01:21 -03:00
Aaron Janeiro Stone
a5d9526d65 Add defaults (as comments) to Python.Runtime (#4904)
* Redone defaults for python runtime dll config and readme

* Update Python.Runtime.dll.config

Co-authored-by: Jared <jaredbroad@gmail.com>
2020-11-02 15:02:59 -08:00
Juan José D'Ambrosio
8e525c63fc Fix issue in TiingoNews converter if tickers contains space (#4908)
Also, if ticker contains pipe ( "|" )  it'll be ignored.
2020-11-02 13:58:39 -03:00
Stefano Raggi
d0e9134cc9 IB Brokerage Updates (#4900)
* Add RequestId to request information logging

* Use unique request id across all request types (orders, subscriptions, data queries)

- Previously we had three separate counters for request types and this was causing request information messages to be overwritten (different request types with same ids)

* Update GetContractDetails to log all contracts found
2020-11-02 10:58:12 -03:00
Gerardo Salazar
883d354a98 Adds DC future contract symbol mapping for IB (#4905)
* Fixes SPDB entry for Class III/IV Milk
2020-11-02 10:57:57 -03:00
Colton Sellers
76a53eb096 Local Object Store Refactor and Fixes (#4880)
* Store temp files in subdirectory

* Fix Dispose case for new temp dir

* Adjust tests for new temp dir

* Dispose unit tests

* Unit test for issue 4811

* Refactor for not using temp files

* Fix storage checks for saving data, plus tests

* Use Base64 for storing keys and decoding them; handles odd key strings

* Don't allow "?" in a key

* Address review

* Deleted test cases

* PersistData handle deletion of files

* Refactor GetFilePath to use Persist()

* Make PathForKey protected
2020-10-30 21:36:23 -03:00
Colton Sellers
c02ee1b0d8 QB Set Start Date Relative to Data Release (#4894)
* Set start date relative to timezone

* Address Review
2020-10-30 21:20:32 -03:00
Colton Sellers
9167882ab2 Feature Notebook Api Support (#4898)
* Modify Notebook scripts to load API instance

* Modify docker to move script to IPython profile

* Fix dockerfile copying of start.py

* Fix break for C# cloud and docker load

* Unnecessary path finding

* Update example notebooks

* Update documentation

* Address review

* Adjust readme

* Poor choice of words
2020-10-30 21:05:09 -03:00
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
453 changed files with 21923 additions and 8159 deletions

3
.gitignore vendored
View File

@@ -271,3 +271,6 @@ QuantConnect.Lean.sln.DotSettings*
#User notebook files
Research/Notebooks
#Docker result files
Results/

14
.idea/readme.md generated
View File

@@ -91,14 +91,14 @@ From a terminal; Pycharm has a built in terminal on the bottom taskbar labeled *
2. Using the **run_docker.cfg** to store args for repeated use; any blank entries will resort to default values! example: **_./run_docker.bat run_docker.cfg_**
image=quantconnect/lean:latest
config_file=
data_dir=
results_dir=
debugging=
python_dir=
IMAGE=quantconnect/lean:latest
CONFIG_FILE=
DATA_DIR=
RESULTS_DIR=
DEBUGGING=
PYTHON_DIR=
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat debugging=y_**
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat DEBUGGING=y_**
* Accepted args for inline include all listed in the file in #2; must follow the **key=value** format
<br />

View File

@@ -23,4 +23,4 @@ script:
- msbuild /p:Configuration=Release /p:VbcToolExe=vbnc.exe QuantConnect.Lean.sln
- mono ./testrunner/NUnit.ConsoleRunner.3.11.1/tools/nunit3-console.exe ./Tests/bin/Release/QuantConnect.Tests.dll --where "cat != TravisExclude" --labels=Off
- chmod +x ci_build_stubs.sh
- sudo -E ./ci_build_stubs.sh -ipy -g -p
- sudo -E ./ci_build_stubs.sh -d -t -g -p

View File

@@ -106,14 +106,14 @@ From a terminal launch the run_docker.bat/.sh script; there are a few choices on
2. Using the **run_docker.cfg** to store args for repeated use; any blank entries will resort to default values! example: **_./run_docker.bat run_docker.cfg_**
image=quantconnect/lean:latest
config_file=
data_dir=
results_dir=
debugging=
python_dir=
IMAGE=quantconnect/lean:latest
CONFIG_FILE=
DATA_DIR=
RESULTS_DIR=
DEBUGGING=
PYTHON_DIR=
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat debugging=y_**
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat DEBUGGING=y_**
* Accepted args for inline include all listed in the file in #2
<br />

34
.vscode/readme.md vendored
View File

@@ -101,14 +101,14 @@ This section will cover how to actually launch Lean in the container with your d
<h3>Option 1 (Recommended)</h3>
In VS Code click on the debug/run icon on the left toolbar, at the top you should see a drop down menu with launch options, be sure to select **Debug in Container**. This option will kick off a launch script that will start the docker. With this specific launch option the parameters are already configured in VS Codes **tasks.json** under the **run-docker** task args. These set arguements are:
In VS Code click on the debug/run icon on the left toolbar, at the top you should see a drop down menu with launch options, be sure to select **Debug in Container**. This option will kick off a launch script that will start the docker. With this specific launch option the parameters are already configured in VS Codes **tasks.json** under the **run-docker** task args. These set arguments are:
"image=quantconnect/lean:latest",
"config_file=${workspaceFolder}/Launcher/config.json",
"data_dir=${workspaceFolder}/Data",
"results_dir=${workspaceFolder}/",
"debugging=Y",
"python_location=${workspaceFolder}/Algorithm.Python"
"IMAGE=quantconnect/lean:latest",
"CONFIG_FILE=${workspaceFolder}/Launcher/config.json",
"DATA_DIR=${workspaceFolder}/Data",
"RESULTS_DIR=${workspaceFolder}/Results",
"DEBUGGING=Y",
"PYHTON_DIR=${workspaceFolder}/Algorithm.Python"
As defaults these are all great! Feel free to change them as needed for your setup.
@@ -120,21 +120,21 @@ From a terminal launch the run_docker.bat/.sh script; there are a few choices on
1. Launch with no parameters and answer the questions regarding configuration (Press enter for defaults)
*   Enter docker image [default: quantconnect/lean:latest]:
*   Enter absolute path to Lean config file [default: _~currentDir_\Launcher\config.json]:
*   Enter absolute path to Data folder [default: ~_currentDir_\Data\]:
*   Enter absolute path to store results [default: ~_currentDir_\]:
*   Enter absolute path to Lean config file [default: .\Launcher\config.json]:
*   Enter absolute path to Data folder [default: .\Data\]:
*   Enter absolute path to store results [default: .\Results]:
* Would you like to debug C#? (Requires mono debugger attachment) [default: N]:
2. Using the **run_docker.cfg** to store args for repeated use; any blank entries will resort to default values! example: **_./run_docker.bat run_docker.cfg_**
image=quantconnect/lean:latest
config_file=
data_dir=
results_dir=
debugging=
python_dir=
IMAGE=quantconnect/lean:latest
CONFIG_FILE=
DATA_DIR=
RESULTS_DIR=
DEBUGGING=
PYTHON_DIR=
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat debugging=y_**
3. Inline arguments; anything you don't enter will use the default args! example: **_./run_docker.bat DEBUGGING=y_**
* Accepted args for inline include all listed in the file in #2
<br />

14
.vscode/tasks.json vendored
View File

@@ -51,13 +51,13 @@
"command": "${workspaceFolder}/run_docker.sh"
},
"args": [
"image=quantconnect/lean:latest",
"config_file=${workspaceFolder}/Launcher/config.json",
"data_dir=${workspaceFolder}/Data",
"results_dir=${workspaceFolder}/",
"debugging=Y",
"python_dir=${workspaceFolder}/Algorithm.Python",
"exit=Y"
"IMAGE=quantconnect/lean:latest",
"CONFIG_FILE=${workspaceFolder}/Launcher/config.json",
"DATA_DIR=${workspaceFolder}/Data",
"RESULTS_DIR=${workspaceFolder}/Results",
"DEBUGGING=Y",
"PYTHON_DIR=${workspaceFolder}/Algorithm.Python",
"EXIT=Y"
],
"problemMatcher": [
{

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

@@ -0,0 +1,62 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Custom.Quiver;
namespace QuantConnect.Algorithm.CSharp.AltData
{
/// <summary>
/// Quiver Quantitative is a provider of alternative data.
/// This algorithm shows how to consume the <see cref="QuiverWallStreetBets"/>
/// </summary>
public class QuiverWallStreetBetsDataAlgorithm : QCAlgorithm
{
/// <summary>
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
/// </summary>
public override void Initialize()
{
SetStartDate(2019, 1, 1);
SetEndDate(2020, 6, 1);
SetCash(100000);
var aapl = AddEquity("AAPL", Resolution.Daily).Symbol;
var quiverWSBSymbol = AddData<QuiverWallStreetBets>(aapl).Symbol;
var history = History<QuiverWallStreetBets>(quiverWSBSymbol, 60, Resolution.Daily);
Debug($"We got {history.Count()} items from our history request");
}
public override void OnData(Slice data)
{
var points = data.Get<QuiverWallStreetBets>();
foreach (var point in points.Values)
{
// Go long in the stock if it was mentioned more than 5 times in the WallStreetBets daily discussion
if (point.Mentions > 5)
{
SetHoldings(point.Symbol.Underlying, 1);
}
// Go short in the stock if it was mentioned less than 5 times in the WallStreetBets daily discussion
if (point.Mentions < 5)
{
SetHoldings(point.Symbol.Underlying, -1);
}
}
}
}
}

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

@@ -187,12 +187,12 @@ namespace QuantConnect.Algorithm.CSharp
{"Total Trades", "6441"},
{"Average Win", "0.07%"},
{"Average Loss", "-0.07%"},
{"Compounding Annual Return", "13.284%"},
{"Compounding Annual Return", "13.331%"},
{"Drawdown", "10.700%"},
{"Expectancy", "0.061"},
{"Net Profit", "13.284%"},
{"Sharpe Ratio", "0.96"},
{"Probabilistic Sharpe Ratio", "46.111%"},
{"Net Profit", "13.331%"},
{"Sharpe Ratio", "0.963"},
{"Probabilistic Sharpe Ratio", "46.232%"},
{"Loss Rate", "46%"},
{"Win Rate", "54%"},
{"Profit-Loss Ratio", "0.97"},
@@ -200,15 +200,15 @@ namespace QuantConnect.Algorithm.CSharp
{"Beta", "-0.066"},
{"Annual Standard Deviation", "0.121"},
{"Annual Variance", "0.015"},
{"Information Ratio", "0.004"},
{"Information Ratio", "0.006"},
{"Tracking Error", "0.171"},
{"Treynor Ratio", "-1.754"},
{"Total Fees", "$8669.33"},
{"Treynor Ratio", "-1.761"},
{"Total Fees", "$8669.41"},
{"Fitness Score", "0.675"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "1.124"},
{"Return Over Maximum Drawdown", "1.242"},
{"Sortino Ratio", "1.127"},
{"Return Over Maximum Drawdown", "1.246"},
{"Portfolio Turnover", "1.64"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
@@ -223,7 +223,7 @@ namespace QuantConnect.Algorithm.CSharp
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "-1120327913"}
{"OrderListHash", "-75671425"}
};
}
}

View File

@@ -160,12 +160,12 @@ namespace QuantConnect.Algorithm.CSharp
{"Total Trades", "5059"},
{"Average Win", "0.08%"},
{"Average Loss", "-0.08%"},
{"Compounding Annual Return", "14.901%"},
{"Compounding Annual Return", "14.950%"},
{"Drawdown", "10.600%"},
{"Expectancy", "0.075"},
{"Net Profit", "14.901%"},
{"Sharpe Ratio", "1.068"},
{"Probabilistic Sharpe Ratio", "50.201%"},
{"Net Profit", "14.950%"},
{"Sharpe Ratio", "1.072"},
{"Probabilistic Sharpe Ratio", "50.327%"},
{"Loss Rate", "45%"},
{"Win Rate", "55%"},
{"Profit-Loss Ratio", "0.97"},
@@ -173,15 +173,15 @@ namespace QuantConnect.Algorithm.CSharp
{"Beta", "-0.066"},
{"Annual Standard Deviation", "0.121"},
{"Annual Variance", "0.015"},
{"Information Ratio", "0.08"},
{"Information Ratio", "0.083"},
{"Tracking Error", "0.171"},
{"Treynor Ratio", "-1.963"},
{"Total Fees", "$6806.57"},
{"Treynor Ratio", "-1.971"},
{"Total Fees", "$6806.67"},
{"Fitness Score", "0.694"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "1.261"},
{"Return Over Maximum Drawdown", "1.404"},
{"Sortino Ratio", "1.265"},
{"Return Over Maximum Drawdown", "1.409"},
{"Portfolio Turnover", "1.296"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
@@ -196,7 +196,7 @@ namespace QuantConnect.Algorithm.CSharp
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "974523768"}
{"OrderListHash", "1142077166"}
};
}
}

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

@@ -53,7 +53,7 @@ namespace QuantConnect.Algorithm.CSharp
}
var firstBar = history.First().Bars.GetValue(symbol);
if (firstBar.EndTime != new DateTime(1998, 3, 3) || firstBar.Close != 26.3607004m)
if (firstBar.EndTime != new DateTime(1998, 3, 3) || firstBar.Close != 25.11427695m)
{
throw new Exception("First History bar - unexpected data received");
}

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

@@ -56,6 +56,13 @@ namespace QuantConnect.Algorithm.CSharp
/// <param name="slice">The current slice of data keyed by symbol string</param>
public override void OnData(Slice slice)
{
foreach (var dividend in slice.Dividends.Values)
{
if (dividend.ReferencePrice != 32.59m || dividend.Distribution != 3.82m)
{
throw new Exception($"{Time} - Invalid dividend {dividend}");
}
}
if (!Portfolio.Invested)
{
if (Time.Day == 28 && Time.Hour > 9 && Time.Minute > 0)
@@ -139,11 +146,11 @@ namespace QuantConnect.Algorithm.CSharp
{"Total Trades", "4"},
{"Average Win", "0%"},
{"Average Loss", "-0.02%"},
{"Compounding Annual Return", "-0.453%"},
{"Compounding Annual Return", "-0.492%"},
{"Drawdown", "0.000%"},
{"Expectancy", "-1"},
{"Net Profit", "-0.006%"},
{"Sharpe Ratio", "-3.554"},
{"Sharpe Ratio", "-3.943"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
@@ -152,15 +159,15 @@ namespace QuantConnect.Algorithm.CSharp
{"Beta", "0"},
{"Annual Standard Deviation", "0.002"},
{"Annual Variance", "0"},
{"Information Ratio", "-3.554"},
{"Information Ratio", "-3.943"},
{"Tracking Error", "0.002"},
{"Treynor Ratio", "0"},
{"Total Fees", "$4.00"},
{"Fitness Score", "0.001"},
{"Fitness Score", "0"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "-1.768"},
{"Return Over Maximum Drawdown", "-2.808"},
{"Portfolio Turnover", "0.001"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},

View File

@@ -142,8 +142,18 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AddAlphaModelAlgorithm.cs" />
<Compile Include="CustomBuyingPowerModelAlgorithm.cs" />
<Compile Include="AddOptionContractExpiresRegressionAlgorithm.cs" />
<Compile Include="AltData\QuiverWallStreetBetsDataAlgorithm.cs" />
<Compile Include="ScaledFillForwardDataRegressionAlgorithm.cs" />
<Compile Include="DailyHistoryForDailyResolutionRegressionAlgorithm.cs" />
<Compile Include="DailyHistoryForMinuteResolutionRegressionAlgorithm.cs" />
<Compile Include="ExtendedMarketHoursHistoryRegressionAlgorithm.cs" />
<Compile Include="EquityTickQuoteAdjustedModeRegressionAlgorithm.cs" />
<Compile Include="AddOptionContractFromUniverseRegressionAlgorithm.cs" />
<Compile Include="CoarseFineOptionUniverseChainRegressionAlgorithm.cs" />
<Compile Include="OptionChainedAndUniverseSelectionRegressionAlgorithm.cs" />
<Compile Include="OptionAssignmentRegressionAlgorithm.cs" />
<Compile Include="SwitchDataModeRegressionAlgorithm.cs" />
<Compile Include="AddRemoveOptionUniverseRegressionAlgorithm.cs" />
<Compile Include="AddRemoveSecurityRegressionAlgorithm.cs" />

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

@@ -117,28 +117,28 @@ namespace QuantConnect.Algorithm.CSharp
{"Total Trades", "3528"},
{"Average Win", "0.67%"},
{"Average Loss", "-0.71%"},
{"Compounding Annual Return", "17.318%"},
{"Compounding Annual Return", "17.227%"},
{"Drawdown", "63.700%"},
{"Expectancy", "0.020"},
{"Net Profit", "17.318%"},
{"Sharpe Ratio", "0.836"},
{"Probabilistic Sharpe Ratio", "33.715%"},
{"Net Profit", "17.227%"},
{"Sharpe Ratio", "0.834"},
{"Probabilistic Sharpe Ratio", "33.688%"},
{"Loss Rate", "48%"},
{"Win Rate", "52%"},
{"Profit-Loss Ratio", "0.95"},
{"Alpha", "0.826"},
{"Alpha", "0.825"},
{"Beta", "-0.34"},
{"Annual Standard Deviation", "0.945"},
{"Annual Variance", "0.893"},
{"Information Ratio", "0.714"},
{"Information Ratio", "0.713"},
{"Tracking Error", "0.957"},
{"Treynor Ratio", "-2.325"},
{"Total Fees", "$24713.42"},
{"Treynor Ratio", "-2.323"},
{"Total Fees", "$24760.85"},
{"Fitness Score", "0.54"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "0.24"},
{"Return Over Maximum Drawdown", "0.272"},
{"Sortino Ratio", "0.238"},
{"Return Over Maximum Drawdown", "0.27"},
{"Portfolio Turnover", "7.204"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
@@ -153,7 +153,7 @@ namespace QuantConnect.Algorithm.CSharp
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "-1547947497"}
{"OrderListHash", "843493486"}
};
}
}

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,50 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Custom.Quiver import *
### <summary>
### Quiver Quantitative is a provider of alternative data.
### This algorithm shows how to consume the 'QuiverWallStreetBets'
### </summary>
class QuiverWallStreetBetsDataAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2019, 1, 1)
self.SetEndDate(2020, 6, 1)
self.SetCash(100000)
aapl = self.AddEquity("AAPL", Resolution.Daily).Symbol
quiverWSBSymbol = self.AddData(QuiverWallStreetBets, aapl).Symbol
history = self.History(QuiverWallStreetBets, quiverWSBSymbol, 60, Resolution.Daily)
self.Debug(f"We got {len(history)} items from our history request");
def OnData(self, data):
points = data.Get(QuiverWallStreetBets)
for point in points.Values:
# Go long in the stock if it was mentioned more than 5 times in the WallStreetBets daily discussion
if point.Mentions > 5:
self.SetHoldings(point.Symbol.Underlying, 1)
# Go short in the stock if it was mentioned less than 5 times in the WallStreetBets daily discussion
if point.Mentions < 5:
self.SetHoldings(point.Symbol.Underlying, -1)

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" />
@@ -63,6 +65,7 @@
<Content Include="Alphas\VIXDualThrustAlpha.py" />
<Content Include="AltData\CachedAlternativeDataAlgorithm.py" />
<Content Include="AltData\BenzingaNewsAlgorithm.py" />
<Content Include="AltData\QuiverWallStreetBetsDataAlgorithm.py" />
<Content Include="AltData\SECReport8KAlgorithm.py" />
<Content Include="AltData\SmartInsiderTransactionAlgorithm.py" />
<Content Include="AltData\USTreasuryYieldCurveRateAlgorithm.py" />
@@ -83,11 +86,13 @@
<None Include="BasicTemplateOptionsPriceModel.py" />
<Content Include="Benchmarks\SECReportBenchmarkAlgorithm.py" />
<Content Include="Benchmarks\SmartInsiderEventBenchmarkAlgorithm.py" />
<Content Include="CoarseFineOptionUniverseChainRegressionAlgorithm.py" />
<Content Include="CoarseTiingoNewsUniverseSelectionAlgorithm.py" />
<Content Include="ConsolidateRegressionAlgorithm.py" />
<Content Include="CustomConsolidatorRegressionAlgorithm.py" />
<Content Include="CustomDataAddDataOnSecuritiesChangedRegressionAlgorithm.py" />
<Content Include="CustomDataAddDataCoarseSelectionRegressionAlgorithm.py" />
<None Include="CustomBuyingPowerModelAlgorithm.py" />
<Content Include="DynamicSecurityDataAlgorithm.py" />
<Content Include="ConfidenceWeightedFrameworkAlgorithm.py" />
<Content Include="ExtendedMarketTradingRegressionAlgorithm.py" />

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

@@ -24,16 +24,14 @@ Before we enable python support, follow the [installation instructions](https://
3. Install [wrapt=1.11.2](https://pypi.org/project/wrapt/) module.
*Note:* If you encounter the "System.DllNotFoundException: python3.6m" runtime error when running Python algorithms on macOS:
1. Find `libpython3.6m.dylib` in your Python installation folder. If you installed Python with Anaconda, it may be find at
```
/Users/{your_user_name}/anaconda3/lib/libpython3.6m.dylib
```
2. Open `Lean/Launcher/bin/Debug/Python.Runtime.dll.config`, add the following text and save:
```
<configuration>
<dllmap dll="python3.6m" target="{the path in step 1 including libpython3.6m.dylib}" os="!windows"/>
</configuration>
```
1. Find `libpython3.6m.dylib` in your Python installation folder. If you installed Python with Anaconda, it may be found at
```
/Users/{your_user_name}/anaconda3/lib/libpython3.6m.dylib
```
2. Open `Lean/Launcher/bin/Debug/Python.Runtime.dll.config`, add the following text under `<configuration> ... </configuration>` and save:
```
<dllmap dll="python3.6m" target="{the path in step 1 including libpython3.6m.dylib}" os="osx"/>
```
Note: Specify the install of v3.6.8 _exactly_, i.e. if with conda `conda install python=3.6.8` as this is a known compatible version and other versions may have issues as of this writing.
#### [Linux](https://github.com/QuantConnect/Lean#linux-debian-ubuntu)
@@ -50,18 +48,32 @@ conda install -y pandas=0.25.3
conda install -y wrapt=1.11.2
```
*Note:* There is a [known issue](https://github.com/pythonnet/pythonnet/issues/609) with python 3.6.5 that prevents pythonnet installation, please upgrade python to version 3.6.8:
```
conda install -y python=3.6.8
```
*Note 1:* There is a [known issue](https://github.com/pythonnet/pythonnet/issues/609) with python 3.6.5 that prevents pythonnet installation, please upgrade python to version 3.6.8:
```
conda install -y python=3.6.8
```
*Note 2:* If you encounter the "System.DllNotFoundException: python3.6m" runtime error when running Python algorithms on Linux:
1. Find `libpython3.6m.so` in your Python installation folder. If you installed Python with Miniconda, it may be found at
```
/home/{your_user_name}/miniconda3/envs/{qc_environment}/lib/libpython3.6m.so
```
Note that you can create a new virtual environment with all required dependencies by executing:
```
conda create -n qc_environment python=3.6.8 cython=0.29.11 pandas=0.25.3 wrapt=1.11.2
```
2. Open `Lean/Launcher/bin/Debug/Python.Runtime.dll.config`, add the following text under `<configuration> ... </configuration>` and save:
```
<dllmap dll="python3.6m" target="{the path in step 1 including libpython3.6m.so}" os="linux"/>
```
### Run python algorithm
1. Update the [config](https://github.com/QuantConnect/Lean/blob/master/Launcher/config.json) to run the python algorithm:
```json
"algorithm-type-name": "BasicTemplateAlgorithm",
"algorithm-language": "Python",
"algorithm-location": "../../../Algorithm.Python/BasicTemplateAlgorithm.py",
```
```json
"algorithm-type-name": "BasicTemplateAlgorithm",
"algorithm-language": "Python",
"algorithm-location": "../../../Algorithm.Python/BasicTemplateAlgorithm.py",
```
2. Rebuild LEAN.
3. Run LEAN. You should see the same result of the C# algorithm you tested earlier.

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, SecurityType.Crypto, Market.Binance),
Time.UnixMillisecondTimeStampToDateTime(trade.Time),
trade.Price,
trade.Quantity);
break;
}
}
else if (objData["u"] != null)
{
var quote = objData.ToObject<BestBidAskQuote>();
EmitQuoteTick(
_symbolMapper.GetLeanSymbol(quote.Symbol, SecurityType.Crypto, Market.Binance),
quote.BestBidPrice,
quote.BestBidSize,
quote.BestAskPrice,
quote.BestAskSize);
}
}
catch (Exception exception)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1, $"Parsing wss message failed. Data: {e.Message} Exception: {exception}"));
throw;
}
}
private void EmitQuoteTick(Symbol symbol, decimal bidPrice, decimal bidSize, decimal askPrice, decimal askSize)
{
var tick = new Tick
{
AskPrice = askPrice,
BidPrice = bidPrice,
Time = DateTime.UtcNow,
Symbol = symbol,
TickType = TickType.Quote,
AskSize = askSize,
BidSize = bidSize
};
tick.SetValue();
lock (TickLocker)
{
_aggregator.Update(tick);
}
}
private void EmitTradeTick(Symbol symbol, DateTime time, decimal price, decimal quantity)
{
var tick = new Tick
{
Symbol = symbol,
Value = price,
Quantity = Math.Abs(quantity),
Time = time,
TickType = TickType.Trade
};
lock (TickLocker)
{
_aggregator.Update(tick);
}
}
private void OnFillOrder(Execution data)
{
try
{
var order = FindOrderByExternalId(data.OrderId);
if (order == null)
{
// not our order, nothing else to do here
return;
}
var fillPrice = data.LastExecutedPrice;
var fillQuantity = data.Direction == OrderDirection.Sell ? -data.LastExecutedQuantity : data.LastExecutedQuantity;
var updTime = Time.UnixMillisecondTimeStampToDateTime(data.TransactionTime);
var orderFee = new OrderFee(new CashAmount(data.Fee, data.FeeCurrency));
var status = ConvertOrderStatus(data.OrderStatus);
var orderEvent = new OrderEvent
(
order.Id, order.Symbol, updTime, status,
data.Direction, fillPrice, fillQuantity,
orderFee, $"Binance Order Event {data.Direction}"
);
if (status == OrderStatus.Filled)
{
Orders.Order outOrder;
CachedOrderIDs.TryRemove(order.Id, out outOrder);
}
OnOrderEvent(orderEvent);
}
catch (Exception e)
{
Log.Error(e);
throw;
}
}
private Orders.Order FindOrderByExternalId(string brokerId)
{
var order = CachedOrderIDs
.FirstOrDefault(o => o.Value.BrokerId.Contains(brokerId))
.Value;
if (order == null)
{
order = _algorithm.Transactions.GetOrderByBrokerageId(brokerId);
}
return order;
}
}
}

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 SymbolPropertiesDatabaseSymbolMapper _symbolMapper = new SymbolPropertiesDatabaseSymbolMapper(Market.Binance);
private readonly RateGate _webSocketRateLimiter = new RateGate(5, TimeSpan.FromSeconds(1));
private long _lastRequestId;
private readonly Timer _keepAliveTimer;
private readonly Timer _reconnectTimer;
private readonly BinanceRestApiClient _apiClient;
/// <summary>
/// Constructor for brokerage
/// </summary>
/// <param name="apiKey">api key</param>
/// <param name="apiSecret">api secret</param>
/// <param name="algorithm">the algorithm instance is required to retrieve account type</param>
/// <param name="aggregator">the aggregator for consolidating ticks</param>
public BinanceBrokerage(string apiKey, string apiSecret, IAlgorithm algorithm, IDataAggregator aggregator)
: base(WebSocketBaseUrl, new WebSocketClientWrapper(), null, apiKey, apiSecret, "Binance")
{
_algorithm = algorithm;
_aggregator = aggregator;
var subscriptionManager = new EventBasedDataQueueHandlerSubscriptionManager();
subscriptionManager.SubscribeImpl += (s, t) =>
{
Subscribe(s);
return true;
};
subscriptionManager.UnsubscribeImpl += (s, t) => Unsubscribe(s);
SubscriptionManager = subscriptionManager;
_apiClient = new BinanceRestApiClient(
_symbolMapper,
algorithm?.Portfolio,
apiKey,
apiSecret);
_apiClient.OrderSubmit += (s, e) => OnOrderSubmit(e);
_apiClient.OrderStatusChanged += (s, e) => OnOrderEvent(e);
_apiClient.Message += (s, e) => OnMessage(e);
// User data streams will close after 60 minutes. It's recommended to send a ping about every 30 minutes.
// Source: https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#pingkeep-alive-a-listenkey
_keepAliveTimer = new Timer
{
// 30 minutes
Interval = 30 * 60 * 1000
};
_keepAliveTimer.Elapsed += (s, e) => _apiClient.SessionKeepAlive();
WebSocket.Open += (s, e) => { _keepAliveTimer.Start(); };
WebSocket.Closed += (s, e) => { _keepAliveTimer.Stop(); };
// A single connection to stream.binance.com is only valid for 24 hours; expect to be disconnected at the 24 hour mark
// Source: https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#general-wss-information
_reconnectTimer = new Timer
{
// 23.5 hours
Interval = 23.5 * 60 * 60 * 1000
};
_reconnectTimer.Elapsed += (s, e) =>
{
Log.Trace("Daily websocket restart: disconnect");
Disconnect();
Log.Trace("Daily websocket restart: connect");
Connect();
};
}
#region IBrokerage
/// <summary>
/// Checks if the websocket connection is connected or in the process of connecting
/// </summary>
public override bool IsConnected => WebSocket.IsOpen;
/// <summary>
/// Creates wss connection
/// </summary>
public override void Connect()
{
if (IsConnected)
return;
_apiClient.CreateListenKey();
_reconnectTimer.Start();
WebSocket.Initialize($"{WebSocketBaseUrl}/{_apiClient.SessionId}");
base.Connect();
}
/// <summary>
/// Closes the websockets connection
/// </summary>
public override void Disconnect()
{
_reconnectTimer.Stop();
WebSocket?.Close();
_apiClient.StopSession();
}
/// <summary>
/// Gets all open positions
/// </summary>
/// <returns></returns>
public override List<Holding> GetAccountHoldings()
{
return _apiClient.GetAccountHoldings();
}
/// <summary>
/// Gets the total account cash balance for specified account type
/// </summary>
/// <returns></returns>
public override List<CashAmount> GetCashBalance()
{
var account = _apiClient.GetCashBalance();
var balances = account.Balances?.Where(balance => balance.Amount > 0).ToList();
if (balances == null || !balances.Any())
return new List<CashAmount>();
return balances
.Select(b => new CashAmount(b.Amount, b.Asset.LazyToUpper()))
.ToList();
}
/// <summary>
/// Gets all orders not yet closed
/// </summary>
/// <returns></returns>
public override List<Order> GetOpenOrders()
{
var orders = _apiClient.GetOpenOrders();
List<Order> list = new List<Order>();
foreach (var item in orders)
{
Order order;
switch (item.Type.LazyToUpper())
{
case "MARKET":
order = new MarketOrder { Price = item.Price };
break;
case "LIMIT":
case "LIMIT_MAKER":
order = new LimitOrder { LimitPrice = item.Price };
break;
case "STOP_LOSS":
case "TAKE_PROFIT":
order = new StopMarketOrder { StopPrice = item.StopPrice, Price = item.Price };
break;
case "STOP_LOSS_LIMIT":
case "TAKE_PROFIT_LIMIT":
order = new StopLimitOrder { StopPrice = item.StopPrice, LimitPrice = item.Price };
break;
default:
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1,
"BinanceBrokerage.GetOpenOrders: Unsupported order type returned from brokerage: " + item.Type));
continue;
}
order.Quantity = item.Quantity;
order.BrokerId = new List<string> { item.Id };
order.Symbol = _symbolMapper.GetLeanSymbol(item.Symbol, SecurityType.Crypto, Market.Binance);
order.Time = Time.UnixMillisecondTimeStampToDateTime(item.Time);
order.Status = ConvertOrderStatus(item.Status);
order.Price = item.Price;
if (order.Status.IsOpen())
{
var cached = CachedOrderIDs.Where(c => c.Value.BrokerId.Contains(order.BrokerId.First())).ToList();
if (cached.Any())
{
CachedOrderIDs[cached.First().Key] = order;
}
}
list.Add(order);
}
return list;
}
/// <summary>
/// Places a new order and assigns a new broker ID to the order
/// </summary>
/// <param name="order">The order to be placed</param>
/// <returns>True if the request for a new order has been placed, false otherwise</returns>
public override bool PlaceOrder(Order order)
{
var submitted = false;
WithLockedStream(() =>
{
submitted = _apiClient.PlaceOrder(order);
});
return submitted;
}
/// <summary>
/// Updates the order with the same id
/// </summary>
/// <param name="order">The new order information</param>
/// <returns>True if the request was made for the order to be updated, false otherwise</returns>
public override bool UpdateOrder(Order order)
{
throw new NotSupportedException("BinanceBrokerage.UpdateOrder: Order update not supported. Please cancel and re-create.");
}
/// <summary>
/// Cancels the order with the specified ID
/// </summary>
/// <param name="order">The order to cancel</param>
/// <returns>True if the request was submitted for cancellation, false otherwise</returns>
public override bool CancelOrder(Order order)
{
var submitted = false;
WithLockedStream(() =>
{
submitted = _apiClient.CancelOrder(order);
});
return submitted;
}
/// <summary>
/// Gets the history for the requested security
/// </summary>
/// <param name="request">The historical data request</param>
/// <returns>An enumerable of bars covering the span specified in the request</returns>
public override IEnumerable<BaseData> GetHistory(Data.HistoryRequest request)
{
if (request.Resolution == Resolution.Tick || request.Resolution == Resolution.Second)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidResolution",
$"{request.Resolution} resolution is not supported, no history returned"));
yield break;
}
if (request.TickType != TickType.Trade)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidTickType",
$"{request.TickType} tick type not supported, no history returned"));
yield break;
}
var period = request.Resolution.ToTimeSpan();
foreach (var kline in _apiClient.GetHistory(request))
{
yield return new TradeBar()
{
Time = Time.UnixMillisecondTimeStampToDateTime(kline.OpenTime),
Symbol = request.Symbol,
Low = kline.Low,
High = kline.High,
Open = kline.Open,
Close = kline.Close,
Volume = kline.Volume,
Value = kline.Close,
DataType = MarketDataType.TradeBar,
Period = period,
EndTime = Time.UnixMillisecondTimeStampToDateTime(kline.OpenTime + (long)period.TotalMilliseconds)
};
}
}
/// <summary>
/// Wss message handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public override void OnMessage(object sender, WebSocketMessage e)
{
try
{
if (_streamLocked)
{
_messageBuffer.Enqueue(e);
return;
}
}
catch (Exception err)
{
Log.Error(err);
}
OnMessageImpl(e);
}
#endregion
#region IDataQueueHandler
/// <summary>
/// Sets the job we're subscribing for
/// </summary>
/// <param name="job">Job we're subscribing for</param>
public void SetJob(LiveNodePacket job)
{
}
/// <summary>
/// Subscribe to the specified configuration
/// </summary>
/// <param name="dataConfig">defines the parameters to subscribe to a data feed</param>
/// <param name="newDataAvailableHandler">handler to be fired on new data available</param>
/// <returns>The new enumerator for this subscription request</returns>
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
{
if (!CanSubscribe(dataConfig.Symbol))
{
return Enumerable.Empty<BaseData>().GetEnumerator();
}
var enumerator = _aggregator.Add(dataConfig, newDataAvailableHandler);
SubscriptionManager.Subscribe(dataConfig);
return enumerator;
}
/// <summary>
/// Removes the specified configuration
/// </summary>
/// <param name="dataConfig">Subscription config to be removed</param>
public void Unsubscribe(SubscriptionDataConfig dataConfig)
{
SubscriptionManager.Unsubscribe(dataConfig);
_aggregator.Remove(dataConfig);
}
/// <summary>
/// Checks if this brokerage supports the specified symbol
/// </summary>
/// <param name="symbol">The symbol</param>
/// <returns>returns true if brokerage supports the specified symbol; otherwise false</returns>
private static bool CanSubscribe(Symbol symbol)
{
return !symbol.Value.Contains("UNIVERSE") &&
symbol.SecurityType == SecurityType.Crypto;
}
#endregion
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public override void Dispose()
{
_keepAliveTimer.DisposeSafely();
_reconnectTimer.DisposeSafely();
_apiClient.DisposeSafely();
_webSocketRateLimiter.DisposeSafely();
}
/// <summary>
/// Subscribes to the requested symbols (using an individual streaming channel)
/// </summary>
/// <param name="symbols">The list of symbols to subscribe</param>
public override void Subscribe(IEnumerable<Symbol> symbols)
{
foreach (var symbol in symbols)
{
Send(WebSocket,
new
{
method = "SUBSCRIBE",
@params = new[]
{
$"{symbol.Value.ToLowerInvariant()}@trade",
$"{symbol.Value.ToLowerInvariant()}@bookTicker"
},
id = GetNextRequestId()
}
);
}
}
/// <summary>
/// Ends current subscriptions
/// </summary>
private bool Unsubscribe(IEnumerable<Symbol> symbols)
{
if (WebSocket.IsOpen)
{
foreach (var symbol in symbols)
{
Send(WebSocket,
new
{
method = "UNSUBSCRIBE",
@params = new[]
{
$"{symbol.Value.ToLowerInvariant()}@trade",
$"{symbol.Value.ToLowerInvariant()}@bookTicker"
},
id = GetNextRequestId()
}
);
}
}
return true;
}
private void Send(IWebSocket webSocket, object obj)
{
var json = JsonConvert.SerializeObject(obj);
if (!_webSocketRateLimiter.WaitToProceed(TimeSpan.Zero))
{
_webSocketRateLimiter.WaitToProceed();
}
Log.Trace("Send: " + json);
webSocket.Send(json);
}
private long GetNextRequestId()
{
return Interlocked.Increment(ref _lastRequestId);
}
/// <summary>
/// Event invocator for the OrderFilled event
/// </summary>
/// <param name="e">The OrderEvent</param>
private void OnOrderSubmit(BinanceOrderSubmitEventArgs e)
{
var brokerId = e.BrokerId;
var order = e.Order;
if (CachedOrderIDs.ContainsKey(order.Id))
{
CachedOrderIDs[order.Id].BrokerId.Clear();
CachedOrderIDs[order.Id].BrokerId.Add(brokerId);
}
else
{
order.BrokerId.Add(brokerId);
CachedOrderIDs.TryAdd(order.Id, order);
}
}
}
}

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 SymbolPropertiesDatabaseSymbolMapper _symbolMapper;
private readonly ISecurityProvider _securityProvider;
private readonly IRestClient _restClient;
private readonly RateGate _restRateLimiter = new RateGate(10, TimeSpan.FromSeconds(1));
private readonly object _listenKeyLocker = new object();
/// <summary>
/// Event that fires each time an order is filled
/// </summary>
public event EventHandler<BinanceOrderSubmitEventArgs> OrderSubmit;
/// <summary>
/// Event that fires each time an order is filled
/// </summary>
public event EventHandler<OrderEvent> OrderStatusChanged;
/// <summary>
/// Event that fires when an error is encountered in the brokerage
/// </summary>
public event EventHandler<BrokerageMessageEvent> Message;
/// <summary>
/// Key Header
/// </summary>
public readonly string KeyHeader = "X-MBX-APIKEY";
/// <summary>
/// The api secret
/// </summary>
protected string ApiSecret;
/// <summary>
/// The api key
/// </summary>
protected string ApiKey;
/// <summary>
/// Represents UserData Session listen key
/// </summary>
public string SessionId { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="BinanceRestApiClient"/> class.
/// </summary>
/// <param name="symbolMapper">The symbol mapper.</param>
/// <param name="securityProvider">The holdings provider.</param>
/// <param name="apiKey">The Binance API key</param>
/// <param name="apiSecret">The The Binance API secret</param>
public BinanceRestApiClient(SymbolPropertiesDatabaseSymbolMapper symbolMapper, ISecurityProvider securityProvider, string apiKey, string apiSecret)
{
_symbolMapper = symbolMapper;
_securityProvider = securityProvider;
_restClient = new RestClient(RestApiUrl);
ApiKey = apiKey;
ApiSecret = apiSecret;
}
/// <summary>
/// Gets all open positions
/// </summary>
/// <returns></returns>
public List<Holding> GetAccountHoldings()
{
return new List<Holding>();
}
/// <summary>
/// Gets the total account cash balance for specified account type
/// </summary>
/// <returns></returns>
public Messages.AccountInformation GetCashBalance()
{
var queryString = $"timestamp={GetNonce()}";
var endpoint = $"/api/v3/account?{queryString}&signature={AuthenticationToken(queryString)}";
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetCashBalance: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
return JsonConvert.DeserializeObject<Messages.AccountInformation>(response.Content);
}
/// <summary>
/// Gets all orders not yet closed
/// </summary>
/// <returns></returns>
public IEnumerable<Messages.OpenOrder> GetOpenOrders()
{
var queryString = $"timestamp={GetNonce()}";
var endpoint = $"/api/v3/openOrders?{queryString}&signature={AuthenticationToken(queryString)}";
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetCashBalance: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
return JsonConvert.DeserializeObject<Messages.OpenOrder[]>(response.Content);
}
/// <summary>
/// Places a new order and assigns a new broker ID to the order
/// </summary>
/// <param name="order">The order to be placed</param>
/// <returns>True if the request for a new order has been placed, false otherwise</returns>
public bool PlaceOrder(Order order)
{
// supported time in force values {GTC, IOC, FOK}
// use GTC as LEAN doesn't support others yet
IDictionary<string, object> body = new Dictionary<string, object>()
{
{ "symbol", _symbolMapper.GetBrokerageSymbol(order.Symbol) },
{ "quantity", Math.Abs(order.Quantity).ToString(CultureInfo.InvariantCulture) },
{ "side", ConvertOrderDirection(order.Direction) }
};
switch (order.Type)
{
case OrderType.Limit:
body["type"] = (order.Properties as BinanceOrderProperties)?.PostOnly == true
? "LIMIT_MAKER"
: "LIMIT";
body["price"] = ((LimitOrder) order).LimitPrice.ToString(CultureInfo.InvariantCulture);
// timeInForce is not required for LIMIT_MAKER
if (Equals(body["type"], "LIMIT"))
body["timeInForce"] = "GTC";
break;
case OrderType.Market:
body["type"] = "MARKET";
break;
case OrderType.StopLimit:
var ticker = GetTickerPrice(order);
var stopPrice = ((StopLimitOrder) order).StopPrice;
if (order.Direction == OrderDirection.Sell)
{
body["type"] = stopPrice <= ticker ? "STOP_LOSS_LIMIT" : "TAKE_PROFIT_LIMIT";
}
else
{
body["type"] = stopPrice <= ticker ? "TAKE_PROFIT_LIMIT" : "STOP_LOSS_LIMIT";
}
body["timeInForce"] = "GTC";
body["stopPrice"] = stopPrice.ToString(CultureInfo.InvariantCulture);
body["price"] = ((StopLimitOrder) order).LimitPrice.ToString(CultureInfo.InvariantCulture);
break;
default:
throw new NotSupportedException($"BinanceBrokerage.ConvertOrderType: Unsupported order type: {order.Type}");
}
const string endpoint = "/api/v3/order";
body["timestamp"] = GetNonce();
body["signature"] = AuthenticationToken(body.ToQueryString());
var request = new RestRequest(endpoint, Method.POST);
request.AddHeader(KeyHeader, ApiKey);
request.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes(body.ToQueryString()),
ParameterType.RequestBody
);
var response = ExecuteRestRequest(request);
if (response.StatusCode == HttpStatusCode.OK)
{
var raw = JsonConvert.DeserializeObject<Messages.NewOrder>(response.Content);
if (string.IsNullOrEmpty(raw?.Id))
{
var errorMessage = $"Error parsing response from place order: {response.Content}";
OnOrderEvent(new OrderEvent(
order,
DateTime.UtcNow,
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Invalid, Message = errorMessage });
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, (int)response.StatusCode, errorMessage));
return true;
}
OnOrderSubmit(raw, order);
return true;
}
var message = $"Order failed, Order Id: {order.Id} timestamp: {order.Time} quantity: {order.Quantity} content: {response.Content}";
OnOrderEvent(new OrderEvent(
order,
DateTime.UtcNow,
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Invalid });
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, message));
return true;
}
/// <summary>
/// Cancels the order with the specified ID
/// </summary>
/// <param name="order">The order to cancel</param>
/// <returns>True if the request was submitted for cancellation, false otherwise</returns>
public bool CancelOrder(Order order)
{
var success = new List<bool>();
IDictionary<string, object> body = new Dictionary<string, object>()
{
{ "symbol", _symbolMapper.GetBrokerageSymbol(order.Symbol) }
};
foreach (var id in order.BrokerId)
{
if (body.ContainsKey("signature"))
{
body.Remove("signature");
}
body["orderId"] = id;
body["timestamp"] = GetNonce();
body["signature"] = AuthenticationToken(body.ToQueryString());
var request = new RestRequest("/api/v3/order", Method.DELETE);
request.AddHeader(KeyHeader, ApiKey);
request.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes(body.ToQueryString()),
ParameterType.RequestBody
);
var response = ExecuteRestRequest(request);
success.Add(response.StatusCode == HttpStatusCode.OK);
}
var canceled = false;
if (success.All(a => a))
{
OnOrderEvent(new OrderEvent(order,
DateTime.UtcNow,
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Canceled });
canceled = true;
}
return canceled;
}
/// <summary>
/// Gets the history for the requested security
/// </summary>
/// <param name="request">The historical data request</param>
/// <returns>An enumerable of bars covering the span specified in the request</returns>
public IEnumerable<Messages.Kline> GetHistory(Data.HistoryRequest request)
{
var resolution = ConvertResolution(request.Resolution);
var resolutionInMs = (long)request.Resolution.ToTimeSpan().TotalMilliseconds;
var symbol = _symbolMapper.GetBrokerageSymbol(request.Symbol);
var startMs = (long)Time.DateTimeToUnixTimeStamp(request.StartTimeUtc) * 1000;
var endMs = (long)Time.DateTimeToUnixTimeStamp(request.EndTimeUtc) * 1000;
var endpoint = $"/api/v3/klines?symbol={symbol}&interval={resolution}&limit=1000";
while (endMs - startMs >= resolutionInMs)
{
var timeframe = $"&startTime={startMs}&endTime={endMs}";
var restRequest = new RestRequest(endpoint + timeframe, Method.GET);
var response = ExecuteRestRequest(restRequest);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetHistory: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
var klines = JsonConvert.DeserializeObject<object[][]>(response.Content)
.Select(entries => new Messages.Kline(entries))
.ToList();
startMs = klines.Last().OpenTime + resolutionInMs;
foreach (var kline in klines)
{
yield return kline;
}
}
}
/// <summary>
/// Check User Data stream listen key is alive
/// </summary>
/// <returns></returns>
public bool SessionKeepAlive()
{
if (string.IsNullOrEmpty(SessionId))
{
throw new Exception("BinanceBrokerage:UserStream. listenKey wasn't allocated or has been refused.");
}
var ping = new RestRequest(UserDataStreamEndpoint, Method.PUT);
ping.AddHeader(KeyHeader, ApiKey);
ping.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes($"listenKey={SessionId}"),
ParameterType.RequestBody
);
var pong = ExecuteRestRequest(ping);
return pong.StatusCode == HttpStatusCode.OK;
}
/// <summary>
/// Stops the session
/// </summary>
public void StopSession()
{
if (string.IsNullOrEmpty(SessionId))
{
throw new Exception("BinanceBrokerage:UserStream. listenKey wasn't allocated or has been refused.");
}
var request = new RestRequest(UserDataStreamEndpoint, Method.DELETE);
request.AddHeader(KeyHeader, ApiKey);
request.AddParameter(
"application/x-www-form-urlencoded",
Encoding.UTF8.GetBytes($"listenKey={SessionId}"),
ParameterType.RequestBody
);
ExecuteRestRequest(request);
}
/// <summary>
/// Provides the current tickers price
/// </summary>
/// <returns></returns>
public Messages.PriceTicker[] GetTickers()
{
const string endpoint = "/api/v3/ticker/price";
var req = new RestRequest(endpoint, Method.GET);
var response = ExecuteRestRequest(req);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.GetTick: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
return JsonConvert.DeserializeObject<Messages.PriceTicker[]>(response.Content);
}
/// <summary>
/// Start user data stream
/// </summary>
public void CreateListenKey()
{
var request = new RestRequest(UserDataStreamEndpoint, Method.POST);
request.AddHeader(KeyHeader, ApiKey);
var response = ExecuteRestRequest(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"BinanceBrokerage.StartSession: request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
var content = JObject.Parse(response.Content);
lock (_listenKeyLocker)
{
SessionId = content.Value<string>("listenKey");
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_restRateLimiter.DisposeSafely();
}
/// <summary>
/// If an IP address exceeds a certain number of requests per minute
/// HTTP 429 return code is used when breaking a request rate limit.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private IRestResponse ExecuteRestRequest(IRestRequest request)
{
const int maxAttempts = 10;
var attempts = 0;
IRestResponse response;
do
{
if (!_restRateLimiter.WaitToProceed(TimeSpan.Zero))
{
Log.Trace("Brokerage.OnMessage(): " + new BrokerageMessageEvent(BrokerageMessageType.Warning, "RateLimit",
"The API request has been rate limited. To avoid this message, please reduce the frequency of API calls."));
_restRateLimiter.WaitToProceed();
}
response = _restClient.Execute(request);
// 429 status code: Too Many Requests
} while (++attempts < maxAttempts && (int)response.StatusCode == 429);
return response;
}
private decimal GetTickerPrice(Order order)
{
var security = _securityProvider.GetSecurity(order.Symbol);
var tickerPrice = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice;
if (tickerPrice == 0)
{
var brokerageSymbol = _symbolMapper.GetBrokerageSymbol(order.Symbol);
var tickers = GetTickers();
var ticker = tickers.FirstOrDefault(t => t.Symbol == brokerageSymbol);
if (ticker == null)
{
throw new KeyNotFoundException($"BinanceBrokerage: Unable to resolve currency conversion pair: {order.Symbol}");
}
tickerPrice = ticker.Price;
}
return tickerPrice;
}
/// <summary>
/// Timestamp in milliseconds
/// </summary>
/// <returns></returns>
private long GetNonce()
{
return (long)(Time.TimeStamp() * 1000);
}
/// <summary>
/// Creates a signature for signed endpoints
/// </summary>
/// <param name="payload">the body of the request</param>
/// <returns>a token representing the request params</returns>
private string AuthenticationToken(string payload)
{
using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(ApiSecret)))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)).ToHexString();
}
}
private static string ConvertOrderDirection(OrderDirection orderDirection)
{
if (orderDirection == OrderDirection.Buy || orderDirection == OrderDirection.Sell)
{
return orderDirection.ToString().LazyToUpper();
}
throw new NotSupportedException($"BinanceBrokerage.ConvertOrderDirection: Unsupported order direction: {orderDirection}");
}
private readonly Dictionary<Resolution, string> _knownResolutions = new Dictionary<Resolution, string>()
{
{ Resolution.Minute, "1m" },
{ Resolution.Hour, "1h" },
{ Resolution.Daily, "1d" }
};
private string ConvertResolution(Resolution resolution)
{
if (_knownResolutions.ContainsKey(resolution))
{
return _knownResolutions[resolution];
}
else
{
throw new ArgumentException($"BinanceBrokerage.ConvertResolution: Unsupported resolution type: {resolution}");
}
}
/// <summary>
/// Event invocator for the OrderFilled event
/// </summary>
/// <param name="newOrder">The brokerage order submit result</param>
/// <param name="order">The lean order</param>
private void OnOrderSubmit(Messages.NewOrder newOrder, Order order)
{
try
{
OrderSubmit?.Invoke(
this,
new BinanceOrderSubmitEventArgs(newOrder.Id, order));
// Generate submitted event
OnOrderEvent(new OrderEvent(
order,
Time.UnixMillisecondTimeStampToDateTime(newOrder.TransactionTime),
OrderFee.Zero,
"Binance Order Event")
{ Status = OrderStatus.Submitted }
);
Log.Trace($"Order submitted successfully - OrderId: {order.Id}");
}
catch (Exception err)
{
Log.Error(err);
}
}
/// <summary>
/// Event invocator for the OrderFilled event
/// </summary>
/// <param name="e">The OrderEvent</param>
private void OnOrderEvent(OrderEvent e)
{
try
{
Log.Debug("Brokerage.OnOrderEvent(): " + e);
OrderStatusChanged?.Invoke(this, e);
}
catch (Exception err)
{
Log.Error(err);
}
}
/// <summary>
/// Event invocator for the Message event
/// </summary>
/// <param name="e">The error</param>
protected virtual void OnMessage(BrokerageMessageEvent e)
{
try
{
if (e.Type == BrokerageMessageType.Error)
{
Log.Error("Brokerage.OnMessage(): " + e);
}
else
{
Log.Trace("Brokerage.OnMessage(): " + e);
}
Message?.Invoke(this, e);
}
catch (Exception err)
{
Log.Error(err);
}
}
}
}

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

@@ -379,7 +379,7 @@ namespace QuantConnect.Brokerages.Bitfinex
}
}
var symbol = _symbolMapper.GetLeanSymbol(update.Symbol);
var symbol = _symbolMapper.GetLeanSymbol(update.Symbol, SecurityType.Crypto, Market.Bitfinex);
var fillPrice = update.ExecPrice;
var fillQuantity = update.ExecAmount;
var direction = fillQuantity < 0 ? OrderDirection.Sell : OrderDirection.Buy;
@@ -399,6 +399,15 @@ namespace QuantConnect.Brokerages.Bitfinex
: OrderStatus.PartiallyFilled;
}
if (_algorithm.BrokerageModel.AccountType == AccountType.Cash &&
order.Direction == OrderDirection.Buy)
{
// fees are debited in the base currency, so we have to subtract them from the filled quantity
fillQuantity -= orderFee.Value.Amount;
orderFee = new ModifiedFillQuantityOrderFee(orderFee.Value);
}
var orderEvent = new OrderEvent
(
order.Id, symbol, updTime, status,

View File

@@ -221,7 +221,7 @@ namespace QuantConnect.Brokerages.Bitfinex
{
var holding = new Holding
{
Symbol = _symbolMapper.GetLeanSymbol(position.Symbol),
Symbol = _symbolMapper.GetLeanSymbol(position.Symbol, SecurityType.Crypto, Market.Bitfinex),
AveragePrice = position.BasePrice,
Quantity = position.Amount,
UnrealizedPnL = position.ProfitLoss,

View File

@@ -37,7 +37,7 @@ namespace QuantConnect.Brokerages.Bitfinex
/// </summary>
public partial class BitfinexBrokerage : BaseWebsocketsBrokerage, IDataQueueHandler
{
private readonly BitfinexSymbolMapper _symbolMapper = new BitfinexSymbolMapper();
private readonly SymbolPropertiesDatabaseSymbolMapper _symbolMapper = new SymbolPropertiesDatabaseSymbolMapper(Market.Bitfinex);
#region IBrokerage
/// <summary>
@@ -201,7 +201,7 @@ namespace QuantConnect.Brokerages.Bitfinex
order.Quantity = item.Amount;
order.BrokerId = new List<string> { item.Id.ToStringInvariant() };
order.Symbol = _symbolMapper.GetLeanSymbol(item.Symbol);
order.Symbol = _symbolMapper.GetLeanSymbol(item.Symbol, SecurityType.Crypto, Market.Bitfinex);
order.Time = Time.UnixMillisecondTimeStampToDateTime(item.MtsCreate);
order.Status = ConvertOrderStatus(item);
order.Price = item.Price;
@@ -444,8 +444,7 @@ namespace QuantConnect.Brokerages.Bitfinex
{
var symbol = dataConfig.Symbol;
if (symbol.Value.Contains("UNIVERSE") ||
!_symbolMapper.IsKnownLeanSymbol(symbol) ||
symbol.SecurityType != _symbolMapper.GetLeanSecurityType(symbol.Value))
!_symbolMapper.IsKnownLeanSymbol(symbol))
{
return Enumerable.Empty<BaseData>().GetEnumerator();
}

View File

@@ -48,13 +48,13 @@ namespace QuantConnect.Brokerages.Bitfinex
private volatile int _subscribeErrorCode;
private readonly object _locker = new object();
private readonly BitfinexBrokerage _brokerage;
private readonly BitfinexSymbolMapper _symbolMapper;
private readonly ISymbolMapper _symbolMapper;
private readonly RateGate _connectionRateLimiter = new RateGate(5, TimeSpan.FromMinutes(1));
private readonly ConcurrentDictionary<Symbol, List<BitfinexWebSocketWrapper>> _subscriptionsBySymbol = new ConcurrentDictionary<Symbol, List<BitfinexWebSocketWrapper>>();
private readonly ConcurrentDictionary<BitfinexWebSocketWrapper, List<Channel>> _channelsByWebSocket = new ConcurrentDictionary<BitfinexWebSocketWrapper, List<Channel>>();
private readonly ConcurrentDictionary<int, Channel> _channels = new ConcurrentDictionary<int, Channel>();
private readonly ConcurrentDictionary<BitfinexWebSocketWrapper, BitfinexWebSocketChannels> _channelsByWebSocket = new ConcurrentDictionary<BitfinexWebSocketWrapper, BitfinexWebSocketChannels>();
private readonly ConcurrentDictionary<Symbol, DefaultOrderBook> _orderBooks = new ConcurrentDictionary<Symbol, DefaultOrderBook>();
private readonly IReadOnlyDictionary<TickType, string> _tickType2ChannelName = new Dictionary<TickType, string>() {
private readonly IReadOnlyDictionary<TickType, string> _tickType2ChannelName = new Dictionary<TickType, string>
{
{ TickType.Trade, "trades"},
{ TickType.Quote, "book"}
};
@@ -64,7 +64,7 @@ namespace QuantConnect.Brokerages.Bitfinex
/// <summary>
/// Initializes a new instance of the <see cref="BitfinexSubscriptionManager"/> class.
/// </summary>
public BitfinexSubscriptionManager(BitfinexBrokerage brokerage, string wssUrl, BitfinexSymbolMapper symbolMapper)
public BitfinexSubscriptionManager(BitfinexBrokerage brokerage, string wssUrl, ISymbolMapper symbolMapper)
{
_brokerage = brokerage;
_wssUrl = wssUrl;
@@ -101,7 +101,7 @@ namespace QuantConnect.Brokerages.Bitfinex
return v;
});
Log.Trace($"BitfinexBrokerage.Subscribe(): Sent subscribe for {symbol.Value}.");
Log.Trace($"BitfinexBrokerage.Subscribe(): Sent subscribe for {symbol.Value}/{tickType}.");
if (_onSubscribeEvent.WaitOne(TimeSpan.FromSeconds(10)) && _subscribeErrorCode == 0)
{
@@ -109,7 +109,7 @@ namespace QuantConnect.Brokerages.Bitfinex
}
else
{
Log.Trace($"BitfinexBrokerage.Subscribe(): Could not subscribe to {symbol.Value}.");
Log.Trace($"BitfinexBrokerage.Subscribe(): Could not subscribe to {symbol.Value}/{tickType}.");
states.Add(false);
}
}
@@ -130,24 +130,24 @@ namespace QuantConnect.Brokerages.Bitfinex
/// <param name="tickType">Type of tick data</param>
protected override bool Unsubscribe(IEnumerable<Symbol> symbols, TickType tickType)
{
string channelName = ChannelNameFromTickType(tickType);
var channelName = ChannelNameFromTickType(tickType);
var states = new List<bool>(symbols.Count());
foreach (var symbol in symbols)
{
List<BitfinexWebSocketWrapper> subscriptions;
if (_subscriptionsBySymbol.TryGetValue(symbol, out subscriptions))
{
for (int i = subscriptions.Count - 1; i >= 0; i--)
for (var i = subscriptions.Count - 1; i >= 0; i--)
{
var webSocket = subscriptions[i];
_onUnsubscribeEvent.Reset();
try
{
Channel channel = new Channel(channelName, symbol);
List<Channel> channels;
var channel = new Channel(channelName, symbol);
BitfinexWebSocketChannels channels;
if (_channelsByWebSocket.TryGetValue(webSocket, out channels) && channels.Contains(channel))
{
UnsubscribeChannel(webSocket, channel);
UnsubscribeChannel(webSocket, channels, channel);
if (_onUnsubscribeEvent.WaitOne(TimeSpan.FromSeconds(30)))
{
@@ -199,9 +199,10 @@ namespace QuantConnect.Brokerages.Bitfinex
return webSocket;
}
private void UnsubscribeChannel(IWebSocket webSocket, Channel channel)
private void UnsubscribeChannel(IWebSocket webSocket, BitfinexWebSocketChannels channels, Channel channel)
{
int channelId = _channels.First(c => c.Value.Equals(channel)).Key;
var channelId = channels.GetChannelId(channel);
webSocket.Send(JsonConvert.SerializeObject(new
{
@event = "unsubscribe",
@@ -211,19 +212,12 @@ namespace QuantConnect.Brokerages.Bitfinex
private BitfinexWebSocketWrapper GetFreeWebSocket(Channel channel)
{
int count;
lock (_locker)
{
foreach (var kvp in _channelsByWebSocket)
{
if (kvp.Value.Count < MaximumSubscriptionsPerSocket)
{
kvp.Value.Add(channel);
count = _channelsByWebSocket.Sum(x => x.Value.Count);
Log.Trace($"BitfinexSubscriptionManager.GetFreeWebSocket(): Channel added: Total channels:{count}");
return kvp.Key;
}
}
@@ -242,10 +236,7 @@ namespace QuantConnect.Brokerages.Bitfinex
lock (_locker)
{
_channelsByWebSocket.TryAdd(webSocket, new List<Channel> { channel });
count = _channelsByWebSocket.Sum(x => x.Value.Count);
Log.Trace($"BitfinexSubscriptionManager.GetFreeWebSocket(): Channel added: Total channels:{count}");
_channelsByWebSocket.TryAdd(webSocket, new BitfinexWebSocketChannels());
}
webSocket.Initialize(_wssUrl);
@@ -332,7 +323,7 @@ namespace QuantConnect.Brokerages.Bitfinex
Log.Trace($"BitfinexSubscriptionManager.OnReconnectRequested(): Reconnected: IsOpen:{webSocket.IsOpen} [Id: {connectionHandler.ConnectionId}]");
List<Channel> channels;
BitfinexWebSocketChannels channels;
lock (_locker)
{
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
@@ -343,7 +334,7 @@ namespace QuantConnect.Brokerages.Bitfinex
Log.Trace($"BitfinexSubscriptionManager.OnReconnectRequested(): Resubscribing channels. [Id: {connectionHandler.ConnectionId}]");
foreach (var channel in channels)
foreach (var channel in channels.Values)
{
webSocket.Send(JsonConvert.SerializeObject(new
{
@@ -380,7 +371,7 @@ namespace QuantConnect.Brokerages.Bitfinex
// trade execution
case "te":
OnUpdate(channel, token[2].ToObject<string[]>());
OnUpdate(webSocket, channel, token[2].ToObject<string[]>());
break;
// ignored -- trades already handled in "te" message
@@ -400,6 +391,7 @@ namespace QuantConnect.Brokerages.Bitfinex
if (token[1][0].Type == JTokenType.Array)
{
OnSnapshot(
webSocket,
channel,
token[1].ToObject<string[][]>()
);
@@ -408,6 +400,7 @@ namespace QuantConnect.Brokerages.Bitfinex
{
// pass channel id as separate arg
OnUpdate(
webSocket,
channel,
token[1].ToObject<string[]>()
);
@@ -416,7 +409,7 @@ namespace QuantConnect.Brokerages.Bitfinex
}
else if (token is JObject)
{
var raw = token.ToObject<Messages.BaseMessage>();
var raw = token.ToObject<BaseMessage>();
switch (raw.Event.ToLowerInvariant())
{
case "subscribed":
@@ -457,15 +450,25 @@ namespace QuantConnect.Brokerages.Bitfinex
}
}
private void OnSubscribe(BitfinexWebSocketWrapper webSocket, Messages.ChannelSubscription data)
private void OnSubscribe(BitfinexWebSocketWrapper webSocket, ChannelSubscription data)
{
try
{
lock (_locker)
{
var channel = new Channel(data.Channel, _symbolMapper.GetLeanSymbol(data.Symbol));
var channel = new Channel(data.Channel, _symbolMapper.GetLeanSymbol(data.Symbol, SecurityType.Crypto, Market.Bitfinex));
BitfinexWebSocketChannels channels;
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
{
_onSubscribeEvent.Set();
return;
}
channels.TryAdd(data.ChannelId, channel);
Log.Trace($"BitfinexSubscriptionManager.OnSubscribe(): Channel subscribed: Id:{data.ChannelId} {channel.Symbol}/{channel.Name}");
_channels.AddOrUpdate(data.ChannelId, channel);
_onSubscribeEvent.Set();
webSocket.ConnectionHandler.EnableMonitoring(true);
@@ -478,23 +481,27 @@ namespace QuantConnect.Brokerages.Bitfinex
}
}
private void OnUnsubscribe(BitfinexWebSocketWrapper webSocket, Messages.ChannelUnsubscribing data)
private void OnUnsubscribe(BitfinexWebSocketWrapper webSocket, ChannelUnsubscribing data)
{
try
{
lock (_locker)
{
BitfinexWebSocketChannels channels;
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
{
return;
}
Channel channel;
if (!_channels.TryRemove(data.ChannelId, out channel)) return;
if (!channels.TryRemove(data.ChannelId, out channel))
{
return;
}
_onUnsubscribeEvent.Set();
List<Channel> channels;
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels)) return;
channels.Remove(channel);
if (channels.Count(c => c.Symbol.Equals(channel.Symbol)) == 0)
if (channels.Values.Count(c => c.Symbol.Equals(channel.Symbol)) == 0)
{
List<BitfinexWebSocketWrapper> subscriptions;
if (_subscriptionsBySymbol.TryGetValue(channel.Symbol, out subscriptions))
@@ -508,7 +515,10 @@ namespace QuantConnect.Brokerages.Bitfinex
}
}
if (channels.Count != 0) return;
if (channels.Count != 0)
{
return;
}
_channelsByWebSocket.TryRemove(webSocket, out channels);
}
@@ -523,7 +533,7 @@ namespace QuantConnect.Brokerages.Bitfinex
}
}
private void OnSnapshot(int channelId, string[][] entries)
private void OnSnapshot(BitfinexWebSocketWrapper webSocket, int channelId, string[][] entries)
{
try
{
@@ -531,7 +541,13 @@ namespace QuantConnect.Brokerages.Bitfinex
lock (_locker)
{
if (!_channels.TryGetValue(channelId, out channel))
BitfinexWebSocketChannels channels;
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
{
return;
}
if (!channels.TryGetValue(channelId, out channel))
{
_brokerage.OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, $"Message received from unknown channel Id {channelId}"));
return;
@@ -592,7 +608,7 @@ namespace QuantConnect.Brokerages.Bitfinex
}
}
private void OnUpdate(int channelId, string[] entries)
private void OnUpdate(BitfinexWebSocketWrapper webSocket, int channelId, string[] entries)
{
try
{
@@ -600,7 +616,13 @@ namespace QuantConnect.Brokerages.Bitfinex
lock (_locker)
{
if (!_channels.TryGetValue(channelId, out channel))
BitfinexWebSocketChannels channels;
if (!_channelsByWebSocket.TryGetValue(webSocket, out channels))
{
return;
}
if (!channels.TryGetValue(channelId, out channel))
{
_brokerage.OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, $"Message received from unknown channel Id {channelId}"));
return;

View File

@@ -1,176 +0,0 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Securities;
namespace QuantConnect.Brokerages.Bitfinex
{
/// <summary>
/// Provides the mapping between Lean symbols and Bitfinex symbols.
/// </summary>
public class BitfinexSymbolMapper : ISymbolMapper
{
/// <summary>
/// The list of known Bitfinex symbols.
/// https://api.bitfinex.com/v1/symbols
/// </summary>
public static readonly HashSet<string> KnownTickers =
new HashSet<string>(SymbolPropertiesDatabase
.FromDataFolder()
.GetSymbolPropertiesList(Market.Bitfinex, SecurityType.Crypto)
.Select(x => x.Key.Symbol));
/// <summary>
/// Converts a Lean symbol instance to an Bitfinex symbol
/// </summary>
/// <param name="symbol">A Lean symbol instance</param>
/// <returns>The Bitfinex symbol</returns>
public string GetBrokerageSymbol(Symbol symbol)
{
if (symbol == null || string.IsNullOrWhiteSpace(symbol.Value))
throw new ArgumentException("Invalid symbol: " + (symbol == null ? "null" : symbol.ToString()));
if (symbol.ID.SecurityType != SecurityType.Crypto)
throw new ArgumentException("Invalid security type: " + symbol.ID.SecurityType);
var brokerageSymbol = ConvertLeanSymbolToBitfinexSymbol(symbol.Value);
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException("Unknown symbol: " + symbol.Value);
return brokerageSymbol;
}
/// <summary>
/// Converts an Bitfinex symbol to a Lean symbol instance
/// </summary>
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
/// <param name="securityType">The security type</param>
/// <param name="market">The market</param>
/// <param name="expirationDate">Expiration date of the security(if applicable)</param>
/// <param name="strike">The strike of the security (if applicable)</param>
/// <param name="optionRight">The option right of the security (if applicable)</param>
/// <returns>A new Lean Symbol instance</returns>
public Symbol GetLeanSymbol(string brokerageSymbol, SecurityType securityType, string market, DateTime expirationDate = default(DateTime), decimal strike = 0, OptionRight optionRight = 0)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
throw new ArgumentException($"Invalid Bitfinex symbol: {brokerageSymbol}");
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException($"Unknown Bitfinex symbol: {brokerageSymbol}");
if (securityType != SecurityType.Crypto)
throw new ArgumentException($"Invalid security type: {securityType}");
if (market != Market.Bitfinex)
throw new ArgumentException($"Invalid market: {market}");
return Symbol.Create(ConvertBitfinexSymbolToLeanSymbol(brokerageSymbol), GetBrokerageSecurityType(brokerageSymbol), Market.Bitfinex);
}
/// <summary>
/// Converts an Bitfinex symbol to a Lean symbol instance
/// </summary>
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
/// <returns>A new Lean Symbol instance</returns>
public Symbol GetLeanSymbol(string brokerageSymbol)
{
var securityType = GetBrokerageSecurityType(brokerageSymbol);
return GetLeanSymbol(brokerageSymbol, securityType, Market.Bitfinex);
}
/// <summary>
/// Returns the security type for an Bitfinex symbol
/// </summary>
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
/// <returns>The security type</returns>
public SecurityType GetBrokerageSecurityType(string brokerageSymbol)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
throw new ArgumentException($"Invalid Bitfinex symbol: {brokerageSymbol}");
if (!IsKnownBrokerageSymbol(brokerageSymbol))
throw new ArgumentException($"Unknown Bitfinex symbol: {brokerageSymbol}");
return SecurityType.Crypto;
}
/// <summary>
/// Returns the security type for a Lean symbol
/// </summary>
/// <param name="leanSymbol">The Lean symbol</param>
/// <returns>The security type</returns>
public SecurityType GetLeanSecurityType(string leanSymbol)
{
return GetBrokerageSecurityType(ConvertLeanSymbolToBitfinexSymbol(leanSymbol));
}
/// <summary>
/// Checks if the symbol is supported by Bitfinex
/// </summary>
/// <param name="brokerageSymbol">The Bitfinex symbol</param>
/// <returns>True if Bitfinex supports the symbol</returns>
public bool IsKnownBrokerageSymbol(string brokerageSymbol)
{
if (string.IsNullOrWhiteSpace(brokerageSymbol))
return false;
// Strip leading 't' char
return KnownTickers.Contains(brokerageSymbol.Substring(1));
}
/// <summary>
/// Checks if the symbol is supported by Bitfinex
/// </summary>
/// <param name="symbol">The Lean symbol</param>
/// <returns>True if Bitfinex supports the symbol</returns>
public bool IsKnownLeanSymbol(Symbol symbol)
{
if (string.IsNullOrWhiteSpace(symbol?.Value) || symbol.Value.Length <= 3)
return false;
var bitfinexSymbol = ConvertLeanSymbolToBitfinexSymbol(symbol.Value);
return IsKnownBrokerageSymbol(bitfinexSymbol) && GetBrokerageSecurityType(bitfinexSymbol) == symbol.ID.SecurityType;
}
/// <summary>
/// Converts an Bitfinex symbol to a Lean symbol string
/// </summary>
private static string ConvertBitfinexSymbolToLeanSymbol(string bitfinexSymbol)
{
if (string.IsNullOrWhiteSpace(bitfinexSymbol) || !bitfinexSymbol.StartsWith("t"))
throw new ArgumentException($"Invalid Bitfinex symbol: {bitfinexSymbol}");
// Strip leading 't' char
return bitfinexSymbol.Substring(1).ToUpperInvariant();
}
/// <summary>
/// Converts a Lean symbol string to an Bitfinex symbol
/// </summary>
private static string ConvertLeanSymbolToBitfinexSymbol(string leanSymbol)
{
if (string.IsNullOrWhiteSpace(leanSymbol))
throw new ArgumentException($"Invalid Lean symbol: {leanSymbol}");
// Prepend 't' for Trading pairs
return "t" + leanSymbol.ToUpperInvariant();
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Concurrent;
using System.Linq;
using QuantConnect.Data;
namespace QuantConnect.Brokerages.Bitfinex
{
/// <summary>
/// Contains the channel mappings for a WebSocket connection
/// </summary>
public class BitfinexWebSocketChannels : ConcurrentDictionary<int, Channel>
{
/// <summary>
/// Determines whether the dictionary contains a specific channel.
/// </summary>
/// <param name="channel">The channel</param>
/// <returns>true if the channel was found</returns>
public bool Contains(Channel channel)
{
return Values.Contains(channel);
}
/// <summary>
/// Returns the channel id for the given channel.
/// </summary>
/// <param name="channel">The channel</param>
/// <returns>The channel id</returns>
public int GetChannelId(Channel channel)
{
return this.First(c => c.Value.Equals(channel)).Key;
}
}
}

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
@@ -109,7 +107,8 @@ namespace QuantConnect.Brokerages.Fxcm
AutoResetEvent autoResetEvent;
lock (_locker)
{
_currentRequest = _gateway.requestOpenOrders(_accountId);
_currentRequest = _gateway.requestOpenOrders(null);
autoResetEvent = new AutoResetEvent(false);
_mapRequestsToAutoResetEvents[_currentRequest] = autoResetEvent;
}
@@ -246,7 +245,7 @@ namespace QuantConnect.Brokerages.Fxcm
}
// get account base currency
_fxcmAccountCurrency = message.getParameter("BASE_CRNCY").getValue();
AccountBaseCurrency = message.getParameter("BASE_CRNCY").getValue();
_mapRequestsToAutoResetEvents[_currentRequest].Set();
_mapRequestsToAutoResetEvents.Remove(_currentRequest);

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

@@ -26,7 +26,6 @@ using System.Net;
using System.Threading.Tasks;
using System.Threading;
using RestSharp;
using System.Text.RegularExpressions;
using QuantConnect.Configuration;
using QuantConnect.Logging;
using QuantConnect.Orders.Fees;
@@ -45,13 +44,14 @@ namespace QuantConnect.Brokerages.GDAX
/// </summary>
public ConcurrentDictionary<long, GDAXFill> FillSplit { get; set; }
private readonly string _passPhrase;
private const string SymbolMatching = "ETH|LTC|BTC|BCH|XRP|EOS|XLM|ETC|ZRX";
private readonly IAlgorithm _algorithm;
private readonly CancellationTokenSource _canceller = new CancellationTokenSource();
private readonly ConcurrentDictionary<Symbol, DefaultOrderBook> _orderBooks = new ConcurrentDictionary<Symbol, DefaultOrderBook>();
private readonly bool _isDataQueueHandler;
protected readonly IDataAggregator _aggregator;
private readonly SymbolPropertiesDatabaseSymbolMapper _symbolMapper = new SymbolPropertiesDatabaseSymbolMapper(Market.GDAX);
// GDAX has different rate limits for public and private endpoints
// https://docs.gdax.com/#rate-limits
internal enum GdaxEndpointType { Public, Private }
@@ -165,7 +165,7 @@ namespace QuantConnect.Brokerages.GDAX
{
var message = JsonConvert.DeserializeObject<Messages.Snapshot>(data);
var symbol = ConvertProductId(message.ProductId);
var symbol = _symbolMapper.GetLeanSymbol(message.ProductId, SecurityType.Crypto, Market.GDAX);
DefaultOrderBook orderBook;
if (!_orderBooks.TryGetValue(symbol, out orderBook))
@@ -220,7 +220,7 @@ namespace QuantConnect.Brokerages.GDAX
{
var message = JsonConvert.DeserializeObject<Messages.L2Update>(data);
var symbol = ConvertProductId(message.ProductId);
var symbol = _symbolMapper.GetLeanSymbol(message.ProductId, SecurityType.Crypto, Market.GDAX);
var orderBook = _orderBooks[symbol];
@@ -275,7 +275,7 @@ namespace QuantConnect.Brokerages.GDAX
private void EmitFillOrderEvent(Messages.Fill fill, Order order)
{
var symbol = ConvertProductId(fill.ProductId);
var symbol = _symbolMapper.GetLeanSymbol(fill.ProductId, SecurityType.Crypto, Market.GDAX);
if (!FillSplit.ContainsKey(order.Id))
{
@@ -327,7 +327,9 @@ namespace QuantConnect.Brokerages.GDAX
/// <returns></returns>
public Tick GetTick(Symbol symbol)
{
var req = new RestRequest($"/products/{ConvertSymbol(symbol)}/ticker", Method.GET);
var brokerageSymbol = _symbolMapper.GetBrokerageSymbol(symbol);
var req = new RestRequest($"/products/{brokerageSymbol}/ticker", Method.GET);
var response = ExecuteRestRequest(req, GdaxEndpointType.Public);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
@@ -366,7 +368,7 @@ namespace QuantConnect.Brokerages.GDAX
/// </summary>
private void EmitTradeTick(Messages.Matched message)
{
var symbol = ConvertProductId(message.ProductId);
var symbol = _symbolMapper.GetLeanSymbol(message.ProductId, SecurityType.Crypto, Market.GDAX);
_aggregator.Update(new Tick
{
@@ -387,20 +389,25 @@ namespace QuantConnect.Brokerages.GDAX
var pendingSymbols = new List<Symbol>();
foreach (var item in fullList)
{
if (!IsSubscribeAvailable(item))
if (_symbolMapper.IsKnownLeanSymbol(item))
{
pendingSymbols.Add(item);
}
else if (item.SecurityType == SecurityType.Crypto)
{
Log.Error($"Unknown GDAX symbol: {item.Value}");
}
else
{
//todo: refactor this outside brokerage
//alternative service: http://openexchangerates.org/latest.json
PollTick(item);
}
else
{
pendingSymbols.Add(item);
}
}
var products = pendingSymbols
.Select(s => s.Value.Substring(0, 3) + "-" + s.Value.Substring(3)).ToArray();
.Select(s => _symbolMapper.GetBrokerageSymbol(s))
.ToArray();
var payload = new
{
@@ -484,11 +491,6 @@ namespace QuantConnect.Brokerages.GDAX
}
}
private bool IsSubscribeAvailable(Symbol symbol)
{
return Regex.IsMatch(symbol.Value, SymbolMatching);
}
/// <summary>
/// Ends current subscriptions
/// </summary>
@@ -497,7 +499,7 @@ namespace QuantConnect.Brokerages.GDAX
if (WebSocket.IsOpen)
{
var products = symbols
.Select(s => s.Value.Substring(0, 3) + "-" + s.Value.Substring(3))
.Select(s => _symbolMapper.GetBrokerageSymbol(s))
.ToArray();
var payload = new
@@ -539,7 +541,12 @@ namespace QuantConnect.Brokerages.GDAX
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"GDAXBrokerage.FillMonitorAction(): request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
OnMessage(new BrokerageMessageEvent(
BrokerageMessageType.Warning,
-1,
$"GDAXBrokerage.FillMonitorAction(): request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}"));
continue;
}
var fills = JsonConvert.DeserializeObject<List<Messages.Fill>>(response.Content);

View File

@@ -129,26 +129,6 @@ namespace QuantConnect.Brokerages.GDAX
throw new NotSupportedException($"GDAXBrokerage.ConvertOrderType: Unsupported order type:{orderType.ToStringInvariant()}");
}
/// <summary>
/// Converts a product id to a symbol
/// </summary>
/// <param name="productId">gdax format product id</param>
/// <returns>Symbol</returns>
public static Symbol ConvertProductId(string productId)
{
return Symbol.Create(productId.Replace("-", ""), SecurityType.Crypto, Market.GDAX);
}
/// <summary>
/// Converts a symbol to a product id
/// </summary>
/// <param name="symbol">Th symbol</param>
/// <returns>gdax product id</returns>
protected static string ConvertSymbol(Symbol symbol)
{
return $"{symbol.Value.Substring(0, 3).ToUpperInvariant()}-{symbol.Value.Substring(3, 3).ToUpperInvariant()}";
}
private static Orders.OrderStatus ConvertOrderStatus(Messages.Order order)
{
if (order.FilledSize != 0 && order.FilledSize != order.Size)

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
@@ -64,7 +72,7 @@ namespace QuantConnect.Brokerages.GDAX
(order as StopMarketOrder)?.StopPrice ?? 0;
}
payload.product_id = ConvertSymbol(order.Symbol);
payload.product_id = _symbolMapper.GetBrokerageSymbol(order.Symbol);
if (_algorithm.BrokerageModel.AccountType == AccountType.Margin)
{
@@ -186,6 +194,16 @@ namespace QuantConnect.Brokerages.GDAX
return success.All(a => a);
}
/// <summary>
/// Connects the client to the broker's remote servers
/// </summary>
public override void Connect()
{
base.Connect();
AccountBaseCurrency = GetAccountBaseCurrency();
}
/// <summary>
/// Closes the websockets connection
/// </summary>
@@ -244,7 +262,7 @@ namespace QuantConnect.Brokerages.GDAX
order.Quantity = item.Side == "sell" ? -item.Size : item.Size;
order.BrokerId = new List<string> { item.Id };
order.Symbol = ConvertProductId(item.ProductId);
order.Symbol = _symbolMapper.GetLeanSymbol(item.ProductId, SecurityType.Crypto, Market.GDAX);
order.Time = DateTime.UtcNow;
order.Status = ConvertOrderStatus(item);
order.Price = item.Price;
@@ -321,6 +339,13 @@ namespace QuantConnect.Brokerages.GDAX
yield break;
}
if (!_symbolMapper.IsKnownLeanSymbol(request.Symbol))
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidSymbol",
$"Unknown symbol: {request.Symbol.Value}, no history returned"));
yield break;
}
if (request.EndTimeUtc < request.StartTimeUtc)
{
OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, "InvalidDateRange",
@@ -349,7 +374,7 @@ namespace QuantConnect.Brokerages.GDAX
/// <param name="request">The history request instance</param>
private IEnumerable<TradeBar> GetHistoryFromCandles(HistoryRequest request)
{
var productId = ConvertSymbol(request.Symbol);
var productId = _symbolMapper.GetBrokerageSymbol(request.Symbol);
var granularity = Convert.ToInt32(request.Resolution.ToTimeSpan().TotalSeconds);
var startTime = request.StartTimeUtc;
@@ -433,6 +458,31 @@ namespace QuantConnect.Brokerages.GDAX
#endregion
/// <summary>
/// Gets the account base currency
/// </summary>
private string GetAccountBaseCurrency()
{
var req = new RestRequest("/accounts", Method.GET);
GetAuthenticationToken(req);
var response = ExecuteRestRequest(req, GdaxEndpointType.Private);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"GDAXBrokerage.GetAccountBaseCurrency(): request failed: [{(int)response.StatusCode}] {response.StatusDescription}, Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
}
foreach (var item in JsonConvert.DeserializeObject<Messages.Account[]>(response.Content))
{
if (FiatCurrencies.Contains(item.Currency))
{
return item.Currency;
}
}
return Currencies.USD;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>

View File

@@ -0,0 +1,62 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace QuantConnect.Brokerages.InteractiveBrokers.Client
{
/// <summary>
/// Event arguments class for the <see cref="InteractiveBrokersClient.AccountSummary"/> event
/// </summary>
public class AccountSummaryEventArgs : EventArgs
{
/// <summary>
/// The request's identifier.
/// </summary>
public int RequestId { get; }
/// <summary>
/// The account id.
/// </summary>
public string Account { get; }
/// <summary>
/// The account's attribute being received.
/// </summary>
public string Tag { get; }
/// <summary>
/// The account's attribute's value.
/// </summary>
public string Value { get; }
/// <summary>
/// The currency on which the value is expressed.
/// </summary>
public string Currency { get; }
/// <summary>
/// Initializes a new instance of the <see cref="AccountSummaryEventArgs"/> class
/// </summary>
public AccountSummaryEventArgs(int reqId, string account, string tag, string value, string currency)
{
RequestId = reqId;
Account = account;
Tag = tag;
Value = value;
Currency = currency;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using IBApi;
namespace QuantConnect.Brokerages.InteractiveBrokers.Client
{
/// <summary>
/// Event arguments class for the <see cref="InteractiveBrokersClient.FamilyCodes"/> event
/// </summary>
public class FamilyCodesEventArgs : EventArgs
{
/// <summary>
/// A comma-separated string with the managed account ids.
/// </summary>
public FamilyCode[] FamilyCodes { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FamilyCodesEventArgs"/> class
/// </summary>
public FamilyCodesEventArgs(FamilyCode[] familyCodes)
{
FamilyCodes = familyCodes;
}
}
}

View File

@@ -55,6 +55,11 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
/// </summary>
public event EventHandler ConnectionClosed;
/// <summary>
/// AccountSummary event handler
/// </summary>
public event EventHandler<AccountSummaryEventArgs> AccountSummary;
/// <summary>
/// AccountSummaryEnd event handler
/// </summary>
@@ -145,6 +150,16 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
/// </summary>
public event EventHandler ConnectAck;
/// <summary>
/// ManagedAccounts event handler
/// </summary>
public event EventHandler<ManagedAccountsEventArgs> ManagedAccounts;
/// <summary>
/// FamilyCodes event handler
/// </summary>
public event EventHandler<FamilyCodesEventArgs> FamilyCodes;
#endregion
/// <summary>
@@ -258,6 +273,19 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
OnConnectionClosed();
}
/// <summary>
/// Receives the account information.
/// </summary>
/// <param name="reqId">The request's identifier.</param>
/// <param name="account">The account id</param>
/// <param name="tag">The account's attribute being received.</param>
/// <param name="value">The account's attribute's value.</param>
/// <param name="currency">The currency on which the value is expressed.</param>
public override void accountSummary(int reqId, string account, string tag, string value, string currency)
{
OnAccountSummary(new AccountSummaryEventArgs(reqId, account, tag, value, currency));
}
/// <summary>
/// This is called once all account information for a given reqAccountSummary() request are received.
/// </summary>
@@ -456,6 +484,24 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
OnConnectAck();
}
/// <summary>
/// Receives a comma-separated string with the managed account ids. Occurs automatically on initial API client connection.
/// </summary>
/// <param name="accountList">A comma-separated string with the managed account ids.</param>
public override void managedAccounts(string accountList)
{
OnManagedAccounts(new ManagedAccountsEventArgs(accountList));
}
/// <summary>
/// Returns array of family codes
/// </summary>
/// <param name="familyCodes">An array of family codes.</param>
public override void familyCodes(FamilyCode[] familyCodes)
{
OnFamilyCodes(new FamilyCodesEventArgs(familyCodes));
}
#endregion
#region Event Invocators
@@ -508,6 +554,14 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
ConnectionClosed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// AccountSummary event invocator
/// </summary>
protected virtual void OnAccountSummary(AccountSummaryEventArgs e)
{
AccountSummary?.Invoke(this, e);
}
/// <summary>
/// AccountSummaryEnd event invocator
/// </summary>
@@ -652,6 +706,22 @@ namespace QuantConnect.Brokerages.InteractiveBrokers.Client
ConnectAck?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// ManagedAccounts event invocator
/// </summary>
protected virtual void OnManagedAccounts(ManagedAccountsEventArgs e)
{
ManagedAccounts?.Invoke(this, e);
}
/// <summary>
/// FamilyCodes event invocator
/// </summary>
protected virtual void OnFamilyCodes(FamilyCodesEventArgs e)
{
FamilyCodes?.Invoke(this, e);
}
#endregion
}
}

View File

@@ -0,0 +1,38 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace QuantConnect.Brokerages.InteractiveBrokers.Client
{
/// <summary>
/// Event arguments class for the <see cref="InteractiveBrokersClient.ManagedAccounts"/> event
/// </summary>
public class ManagedAccountsEventArgs : EventArgs
{
/// <summary>
/// A comma-separated string with the managed account ids.
/// </summary>
public string AccountList { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ManagedAccountsEventArgs"/> class
/// </summary>
public ManagedAccountsEventArgs(string accountList)
{
AccountList = accountList;
}
}
}

View File

@@ -14,5 +14,6 @@
"MXP": "6M",
"RUR": "6R",
"ZAR": "6Z",
"BRR": "BTC"
"BRR": "BTC",
"DA": "DC"
}

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
{
@@ -55,14 +54,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
// Existing orders created in TWS can *only* be cancelled/modified when connected with ClientId = 0
private const int ClientId = 0;
// next valid order id for this client
// next valid order id (or request id, or ticker id) for this client
private int _nextValidId;
private readonly object _nextValidIdLocker = new object();
// next valid request id for queries
private int _nextRequestId;
private int _nextTickerId;
private readonly int _port;
private readonly string _account;
private readonly string _host;
@@ -131,6 +126,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
private readonly bool _enableDelayedStreamingData = Config.GetBool("ib-enable-delayed-streaming-data");
private volatile bool _isDisposeCalled;
/// <summary>
/// Returns true if we're currently connected to the broker
/// </summary>
@@ -272,6 +269,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
_client.OpenOrder += HandleOpenOrder;
_client.OpenOrderEnd += HandleOpenOrderEnd;
_client.UpdateAccountValue += HandleUpdateAccountValue;
_client.AccountSummary += HandleAccountSummary;
_client.ManagedAccounts += HandleManagedAccounts;
_client.FamilyCodes += HandleFamilyCodes;
_client.ExecutionDetails += HandleExecutionDetails;
_client.CommissionReport += HandleCommissionReport;
_client.Error += HandleError;
@@ -398,7 +398,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
{
var orderId = Parse.Int(id);
_requestInformation[orderId] = "CancelOrder: " + order;
_requestInformation[orderId] = $"[Id={orderId}] CancelOrder: " + order;
CheckRateLimiting();
@@ -605,9 +605,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var manualResetEvent = new ManualResetEvent(false);
var requestId = GetNextRequestId();
var requestId = GetNextId();
_requestInformation[requestId] = "GetExecutions: " + symbol;
_requestInformation[requestId] = $"[Id={requestId}] GetExecutions: " + symbol;
// define our event handlers
EventHandler<IB.RequestEndEventArgs> clientOnExecutionDataEnd = (sender, args) =>
@@ -727,6 +727,11 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
if (!_client.Connected) throw new Exception("InteractiveBrokersBrokerage.Connect(): Connection returned but was not in connected state.");
// request account information for logging purposes
_client.ClientSocket.reqAccountSummary(GetNextId(), "All", "AccountType");
_client.ClientSocket.reqManagedAccts();
_client.ClientSocket.reqFamilyCodes();
if (IsFinancialAdvisor)
{
if (!DownloadFinancialAdvisorAccount(_account))
@@ -905,8 +910,15 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
/// </summary>
public override void Dispose()
{
if (_isDisposeCalled)
{
return;
}
Log.Trace("InteractiveBrokersBrokerage.Dispose(): Disposing of IB resources.");
_isDisposeCalled = true;
if (_client != null)
{
Disconnect();
@@ -941,7 +953,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
if (needsNewId)
{
// the order ids are generated for us by the SecurityTransactionManaer
var id = GetNextBrokerageOrderId();
var id = GetNextId();
order.BrokerId.Add(id.ToStringInvariant());
ibOrderId = id;
}
@@ -955,7 +967,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
throw new ArgumentException("Expected order with populated BrokerId for updating orders.");
}
_requestInformation[ibOrderId] = $"IBPlaceOrder: {order.Symbol.Value} ({contract})";
_requestInformation[ibOrderId] = $"[Id={ibOrderId}] IBPlaceOrder: {order.Symbol.Value} ({GetContractDescription(contract)} )";
CheckRateLimiting();
@@ -972,7 +984,12 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
private static string GetUniqueKey(Contract contract)
{
return $"{contract} {contract.LastTradeDateOrContractMonth.ToStringInvariant()} {contract.Strike.ToStringInvariant()} {contract.Right}";
return $"{contract.ToString().ToUpperInvariant()} {contract.LastTradeDateOrContractMonth.ToStringInvariant()} {contract.Strike.ToStringInvariant()} {contract.Right}";
}
private static string GetContractDescription(Contract contract)
{
return $"{contract} {contract.PrimaryExch ?? string.Empty} {contract.LastTradeDateOrContractMonth.ToStringInvariant()} {contract.Strike.ToStringInvariant()} {contract.Right}";
}
private string GetPrimaryExchange(Contract contract, Symbol symbol)
@@ -1033,10 +1050,13 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
{
const int timeout = 60; // sec
ContractDetails details = null;
var requestId = GetNextRequestId();
var requestId = GetNextId();
_requestInformation[requestId] = $"GetContractDetails: {symbol.Value} ({contract})";
var contractDetailsList = new List<ContractDetails>();
Log.Trace($"InteractiveBrokersBrokerage.GetContractDetails(): {symbol.Value} ({contract})");
_requestInformation[requestId] = $"[Id={requestId}] GetContractDetails: {symbol.Value} ({contract})";
var manualResetEvent = new ManualResetEvent(false);
@@ -1044,12 +1064,26 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
EventHandler<IB.ContractDetailsEventArgs> clientOnContractDetails = (sender, args) =>
{
// ignore other requests
if (args.RequestId != requestId) return;
details = args.ContractDetails;
var uniqueKey = GetUniqueKey(contract);
if (args.RequestId != requestId)
{
return;
}
var details = args.ContractDetails;
contractDetailsList.Add(details);
var uniqueKey = GetUniqueKey(details.Contract);
_contractDetails.TryAdd(uniqueKey, details);
manualResetEvent.Set();
Log.Trace("InteractiveBrokersBrokerage.GetContractDetails(): clientOnContractDetails event: " + uniqueKey);
Log.Trace($"InteractiveBrokersBrokerage.GetContractDetails(): clientOnContractDetails event: {uniqueKey}");
};
EventHandler<IB.RequestEndEventArgs> clientOnContractDetailsEnd = (sender, args) =>
{
if (args.RequestId == requestId)
{
manualResetEvent.Set();
}
};
EventHandler<IB.ErrorEventArgs> clientOnError = (sender, args) =>
@@ -1061,6 +1095,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
};
_client.ContractDetails += clientOnContractDetails;
_client.ContractDetailsEnd += clientOnContractDetailsEnd;
_client.Error += clientOnError;
CheckRateLimiting();
@@ -1075,32 +1110,21 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
// be sure to remove our event handlers
_client.Error -= clientOnError;
_client.ContractDetailsEnd -= clientOnContractDetailsEnd;
_client.ContractDetails -= clientOnContractDetails;
return details;
}
Log.Trace($"InteractiveBrokersBrokerage.GetContractDetails(): contracts found: {contractDetailsList.Count}");
private string GetFuturesContractExchange(Contract contract, string ticker)
{
// searching for available contracts on different exchanges
var contractDetails = FindContracts(contract, ticker);
var exchanges = _futuresExchanges.Values.Reverse().ToArray();
// sorting list of available contracts by exchange priority, taking the top 1
return contractDetails
.Select(details => details.Contract.Exchange)
.OrderByDescending(e => Array.IndexOf(exchanges, e))
.FirstOrDefault();
return contractDetailsList.FirstOrDefault();
}
public IEnumerable<ContractDetails> FindContracts(Contract contract, string ticker)
{
const int timeout = 60; // sec
var requestId = GetNextRequestId();
var requestId = GetNextId();
_requestInformation[requestId] = $"FindContracts: {ticker} ({contract})";
_requestInformation[requestId] = $"[Id={requestId}] FindContracts: {ticker} ({GetContractDescription(contract)})";
var manualResetEvent = new ManualResetEvent(false);
var contractDetails = new List<ContractDetails>();
@@ -1317,6 +1341,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
/// </summary>
private void HandleUpdateAccountValue(object sender, IB.UpdateAccountValueEventArgs e)
{
//Log.Trace($"HandleUpdateAccountValue(): Key:{e.Key} Value:{e.Value} Currency:{e.Currency} AccountName:{e.AccountName}");
try
{
_accountData.AccountProperties[e.Currency + ":" + e.Key] = e.Value;
@@ -1329,6 +1355,12 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
OnAccountChanged(new AccountEvent(e.Currency, cashBalance));
}
// IB does not explicitly return the account base currency, but we can find out using exchange rates returned
if (e.Key == AccountValueKeys.ExchangeRate && e.Currency != "BASE" && e.Value.ToDecimal() == 1)
{
AccountBaseCurrency = e.Currency;
}
}
catch (Exception err)
{
@@ -1837,6 +1869,14 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
_futuresExchanges[symbol.ID.Market] :
symbol.ID.Market;
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
symbol.ID.Market,
symbol.ID.Symbol,
SecurityType.Future,
Currencies.USD);
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
contract.IncludeExpired = includeExpired;
}
@@ -2091,7 +2131,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
default:
throw new NotSupportedException(
$"An existing position or open order for an unsupported security type was found: {contract}. " +
$"An existing position or open order for an unsupported security type was found: {GetContractDescription(contract)}. " +
"Please manually close the position or cancel the order before restarting the algorithm.");
}
}
@@ -2217,10 +2257,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
}
/// <summary>
/// Handles the threading issues of creating an IB order ID
/// Handles the threading issues of creating an IB OrderId/RequestId/TickerId
/// </summary>
/// <returns>The new IB ID</returns>
private int GetNextBrokerageOrderId()
/// <returns>The new IB OrderId/RequestId/TickerId</returns>
private int GetNextId()
{
lock (_nextValidIdLocker)
{
@@ -2229,16 +2269,6 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
}
}
private int GetNextRequestId()
{
return Interlocked.Increment(ref _nextRequestId);
}
private int GetNextTickerId()
{
return Interlocked.Increment(ref _nextTickerId);
}
private void HandleBrokerTime(object sender, IB.CurrentTimeUtcEventArgs e)
{
// keep track of clock drift
@@ -2306,10 +2336,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
return false;
}
var id = GetNextTickerId();
var id = GetNextId();
var contract = CreateContract(subscribeSymbol, false);
_requestInformation[id] = $"Subscribe: {symbol.Value} ({contract})";
_requestInformation[id] = $"[Id={id}] Subscribe: {symbol.Value} ({GetContractDescription(contract)})";
CheckRateLimiting();
@@ -2329,7 +2359,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
_subscribedSymbols[symbol] = id;
_subscribedTickers[id] = new SubscriptionEntry { Symbol = subscribeSymbol };
Log.Trace($"InteractiveBrokersBrokerage.Subscribe(): Subscribe Processed: {symbol.Value} ({contract}) # {id}");
Log.Trace($"InteractiveBrokersBrokerage.Subscribe(): Subscribe Processed: {symbol.Value} ({GetContractDescription(contract)}) # {id}");
return true;
}
}
@@ -2723,6 +2753,18 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
}
else if (securityType == SecurityType.Future)
{
string market;
if (_symbolPropertiesDatabase.TryGetMarket(lookupName, securityType, out market))
{
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
market,
lookupName,
securityType,
Currencies.USD);
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
}
// processing request
var results = FindContracts(contract, contract.Symbol);
@@ -2817,7 +2859,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var startTime = request.Resolution == Resolution.Daily ? request.StartTimeUtc.Date : request.StartTimeUtc;
var endTime = request.Resolution == Resolution.Daily ? request.EndTimeUtc.Date : request.EndTimeUtc;
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Submitting request: {request.Symbol.Value} ({contract}): {request.Resolution}/{request.TickType} {startTime} UTC -> {endTime} UTC");
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Submitting request: {request.Symbol.Value} ({GetContractDescription(contract)}): {request.Resolution}/{request.TickType} {startTime} UTC -> {endTime} UTC");
DateTimeZone exchangeTimeZone;
if (!_symbolExchangeTimeZones.TryGetValue(request.Symbol, out exchangeTimeZone))
@@ -2862,7 +2904,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
yield return bar;
}
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Download completed: {request.Symbol.Value} ({contract})");
Log.Trace($"InteractiveBrokersBrokerage::GetHistory(): Download completed: {request.Symbol.Value} ({GetContractDescription(contract)})");
}
private IEnumerable<TradeBar> GetHistory(
@@ -2888,9 +2930,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
{
var pacing = false;
var historyPiece = new List<TradeBar>();
var historicalTicker = GetNextTickerId();
var historicalTicker = GetNextId();
_requestInformation[historicalTicker] = $"GetHistory: {request.Symbol.Value} ({contract})";
_requestInformation[historicalTicker] = $"[Id={historicalTicker}] GetHistory: {request.Symbol.Value} ({GetContractDescription(contract)})";
EventHandler<IB.HistoricalDataEventArgs> clientOnHistoricalData = (sender, args) =>
{
@@ -3023,7 +3065,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var result = _ibAutomater.GetLastStartResult();
CheckIbAutomaterError(result, false);
if (!result.HasError)
if (!result.HasError && !_isDisposeCalled)
{
// IBGateway was closed by the v978+ automatic logoff or it was closed manually (less likely)
Log.Trace("InteractiveBrokersBrokerage.OnIbAutomaterExited(): IBGateway close detected, restarting IBAutomater and reconnecting...");
@@ -3047,6 +3089,24 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
}
}
private void HandleAccountSummary(object sender, IB.AccountSummaryEventArgs e)
{
Log.Trace($"InteractiveBrokersBrokerage.HandleAccountSummary(): Request id: {e.RequestId}, Account: {e.Account}, Tag: {e.Tag}, Value: {e.Value}, Currency: {e.Currency}");
}
private void HandleFamilyCodes(object sender, IB.FamilyCodesEventArgs e)
{
foreach (var familyCode in e.FamilyCodes)
{
Log.Trace($"InteractiveBrokersBrokerage.HandleFamilyCodes(): Account id: {familyCode.AccountID}, Family code: {familyCode.FamilyCodeStr}");
}
}
private void HandleManagedAccounts(object sender, IB.ManagedAccountsEventArgs e)
{
Log.Trace($"InteractiveBrokersBrokerage.HandleManagedAccounts(): Account list: {e.AccountList}");
}
private readonly ConcurrentDictionary<Symbol, int> _subscribedSymbols = new ConcurrentDictionary<Symbol, int>();
private readonly ConcurrentDictionary<int, SubscriptionEntry> _subscribedTickers = new ConcurrentDictionary<int, SubscriptionEntry>();
private readonly Dictionary<Symbol, Symbol> _underlyings = new Dictionary<Symbol, Symbol>();
@@ -3062,8 +3122,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
private static class AccountValueKeys
{
public const string CashBalance = "CashBalance";
// public const string AccruedCash = "AccruedCash";
// public const string NetLiquidationByCurrency = "NetLiquidationByCurrency";
public const string ExchangeRate = "ExchangeRate";
}
// these are fatal errors from IB
@@ -3084,5 +3143,4 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
105, 106, 107, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 129, 131, 132, 133, 134, 135, 136, 137, 140, 141, 146, 147, 148, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 163, 167, 168, 201, 313,314,315,325,328,329,334,335,336,337,338,339,340,341,342,343,345,347,348,349,350,352,353,355,356,358,359,360,361,362,363,364,367,368,369,370,371,372,373,374,375,376,377,378,379,380,382,383,387,388,389,390,391,392,393,394,395,396,397,398,400,401,402,403,405,406,407,408,409,410,411,412,413,417,418,419,421,423,424,427,428,429,433,434,435,436,437,439,440,441,442,443,444,445,446,447,448,449,10002,10006,10007,10008,10009,10010,10011,10012,10014,10020,2102
};
}
}

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

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