Compare commits

...

41 Commits

Author SHA1 Message Date
Gerardo Salazar
be6ead75e9 Revert some changes and adds pandas memory allocator 2020-12-06 19:15:38 -08:00
Gerardo Salazar
25f7ccf0c2 Performance improvements 2020-12-06 19:15:38 -08:00
Gerardo Salazar
afb0a86291 Faster double casting than CLR implementation 2020-12-06 19:14:48 -08:00
Gerardo Salazar
f5c5faa3d4 Remove bindingRedirect in Tests and re-adds pyarrow to DockerfileLeanFoundation 2020-12-06 19:14:48 -08:00
Gerardo Salazar
c471d1ced0 Adds unit tests and fixes various bugs in PandasConverter 2020-12-06 19:14:48 -08:00
Gerardo Salazar
5c345faea7 Revert changes to DockerfileLeanFoundation 2020-12-06 19:14:24 -08:00
Gerardo Salazar
11bed1f0c3 Address self-review: Adds explanitory comments and cleans up code 2020-12-06 19:14:24 -08:00
Gerardo Salazar
ec57799a5d Reverts changes and deletes unused classes 2020-12-06 19:14:24 -08:00
Gerardo Salazar
4c8aa638eb Fixes failing unit tests by converting filter mask to PyObject[] 2020-12-06 19:13:54 -08:00
Gerardo Salazar
7c3caec07f Use external NuGet package for Arrow
* Updates CI for Arrow
  * Updates DockerfileLeanFoundation
  * Adds Arrow to Python packages tests
2020-12-06 19:13:54 -08:00
Gerardo Salazar
45f71543bd Filters duplicates more efficiently, fixes warning on removal of dupes
* Commit includes behind the scenes testing and validation of the data
    and performance as a result of modifying the dupe removal process
2020-12-06 19:13:54 -08:00
Gerardo Salazar
13b9d91ecb Memory leak fixes for options and performance improvement for NA replace 2020-12-06 19:13:54 -08:00
Gerardo Salazar
f239018e77 Tames the wild beast of memory leaks, for QuoteBar data 2020-12-06 19:13:54 -08:00
Gerardo Salazar
f1c98b848a Partially fixes memory leak in PandasConverter 2020-12-06 19:13:54 -08:00
Gerardo Salazar
2c7bde422a Reuses MemoryStream to avoid having to reallocate consistently
* Cleans up .csproj and packages.config
  * Adds IDisposable to PandasConverter, PandasArrowMemoryAllocator
2020-12-06 19:13:54 -08:00
Gerardo Salazar
9e95bfea90 Fixes various bugs and matches previous version for backwards compat 2020-12-06 19:13:35 -08:00
Gerardo Salazar
b820ff4de8 EOD commit; Attempts to implement alt. data support 2020-12-06 19:13:35 -08:00
Gerardo Salazar
8218a2be99 Fixes bug with options and refactors code
* Option index is now 1-1 with existing impl
  * Begin adding support for custom data
2020-12-06 19:13:35 -08:00
Gerardo Salazar
9e3031c562 Fixes options support
* 1 bug remaining: Get index to be ''
2020-12-06 19:13:35 -08:00
Gerardo Salazar
2cf9239b6e Improves performance by ~10% of Arrow implementation
* New allocator improves performance by reusing allocated buffers
2020-12-06 19:13:35 -08:00
Gerardo Salazar
4c63c60a89 Adds option expiry support 2020-12-06 19:13:35 -08:00
Gerardo Salazar
9a2e47b05c Adds support for future expiry and proper indexing 2020-12-06 19:13:35 -08:00
Gerardo Salazar
711d46d6e2 Fixes bugs with index and handling of no data 2020-12-06 19:13:35 -08:00
Gerardo Salazar
5d6625c1ea Adds support for Tick, OpenInterest
* Performance improvement for Symbol creation in TimeSliceFactory for
    futures
2020-12-06 19:13:34 -08:00
Gerardo Salazar
0a70611d81 MVP of Apache Arrow in-memory pandas DataFrame construction 2020-12-06 19:13:16 -08:00
Gerardo Salazar
43c6d5cc5a Adds extra simplified history request method in PortfoliioLooper (#5003) 2020-12-06 15:13:32 -08:00
Martin-Molinero
f39acceadc Load fine fundamental from data cache folder (#4995)
* Load fine fundamental from data cache folder

* Add daily backwards lookup for live fine
2020-12-04 18:28:24 -08:00
Jasper van Merle
921ddced04 Fix invalid xml on SafeMultiply100 documentation (#5000) 2020-12-04 18:22:12 -08:00
Gerardo Salazar
ff8563244f Fixes Random Length Lumber contract multiplier/min price variation (#4993) 2020-12-04 12:33:19 -03:00
Colton Sellers
041f1d9db5 Limit Futures Resolutions in BaseData (#4979)
* Limit futures support resolutions

* Fix broken tests

* Address review
2020-12-03 21:50:17 -03:00
Gerardo Salazar
eb1181f5f7 Adds Futures Options Asset Class w/ IB Support (#4928)
* Adds preliminary universe selection for Future Options

* Fixes scaling issues with Future Options

* Fixes scaling multiplying by 10000x instead of using _scaleFactor

* Fixes scaling for Tick

* Revert changes to Tick since it divides the scaling factor

* Changes stale method name to new method name after rebase

* Fixes selection bugs, adds new methods, and adds unit tests

  * Fixes bug where Equity Symbol was created for an underlying
    non-equity Symbol, resulting in equity data trying to be loaded

  * Adds unit tests covering changes to Tick, QuoteBar, TradeBar and
    LeanData

  * Adds regression test for AddUniverseOption filter contract selection
    for Future Options

* Addresses review - modifies the AddFutureOption signature

  * Adds new AddUniverseOptions method overload
  * Removes and adds a new unit test
  * Misc. modifications to account for new changes

* Fixes bug where futures were loaded using default SID Date

  * Refactors and removes unnecessary work
  * Fixes regression algorithm, which previously made no trades

* Adds future option data

  * Adds the corresponding underlying data, in this case, futures data
    to enable usage of future options data

* Replaces data with new data (ES18Z20)

  * Improves Future chain filtering and updates regression stats

* Add AddFutureOptionContract API

* Expands regression and unit tests to test in finer detail

* Adds Python regression algorithms for AddFutureOption[Contract] methods

* Adds new unit test for BacktestingOptionChainProvider

  * Fixes bug with BacktesingOptionChainProvider where we
    attempted to load the Trades option chain first, resulting
    in breakage of backwards compatibility and limitation of the
    option chain.

  * Adds new regression algorithms (Py) to Algorithm.Python project

* Adds FutureOptionMarginBuyingPowerModel

  * Modifies code paths used to select margin model
  * Adds related unit tests for margin model

* Fixes issue with unit test and MHDB/SPDB lookup for Future Options

* Preliminary regression algorithm testing ITM call/put option buying

  * Fixes bug where fee model used did not find non-US market
    options fee model. We now use the futures fee model for future
    options because IB charges the same commissions per contract
    between futures and futures options

* Adds proper regression algorithm for ITM future options expiration

* Pushing broken algorithm for review

  * Currently, algorithm does not fill forward, causing
    a single future option to not get exercised when it is delisted.

* Adds FutureOptionPutITMExpiryRegressionAlgorithm

  * Improves existing regression algorithm for call side
  * Fixes bug in existing regression algorithm
  * Adds AAPL daily data to advance enumerator for ^^^ fix

* Adds additional future option regression algorithms

  * Adds Buy OTM expiration regression algorithms
  * Adds Sell ITM/OTM expiration regression algorithms
  * Adds missing Python regression algorithms

* Adds remaining Python regression algorithms and fixes issues

  * Fixes naming issues and statistics
  * Adds short option OTM regression algorithms (Py)

* Add license header and class comments to python algorithms

  * Cleans up comments and docstrings
  * Create Buy/Sell call intraday regression algo

* Redirects future options symbol properties to futures symbol properties

  * Asserts exercise/assignment price and updates stats in regression algos
  * Adds new unit test covering changes to SecurityService

* Adds comments and fixes failing test

* Partially fixes future option mis-calculated profit/loss

* Adjusts portfolio model to calculate FOP as a no upfront pay asset class

  * Updates regression algorithm statistics

* Begin IB FOP support

* Initial support for FOP IB data streaming, live í¾‰

  * Adds additional functionality to LiveOptionChainProvider
    - Allows querying CME API to retrieve option chains for CME products
    - Ultimately, it's also the groundwork for the CME
      LiveFutureChainProvider

  * Edits IDataQueueUniverseProvider interface to provide greater
    control to implementors of it

  * Misc. bug fixes required to get FOP data streaming through IB

* Adds comments, adds missing rategate call, and cleans up code

* Force exchange for FOP and Futures when no exchange is provided

* Fixes bug with Portfolio modeling across all asset classes

* Adds LiveOptionChainProvider tests for Future Options

* IB brokerage option symbol bug fixes and improvements

* Fixes contract multiplier lookup bug

  * Fixes issue where we attempted to subscribe to IB data feed with canonical security
  * Adds ES MHDB entry

* Reverts portfolio modeling changes for Futures Options

  * Since IB eats into our account's cash balance when
    a new FOP contract is purchased, we must model by applying funds
    to our cash whenever a new purchase/sell occurs.
    If we choose to model FOPs exactly as we do with futures, we
    will end up with an invalid TotalPortfolioValue on algorithm
    restart. By all means and purposes, FOPs are modeled exactly
    the same as equity options with respect to the portfolio.

  * Adds comments clarifying portfolio modeling and clarifies
    existing portfolio modeling comments with additional context.

* Fixes IB symbol lookup for future options

  * Fixes LiveOptionChainProvider looping 5 times per option chain
    request, even on success

  * Sets OptionChainedUniverseSelectionModel to produce a canonical
    future/future option/option Symbol to avoid creating two Symbols

  * Adds GLOBEX future option symbol mapping from future -> fop

* Fixes LiveOptionChainProvider loading wrong contract option chains

  * Fixes loading of futures options ZIP files when backtesting
  * Adds a string -> decimal JSON converter
  * Additional fixes/refactoring to the LiveOptionChainProvider

* Adds tests for changes to Symbol and LeanData

  * Reverts changes to IB-symbol-map

* Fixes Value for mapped future options tickers

  * Fixes Symbol test

* Changes path of future options to future's expiry date

  * Extra changes made to remove scaling from writing CSV
  * Added method to map from FOP Globex -> FUT Globex

* Fixes MOO and MOC orders for future options

  * Note: this order type might not be supported by IB or CME.

* Bug fixes and updates unit tests

* Update regression tests and data format

* Rebase changes

* 1. Multiple bug fixes for LiveOptionChainProvider, reverts IQFeed changes
2. Address review (partial): Code reuse and cleanup

1.
  * Modifies check in
    `AddFutureOptionShort(Call|Put)ITMExpiryRegressionAlgorithm`
    to ensure no buys have negative quantity

  * Code reuse changes in IB brokerage

  * Bug fix in IB brokerage where we assigned the FOP expiry
    as the futures expiry (requires verification)

  * Doc changes and adds missing summaries/license banners
  * Disposes of HTTP client resources in LiveOptionChainProvider
  * Renames classes and adds FutureOption folder in Common/Securities

2.
  * We revert back to the quotes API for the option chain,
    since the settlement API sometimes had missing strikes.

  * Fixes future option expiry being set as future's expiry
    in LiveOptionChainProvider

  * Fixes bug where wrong option chain was selected because of bad
    expiry lookup in the futures expiries returned from CME

  * Fixes multiple looping bug in LiveOptionChainProvider
  * Adds strike price scaling for LiveOptionChainProvider

  * Reverts IQFeed changes and simplifies interface upgrade changes

  Some additional challenges we'll have to solve as part of FOPs:

    - The `OptionSymbol.IsStandard` method makes the assumption that
      weeklies contracts follow the pattern equities follows, which
      does not apply to Futures Options

    - The Subscription created in:
        `OptionChainUniverseSubscriptionEnumeratorFactory`

      ...adds a Trade config. For illiquid contracts, this
      will delay universe selection for the option symbol
      until we get a trade. However, if we add a quote config,
      the data would instead be loaded based on the first quote
      we received from the brokerage.

      But since we're currently using a trade config, illiquid
      contracts won't start streaming data until it receives a trade.

NOTE: this commit is a WIP to addressing the reviews received in the PR,
but has been committed early for efficiency in the review process

* Fixes regression algorithms and misc. bugs

  * Fixes map file lookup for non-equity options
  * Adds extra assertion at end of algorithm to ensure no holdings are
    left when the algorithm ends.

  * Adds FutureOptionSymbol, allowing all contracts through as standard
  * Changes SPDB to allow defaulting to underlying future symbol
    properties if no entry is found for the given FOP

  * Fixes calls to SPDB in SecurityService, IBBrokerage
  * Reverts AAPL daily ZIP file to fix majority of regression algorithms
  * Adds FOPs symbol properties
  * Fixes existing symbol properties for a few futures
  * Adds tests for changes to Symbol Properties Database

* Removes string SPDB lookup method

  * Updates tests and misc callees of previous method

* Updates all regression tests to use data of already expired contracts

  * Adds Futures Options Expiry Functions tests
  * Adds required futures data for 2020-01-05

* Address review (partial): Expands test coverage and fixes tests

* Set option chain tests parallelism to fixture only

* Fixes broken test for contract month delta for FuturesOptionsExpiryFunctions

* Changes delisting date logic for Futures Options

* Address review: removes duplicate code, misc code fixes

  * Bug fix in MarketHoursDatabase.GetDatabaseSymbolKey() where
    we would use the underlying's Symbol for lookup in the MHDB

  * Adds missing license banner
  * Removes Futures Options entries from MHDB
  * Adds new tests

* Adds SecurityType.FutureOption

  * Converts any underlying comparisons and uses SecurityType directly
    instead for FOP specific behavior

  * Extra code modifications to acommodate new SecurityType

* Addresses review: fixes order fee bug on exercise

  * Additional bug fixes and adding of SecurityType.FutureOption
  * Updates regression algorithms OrderListHash

* Fixes various bugs in IB live implementation

  * Fixes bug setting the right contract expiration date for FOP
    generated by LiveOptionChainProvider

  * Adds new function to FuturesOptionsExpiryFunctions

  * Clarifies parameter names better in some functions/methods

  * Fixes bugs in IB brokerage for FOPs

* Address review - code cleanup and refactor

  * Remove MappingEventProvider, SplitEventProvider, and
    DividendEventProvider for Futures Options in
    CorporateEventEnumeratorFactory

* Address review: Use MHDB key resolver in SPDB

* Makes regression tests pass and adds comment for expiry issue

* Fixes MHDB lookup on string symbol method

* Adds Futures Options greeks regression algorithm (C# only)

* Adds explanitory comment on MHDB FOP lookup

* Remove python from FutureOptionCallITMGreeksExpiryRegressionAlgorithm
2020-12-02 21:49:59 -03:00
Colton Sellers
9d4014b7db Bug 4757 Synchronize Security Additions (#4879)
* Bump time by one tick in adding subscription

* Adjust regressions

* Change removal to immediate

* Use series.AddPoint instead of directly adding it

* Adjust regression

* Add checks for fixed behavior in regressions

* Address review
2020-12-02 21:20:30 -03:00
Adalyat Nazirov
a4f66628fd Lean Optimization interface in QCAlgorithm (#4923)
* initial commit

* run parametrized algorithm with command line parameters

* skeleton: top level structure

* OptimizationNodePacket scheme

* pass parameters as HashSet

* run Lean and read results

* call method on optimization completion

* refactor public interfaces

- close ParameterSet collection; allow only get operations
- explicit method to start LeanOptimizer

* synchronize RunLean method; the result could come in before the backtest id is set in the collections

* another portion of refactoring and interface changes

* comments

* comments & tests for Extremum, Minimization and Maximization classes

* unify optimization paramater values (min, max, step) & mode GridSearch tests

- swap min&max if necessary
- iterate left => right (negate step value if necessary) & provide default step value if step == 0
- no StackOverflow Exception
- parameterSet Id should be global for current generator and retain between steps
- test signle point boundary (min == max)

* BruteForceStrategy tests

* more comments

* Update Optimizer assembly information

- Update Optimizer projects assembly information to match behavior of
  the other projects

* Tweaks

- Adding comments
- Replace OnComplete for Ended event
- Replace Abort for Dispose
- ConsoleLeanOptimizer will keep track of running processes
- Each backtest will store results in a separated directory, so they
  don't fight for the log.txt file.
- Adding cmdline option for lean to close automatically
- Adding concurrent execution backtest limit
- Console optimizer will start Lean minimized
- Escape spaces in Json path

* remove parameter set generator abstraction layer

we don't need this flexibility now.

* refactor public methods; Step shouldn't be public

* constraints: wip

* define contract

* comparison operators and tests

* specify JsonProperty values

* Move SafeMultiply100 to extensions

* Throw exception on failed Optimizer.Start

* constraints: wip

* change finish & dispose process

* minor fixes

- handle force lean abort
- notify consumer if target has been reached

* target & constraints; adapt unit tests

* Minor Tweaks and fixes

- Some logging improvements
- Remove Public since not required

* Ignore empty ParameterValue

* simplify condition

* avoid reinitialization

* reduce type; force immutable

* unit tests for constraints  and target value

* parse & normalize percent values, i.e. 20% => 0.2

* fixup

* Target & Constraint & OptimizationNodePacket unit tests

* Add more json unit tests

- Adding more json conversion unit tests. Fix bug for Extremum which
  wasn't using the converter.

* LeanOptimizer tests

* Estimation results

* User thread safe counters

* LeanOptimizer unit tests; push OptimizationResult on Ended event

* more unit tests

* Minor tweaks

-Estimate ToString in a single line.
-Typos and missing header file

* Add base SendUpdate method

- Add base SendUpdate method for LeanOptimizer

* fix LeanOptimizer test; rely on internal Update rather than timer

* Add OptimizationStatus

- Add missing commments and OptimizationStatus

* EulerSearch implementation: wip

* OptimizationParameter custom converter

* change the type

* make step optional

* change folder structure

* enumerate optimization parameter using IEnumerable & IEnumerator

* unit tests: parameters & objectives

* unit tests: strategies

* remove redundant TODO

* change Euler search boundaries

* more Euler tests

* prevent race condition

* Add account/read endpoint

- Adding account/read endpoint. Adding unit test

* Add status check before running lean

* Minor self review

- Adding missing comments, minor changes

* remove array parameters

* minor changes

- tidy up config file, rename variable
- accept min less or equal than max

* move OptimizationParameter methods to strategies

* Minor improvements for BaseResultHandler derivates

* minor changes

- strict requirements for Step and MinStep values
- strategy specific settigs

* Add TotalRuntime to estimate

Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-12-02 20:10:40 -03:00
Michael Handschuh
b9974e6f54 Add OptionStrategyMatcher (#4924)
* Reformat/cleanup OptionStrategies

This file was breaking pretty much every style convention in LEAN.
There are other things that should be addressed in here that weren't,
such as passing non-argument names as argument names for ArgumentException,
as well as preferring constructors over property initializer syntax, but
such changes aren't being made to keep this commit strictly reformatting
instead of refactoring.

Added braces and reformatted long lines to make code more legible.

* Add abstract base class for OptionStrategy Option/UnderlyingLegData

This allows us to create either or and later use the Invoke method to push it
into the appropriate list on OptionStrategy.

* Replace O(n) option contract search with 2 O(1) TryGetValue calls

A better improvement would be resolving the correct symbol in the strategy, but
this immediate change is instead just focused on removing the O(n) search inside
a loop.

* Add BinaryComparison and supporting methods in ExpressionBuilder

We're going to use these binary comparisons to make it possible to create
ad-hoc queries against a collection of symbols. Using these expressions,
along with type supporting composition of these expression, we'll be able
to define predicates that can declaratively define how to match an option
strategy with an algorithms current holdings.

* Make GetValueOrDefault defaultValue optional

Was receiving ambiguous invocations leading to neading to invoke this
method explicitly (LinqExtensions.GetValueOrDefault) instead of being
able to use it as an extension method. Making the default value optional
seems to have resolved this ambiguity, leading to cleaner code in the
OptionPositionCollection (forthcoming)

* Add OptionPosition and OptionPositionCollection

OptionPositionCollection aims to provide a single coherent interface
for querying an algorithm's option contract positions and the underlying
equity's position in a performant, immutable way. The immutability of
the type is necessary for how the options matcher will operate. We need
to recursively evaluate potential matches, each step down the stack removing
positions from the collection consumed by each leg matched. This will enable
parallelism of the solution as well as simplifying the mental model for
understanding due to not needing to track mutations to the collection
instance.

* Add Option test class for easily creating option symbol objects

* Add OptionStrategyLegPredicate and OptionStrategyLegDefinition

The definition is a composition of predicates, and each predicate supports
matching against a set of pre-existing legs and a current position being
checked for the next leg (this leg). In addition to the matching functionality,
it also supports filtering the OptionPositionCollection, which is where much
of the work for resolving potential option strategies is done. By successively
filtering the OptionPositionCollection through successive application of predicates,
we wil end up with a small set of remaining positions that can be individually
evaluated for best margin impacts.

All of this effectively unrolls into a giant evaluation tree. Because of this
inherent structure, common in combinatorial optimization, the OptionPositionCollection
is an immutable type to support concurrent evaluations of different branches of
the tree. For large position collections this will dramatically improve strategy
resolution times. Finally, the interface between the predicate and the positions
collection is purposefully thin and provides a target for future optimizations.

* Add OptionStrategyDefinition and OptionStrategyDefinitions pre-defined definitions

The OptionStrategyDefinition is a definitional object provided a template and functions
used to match algorithm holdings (via OptionPositionCollection) to this definition. The
definition defines a particular way in which option positions can be combined in order to
achieve a more favorable margin requirement, thereby allowing the algorithm to hold more
positions than otherwise possible. This ties into the existing OptionStrategy classes and
the end result of the matching process will be OptionStrategy instances definiing all
strategies matched according to the provided definitions.

* Add OptionStrategyMatcher and Options class, w/ supporting types

OptionStrategyMatcherOptions aims to provide some knobs and dials to control how
the matcher behaves, and more importantly, which positions get prioritized when
matching. Prioritization is controlled via two different enumerators, one controller
which definitions are matched first and the other controller which positions are
matched first. Still unimplemented, is computing multiple solutions and running the
provided objective function to determine the best match. When this gets implemented,
we'll also want to implement the timer. For anyone looking to implement these features,
please talk with Michael Handschuh as there's a particular way of representing these
types of combinatorial solutions (a 3D tree) that can be used as a variation of the
linear simplex method for optimizing combinatorial problems.

* OptionStrategyMatcher: Address PR review comments

* Ensure created OptionStrategy legs all have the same multiplier

Each leg definition match gets it's own multiplier which indicates the
maximum number of times we matched that particular leg. When we finish
matching all legs, we pick the smallest multiplier from all the legs in
the definition and use that as the definition's multiplier. When we go
to create the OptionStrategy object we MUST make sure we're using the
multiplier from the definition and not from the individual legs.

This change fixes this issue and also provides a guard clause to ensure
that we're not trying to use a multiplier larger than what was matched.

* Add XML docs for OptionStrategyDefinitions from OptionStrategies
2020-12-02 18:42:24 -03:00
Stefano Raggi
474c5cd890 Update IBAutomater to v1.0.35 (#4984) 2020-12-02 09:56:36 -08:00
Martin-Molinero
0dfb368cb4 Lean shutdown improvements (#4982)
- Use DisposeSafely instead of dipose.
- Adding some logs to know when handlers are getting disposed.
- Normalize exit procedure
2020-12-02 09:55:50 -08:00
Colton Sellers
fbf5300bb6 Block SetWarmUp after Algorithm has compeleted initialization (#4975)
* Fix for #4939

* Address review

* Logic fix
2020-12-02 13:44:04 -03:00
Gerardo Salazar
c67845bd45 Fixes issue parsing SI data w/ unknown enum value (#4961)
* Extends tests to cover new additions
2020-12-01 20:17:25 -03:00
Colton Sellers
54ddbfbe24 Bug 4947 OnOrderEvent exceptions (#4974)
* Force algorithm to fail on runtime error

* add comment

* Remove unneeded change

* Update algorithm summary

* Address review
2020-11-30 15:55:39 -03:00
Gerardo Salazar
bd7be31ede Fixes inability to parse negative strike prices in SecurityIdentifier (#4953)
* Fixes inability to parse negative strike prices in SecurityIdentifier

  * Adds new tests ensuring backwards compat and no throwing w/ negative
    strike prices

* Changes strategy used to support negative strike prices

  * We add support for negative strike prices at the cost of
    reducing the maximum allowed precision for the strike price.
    We encode a negative sign into the 20th bit of the strike price
    and set our bounds for precision to a max (exclusive) of 475712.
    This in turn is then used to form a negative strike when rebuilding
    the SID.

  * Adds tests covering changes

* Address review: adds additional tests and refactors code

* Address self-review: remove unused import in SymbolTests

* Address review: adds additional test cases for OptionStyle and OptionRight

  * These tests are to ensure that backwards compatibility is maintained

* Addresses review: Adds option chain <-> master SID hash test

  * Refactors previous tests to reduce on code duplication

* Reduce test duplication

Co-authored-by: Martin Molinero <martin.molinero1@gmail.com>
2020-11-30 15:45:36 -03:00
Colton Sellers
041a111b92 Implement safe exiting for Lean (#4972) 2020-11-27 18:14:18 -03:00
292 changed files with 27254 additions and 1326 deletions

View File

@@ -16,6 +16,7 @@ before_install:
- conda install -y cython=0.29.15
- conda install -y scipy=1.4.1
- conda install -y wrapt=1.12.1
- pip install pyarrow==1.0.1
install:
- nuget restore QuantConnect.Lean.sln
- nuget install NUnit.Runners -Version 3.11.1 -OutputDirectory testrunner

View File

@@ -0,0 +1,210 @@
/*
* 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;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests that we receive the expected data when
/// we add future option contracts individually using <see cref="AddFutureOptionContract"/>
/// </summary>
public class AddFutureOptionContractDataStreamingRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private bool _onDataReached;
private bool _invested;
private Symbol _es20h20;
private Symbol _es19m20;
private readonly HashSet<Symbol> _symbolsReceived = new HashSet<Symbol>();
private readonly HashSet<Symbol> _expectedSymbolsReceived = new HashSet<Symbol>();
private readonly Dictionary<Symbol, List<QuoteBar>> _dataReceived = new Dictionary<Symbol, List<QuoteBar>>();
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 1, 6);
_es20h20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 3, 20)),
Resolution.Minute).Symbol;
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
var optionChains = OptionChainProvider.GetOptionContractList(_es20h20, Time)
.Concat(OptionChainProvider.GetOptionContractList(_es19m20, Time));
foreach (var optionContract in optionChains)
{
_expectedSymbolsReceived.Add(AddFutureOptionContract(optionContract, Resolution.Minute).Symbol);
}
if (_expectedSymbolsReceived.Count == 0)
{
throw new InvalidOperationException("Expected Symbols receive count is 0, expected >0");
}
}
public override void OnData(Slice data)
{
if (!data.HasData)
{
return;
}
_onDataReached = true;
var hasOptionQuoteBars = false;
foreach (var qb in data.QuoteBars.Values)
{
if (qb.Symbol.SecurityType != SecurityType.FutureOption)
{
continue;
}
hasOptionQuoteBars = true;
_symbolsReceived.Add(qb.Symbol);
if (!_dataReceived.ContainsKey(qb.Symbol))
{
_dataReceived[qb.Symbol] = new List<QuoteBar>();
}
_dataReceived[qb.Symbol].Add(qb);
}
if (_invested || !hasOptionQuoteBars)
{
return;
}
if (data.ContainsKey(_es20h20) && data.ContainsKey(_es19m20))
{
SetHoldings(_es20h20, 0.2);
SetHoldings(_es19m20, 0.2);
_invested = true;
}
}
public override void OnEndOfAlgorithm()
{
base.OnEndOfAlgorithm();
if (!_onDataReached)
{
throw new Exception("OnData() was never called.");
}
if (_symbolsReceived.Count != _expectedSymbolsReceived.Count)
{
throw new AggregateException($"Expected {_expectedSymbolsReceived.Count} option contracts Symbols, found {_symbolsReceived.Count}");
}
var missingSymbols = new List<Symbol>();
foreach (var expectedSymbol in _expectedSymbolsReceived)
{
if (!_symbolsReceived.Contains(expectedSymbol))
{
missingSymbols.Add(expectedSymbol);
}
}
if (missingSymbols.Count > 0)
{
throw new Exception($"Symbols: \"{string.Join(", ", missingSymbols)}\" were not found in OnData");
}
foreach (var expectedSymbol in _expectedSymbolsReceived)
{
var data = _dataReceived[expectedSymbol];
var nonDupeDataCount = data.Select(x =>
{
x.EndTime = default(DateTime);
return x;
}).Distinct().Count();
if (nonDupeDataCount < 1000)
{
throw new Exception($"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}");
}
}
}
/// <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%"},
{"Compounding Annual Return", "217.585%"},
{"Drawdown", "0.600%"},
{"Expectancy", "0"},
{"Net Profit", "0.635%"},
{"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", "-14.395"},
{"Tracking Error", "0.043"},
{"Treynor Ratio", "0"},
{"Total Fees", "$7.40"},
{"Fitness Score", "1"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "79228162514264337593543950335"},
{"Portfolio Turnover", "3.199"},
{"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", "1074366800"}
};
}
}

View File

@@ -0,0 +1,244 @@
/*
* 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;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests that we only receive the option chain for a single future contract
/// in the option universe filter.
/// </summary>
public class AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private bool _invested;
private bool _onDataReached;
private bool _optionFilterRan;
private readonly HashSet<Symbol> _symbolsReceived = new HashSet<Symbol>();
private readonly HashSet<Symbol> _expectedSymbolsReceived = new HashSet<Symbol>();
private readonly Dictionary<Symbol, List<QuoteBar>> _dataReceived = new Dictionary<Symbol, List<QuoteBar>>();
private Future _es;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 1, 6);
_es = AddFuture(Futures.Indices.SP500EMini, Resolution.Minute, Market.CME);
_es.SetFilter((futureFilter) =>
{
return futureFilter.Expiration(0, 365).ExpirationCycle(new[] { 3, 6 });
});
AddFutureOption(_es.Symbol, optionContracts =>
{
_optionFilterRan = true;
var expiry = new HashSet<DateTime>(optionContracts.Select(x => x.Underlying.ID.Date)).SingleOrDefault();
// Cast to IEnumerable<Symbol> because OptionFilterContract overrides some LINQ operators like `Select` and `Where`
// and cause it to mutate the underlying Symbol collection when using those operators.
var symbol = new HashSet<Symbol>(((IEnumerable<Symbol>)optionContracts).Select(x => x.Underlying)).SingleOrDefault();
if (expiry == null || symbol == null)
{
throw new InvalidOperationException("Expected a single Option contract in the chain, found 0 contracts");
}
var enumerator = optionContracts.GetEnumerator();
while (enumerator.MoveNext())
{
_expectedSymbolsReceived.Add(enumerator.Current);
}
return optionContracts;
});
}
public override void OnData(Slice data)
{
if (!data.HasData)
{
return;
}
_onDataReached = true;
var hasOptionQuoteBars = false;
foreach (var qb in data.QuoteBars.Values)
{
if (qb.Symbol.SecurityType != SecurityType.FutureOption)
{
continue;
}
hasOptionQuoteBars = true;
_symbolsReceived.Add(qb.Symbol);
if (!_dataReceived.ContainsKey(qb.Symbol))
{
_dataReceived[qb.Symbol] = new List<QuoteBar>();
}
_dataReceived[qb.Symbol].Add(qb);
}
if (_invested || !hasOptionQuoteBars)
{
return;
}
foreach (var chain in data.OptionChains.Values)
{
var futureInvested = false;
var optionInvested = false;
foreach (var option in chain.Contracts.Keys)
{
if (futureInvested && optionInvested)
{
return;
}
var future = option.Underlying;
if (!optionInvested && data.ContainsKey(option))
{
MarketOrder(option, 1);
_invested = true;
optionInvested = true;
}
if (!futureInvested && data.ContainsKey(future))
{
MarketOrder(future, 1);
_invested = true;
futureInvested = true;
}
}
}
}
public override void OnEndOfAlgorithm()
{
base.OnEndOfAlgorithm();
if (!_optionFilterRan)
{
throw new InvalidOperationException("Option chain filter was never ran");
}
if (!_onDataReached)
{
throw new Exception("OnData() was never called.");
}
if (_symbolsReceived.Count != _expectedSymbolsReceived.Count)
{
throw new AggregateException($"Expected {_expectedSymbolsReceived.Count} option contracts Symbols, found {_symbolsReceived.Count}");
}
var missingSymbols = new List<Symbol>();
foreach (var expectedSymbol in _expectedSymbolsReceived)
{
if (!_symbolsReceived.Contains(expectedSymbol))
{
missingSymbols.Add(expectedSymbol);
}
}
if (missingSymbols.Count > 0)
{
throw new Exception($"Symbols: \"{string.Join(", ", missingSymbols)}\" were not found in OnData");
}
foreach (var expectedSymbol in _expectedSymbolsReceived)
{
var data = _dataReceived[expectedSymbol];
var nonDupeDataCount = data.Select(x =>
{
x.EndTime = default(DateTime);
return x;
}).Distinct().Count();
if (nonDupeDataCount < 1000)
{
throw new Exception($"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}");
}
}
}
/// <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%"},
{"Compounding Annual Return", "-15.625%"},
{"Drawdown", "0.200%"},
{"Expectancy", "0"},
{"Net Profit", "-0.093%"},
{"Sharpe Ratio", "-11.181"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "0%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.002"},
{"Beta", "-0.016"},
{"Annual Standard Deviation", "0.001"},
{"Annual Variance", "0"},
{"Information Ratio", "-14.343"},
{"Tracking Error", "0.044"},
{"Treynor Ratio", "0.479"},
{"Total Fees", "$3.70"},
{"Fitness Score", "0.41"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "-185.654"},
{"Portfolio Turnover", "0.821"},
{"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", "1532330301"}
};
}
}

View File

@@ -30,6 +30,7 @@ namespace QuantConnect.Algorithm.CSharp
private readonly Dictionary<Symbol, int> _dataPointsPerSymbol = new Dictionary<Symbol, int>();
private bool _added;
private Symbol _eurusd;
private DateTime lastDataTime = DateTime.MinValue;
/// <summary>
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
@@ -51,6 +52,13 @@ namespace QuantConnect.Algorithm.CSharp
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice data)
{
if (lastDataTime == data.Time)
{
throw new Exception("Duplicate time for current data and last data slice");
}
lastDataTime = data.Time;
if (_added)
{
var eurUsdSubscription = SubscriptionManager.SubscriptionDataConfigService
@@ -94,7 +102,7 @@ namespace QuantConnect.Algorithm.CSharp
var expectedDataPointsPerSymbol = new Dictionary<string, int>
{
{ "EURGBP", 3 },
{ "EURUSD", 29 }
{ "EURUSD", 28 }
};
foreach (var kvp in _dataPointsPerSymbol)

View File

@@ -30,6 +30,7 @@ namespace QuantConnect.Algorithm.CSharp
private readonly Dictionary<Symbol, int> _dataPointsPerSymbol = new Dictionary<Symbol, int>();
private bool _added;
private Symbol _eurusd;
private DateTime lastDataTime = DateTime.MinValue;
/// <summary>
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
@@ -51,6 +52,13 @@ namespace QuantConnect.Algorithm.CSharp
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice data)
{
if (lastDataTime == data.Time)
{
throw new Exception("Duplicate time for current data and last data slice");
}
lastDataTime = data.Time;
if (_added)
{
var eurUsdSubscription = SubscriptionManager.SubscriptionDataConfigService
@@ -96,7 +104,7 @@ namespace QuantConnect.Algorithm.CSharp
// normal feed
{ "EURGBP", 3 },
// internal feed on the first day, normal feed on the other two days
{ "EURUSD", 3 },
{ "EURUSD", 2 },
// internal feed only
{ "GBPUSD", 0 }
};

View File

@@ -0,0 +1,169 @@
/*
* 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 System.Reflection;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests In The Money (ITM) future option calls across different strike prices.
/// We expect 6 orders from the algorithm, which are:
///
/// * (1) Initial entry, buy ES Call Option (ES19M20 expiring ITM)
/// * (2) Initial entry, sell ES Call Option at different strike (ES20H20 expiring ITM)
/// * [2] Option assignment, opens a position in the underlying (ES20H20, Qty: -1)
/// * [2] Future contract liquidation, due to impending expiry
/// * [1] Option exercise, receive 1 ES19M20 future contract
/// * [1] Liquidate ES19M20 contract, due to expiry
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
public class FutureOptionBuySellCallIntradayRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
var es20h20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 3, 20)),
Resolution.Minute).Symbol;
var es20m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
var esOptions = OptionChainProvider.GetOptionContractList(es20m20, Time)
.Concat(OptionChainProvider.GetOptionContractList(es20h20, Time))
.Where(x => x.ID.StrikePrice == 3200m && x.ID.OptionRight == OptionRight.Call)
.Select(x => AddFutureOptionContract(x, Resolution.Minute).Symbol)
.ToList();
var expectedContracts = new[]
{
QuantConnect.Symbol.CreateOption(es20h20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m,
new DateTime(2020, 3, 20)),
QuantConnect.Symbol.CreateOption(es20m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m,
new DateTime(2020, 6, 19))
};
foreach (var esOption in esOptions)
{
if (!expectedContracts.Contains(esOption))
{
throw new Exception($"Contract {esOption} was not found in the chain");
}
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(es20m20, 1), () =>
{
MarketOrder(esOptions[0], 1);
MarketOrder(esOptions[1], -1);
});
Schedule.On(DateRules.Tomorrow, TimeRules.Noon, () =>
{
Liquidate();
});
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "6"},
{"Average Win", "2.94%"},
{"Average Loss", "-4.15%"},
{"Compounding Annual Return", "-5.601%"},
{"Drawdown", "5.600%"},
{"Expectancy", "-0.146"},
{"Net Profit", "-2.771%"},
{"Sharpe Ratio", "-0.49"},
{"Probabilistic Sharpe Ratio", "10.583%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "0.71"},
{"Alpha", "-0.043"},
{"Beta", "-0.001"},
{"Annual Standard Deviation", "0.087"},
{"Annual Variance", "0.008"},
{"Information Ratio", "0.96"},
{"Tracking Error", "0.192"},
{"Treynor Ratio", "58.394"},
{"Total Fees", "$14.80"},
{"Fitness Score", "0.018"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-0.096"},
{"Return Over Maximum Drawdown", "-0.993"},
{"Portfolio Turnover", "0.043"},
{"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", "-290004562"}
};
}
}

View File

@@ -0,0 +1,259 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests In The Money (ITM) future option expiry for calls.
/// We expect 3 orders from the algorithm, which are:
///
/// * Initial entry, buy ES Call Option (expiring ITM)
/// * Option exercise, receiving ES future contracts
/// * Future contract liquidation, due to impending expiry
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
public class FutureOptionCallITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedOptionContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice <= 3200m && x.ID.OptionRight == OptionRight.Call)
.OrderByDescending(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedOptionContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m, new DateTime(2020, 6, 19));
if (_esOption != _expectedOptionContract)
{
throw new Exception($"Contract {_expectedOptionContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, 1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedOptionContract]);
}
else if (security.Symbol == _expectedOptionContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{Time:yyyy-MM-dd HH:mm:ss} -- {orderEvent.Symbol} :: Price: {Securities[orderEvent.Symbol].Holdings.Price} Qty: {Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}");
}
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
{
// We expect the liquidation to occur on the day of the delisting (while the market is open),
// but currently we liquidate at the next market open (AAPL open) which happens to be
// at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
// market open.
// Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
var expectedLiquidationTimeUtc = new DateTime(2020, 6, 22, 13, 32, 0);
if (orderEvent.Direction == OrderDirection.Sell && future.Holdings.Quantity != 0)
{
// We expect the contract to have been liquidated immediately
throw new Exception($"Did not liquidate existing holdings for Symbol {future.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Sell && orderEvent.UtcTime != expectedLiquidationTimeUtc)
{
throw new Exception($"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc:yyyy-MM-dd HH:mm:ss} - found {orderEvent.UtcTime:yyyy-MM-dd HH:mm:ss}");
}
// No way to detect option exercise orders or any other kind of special orders
// other than matching strings, for now.
if (orderEvent.Message.Contains("Option Exercise"))
{
if (orderEvent.FillPrice != 3200m)
{
throw new Exception("Option did not exercise at expected strike price (3200)");
}
if (future.Holdings.Quantity != 1)
{
// Here, we expect to have some holdings in the underlying, but not in the future option anymore.
throw new Exception($"Exercised option contract, but we have no holdings for Future {future.Symbol}");
}
if (optionContract.Holdings.Quantity != 0)
{
throw new Exception($"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}");
}
}
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
{
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
{
throw new Exception($"No holdings were created for option contract {option.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
{
throw new Exception($"Holdings were found after a filled option exercise");
}
if (orderEvent.Message.Contains("Exercise") && option.Holdings.Quantity != 0)
{
throw new Exception($"Holdings were found after exercising option contract {option.Symbol}");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "1.25%"},
{"Average Loss", "-7.42%"},
{"Compounding Annual Return", "-12.413%"},
{"Drawdown", "6.300%"},
{"Expectancy", "-0.416"},
{"Net Profit", "-6.257%"},
{"Sharpe Ratio", "-1.325"},
{"Probabilistic Sharpe Ratio", "0.004%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "0.17"},
{"Alpha", "-0.102"},
{"Beta", "-0.003"},
{"Annual Standard Deviation", "0.076"},
{"Annual Variance", "0.006"},
{"Information Ratio", "0.673"},
{"Tracking Error", "0.188"},
{"Treynor Ratio", "33.559"},
{"Total Fees", "$7.40"},
{"Fitness Score", "0.008"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-0.205"},
{"Return Over Maximum Drawdown", "-1.983"},
{"Portfolio Turnover", "0.023"},
{"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", "23301049"}
};
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests In The Money (ITM) future option expiry for calls.
/// We test to make sure that FOPs have greeks enabled, same as equity options.
/// </summary>
public class FutureOptionCallITMGreeksExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private bool _invested;
private int _onDataCalls;
private Symbol _es19m20;
private Option _esOption;
private Symbol _expectedOptionContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, new DateTime(2020, 1, 5))
.Where(x => x.ID.StrikePrice <= 3200m && x.ID.OptionRight == OptionRight.Call)
.OrderByDescending(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute);
_esOption.PriceModel = OptionPriceModels.BjerksundStensland();
_expectedOptionContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200m, new DateTime(2020, 6, 19));
if (_esOption.Symbol != _expectedOptionContract)
{
throw new Exception($"Contract {_expectedOptionContract} was not found in the chain");
}
}
public override void OnData(Slice data)
{
// Let the algo warmup, but without using SetWarmup. Otherwise, we get
// no contracts in the option chain
if (_invested || _onDataCalls++ < 40)
{
return;
}
if (data.OptionChains.Count == 0)
{
return;
}
if (data.OptionChains.Values.All(o => o.Contracts.Values.Any(c => !data.ContainsKey(c.Symbol))))
{
return;
}
if (data.OptionChains.Values.First().Contracts.Count == 0)
{
throw new Exception($"No contracts found in the option {data.OptionChains.Keys.First()}");
}
var deltas = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Delta).ToList();
var gammas = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Gamma).ToList();
var lambda = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Lambda).ToList();
var rho = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Rho).ToList();
var theta = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Theta).ToList();
var vega = data.OptionChains.Values.OrderByDescending(y => y.Contracts.Values.Sum(x => x.Volume)).First().Contracts.Values.Select(x => x.Greeks.Vega).ToList();
// The commented out test cases all return zero.
// This is because of failure to evaluate the greeks in the option pricing model.
// For now, let's skip those.
if (deltas.Any(d => d == 0))
{
throw new AggregateException("Option contract Delta was equal to zero");
}
if (gammas.Any(g => g == 0))
{
throw new AggregateException("Option contract Gamma was equal to zero");
}
//if (lambda.Any(l => l == 0))
//{
// throw new AggregateException("Option contract Lambda was equal to zero");
//}
if (rho.Any(r => r == 0))
{
throw new AggregateException("Option contract Rho was equal to zero");
}
//if (theta.Any(t => t == 0))
//{
// throw new AggregateException("Option contract Theta was equal to zero");
//}
//if (vega.Any(v => v == 0))
//{
// throw new AggregateException("Option contract Vega was equal to zero");
//}
if (!_invested)
{
SetHoldings(data.OptionChains.Values.First().Contracts.Values.First().Symbol, 1);
_invested = true;
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
if (!_invested)
{
throw new Exception($"Never checked greeks, maybe we have no option data?");
}
}
/// <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", "3"},
{"Average Win", "28.04%"},
{"Average Loss", "-62.81%"},
{"Compounding Annual Return", "-78.165%"},
{"Drawdown", "52.400%"},
{"Expectancy", "-0.277"},
{"Net Profit", "-52.379%"},
{"Sharpe Ratio", "-0.865"},
{"Probabilistic Sharpe Ratio", "0.019%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "0.45"},
{"Alpha", "-0.596"},
{"Beta", "-0.031"},
{"Annual Standard Deviation", "0.681"},
{"Annual Variance", "0.463"},
{"Information Ratio", "-0.514"},
{"Tracking Error", "0.703"},
{"Treynor Ratio", "18.748"},
{"Total Fees", "$66.60"},
{"Fitness Score", "0.157"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-0.133"},
{"Return Over Maximum Drawdown", "-1.492"},
{"Portfolio Turnover", "0.411"},
{"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", "151392833"}
};
}
}

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 System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests Out of The Money (OTM) future option expiry for calls.
/// We expect 1 order from the algorithm, which are:
///
/// * Initial entry, buy ES Call Option (expiring OTM)
/// - contract expires worthless, not exercised, so never opened a position in the underlying
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
/// <remarks>
/// Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
/// See related issue: https://github.com/QuantConnect/Lean/issues/4854
/// </remarks>
public class FutureOptionCallOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option call expiring OTM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice >= 3300m && x.ID.OptionRight == OptionRight.Call)
.OrderBy(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3300m, new DateTime(2020, 6, 19));
if (_esOption != _expectedContract)
{
throw new Exception($"Contract {_expectedContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, 1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
throw new Exception("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM");
}
if (security.Symbol == _expectedContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{orderEvent}");
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
{
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
{
throw new Exception($"No holdings were created for option contract {option.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
{
throw new Exception("Holdings were found after a filled option exercise");
}
if (orderEvent.Direction == OrderDirection.Sell && !orderEvent.Message.Contains("OTM"))
{
throw new Exception("Contract did not expire OTM");
}
if (orderEvent.Message.Contains("Exercise"))
{
throw new Exception("Exercised option, even though it expires OTM");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "-4.03%"},
{"Compounding Annual Return", "-8.088%"},
{"Drawdown", "4.000%"},
{"Expectancy", "-1"},
{"Net Profit", "-4.029%"},
{"Sharpe Ratio", "-1.274"},
{"Probabilistic Sharpe Ratio", "0.015%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "-0.066"},
{"Beta", "-0.002"},
{"Annual Standard Deviation", "0.052"},
{"Annual Variance", "0.003"},
{"Information Ratio", "0.9"},
{"Tracking Error", "0.179"},
{"Treynor Ratio", "28.537"},
{"Total Fees", "$3.70"},
{"Fitness Score", "0"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-0.183"},
{"Return Over Maximum Drawdown", "-2.007"},
{"Portfolio Turnover", "0"},
{"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", "-1116221764"}
};
}
}

View File

@@ -0,0 +1,259 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests In The Money (ITM) future option expiry for puts.
/// We expect 3 orders from the algorithm, which are:
///
/// * Initial entry, buy ES Put Option (expiring ITM) (buy, qty 1)
/// * Option exercise, receiving short ES future contracts (sell, qty -1)
/// * Future contract liquidation, due to impending expiry (buy qty 1)
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
public class FutureOptionPutITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice >= 3300m && x.ID.OptionRight == OptionRight.Put)
.OrderBy(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3300m, new DateTime(2020, 6, 19));
if (_esOption != _expectedContract)
{
throw new Exception($"Contract {_expectedContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, 1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedContract]);
}
else if (security.Symbol == _expectedContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{Time:yyyy-MM-dd HH:mm:ss} -- {orderEvent.Symbol} :: Price: {Securities[orderEvent.Symbol].Holdings.Price} Qty: {Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}");
}
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
{
// We expect the liquidation to occur on the day of the delisting (while the market is open),
// but currently we liquidate at the next market open (AAPL open) which happens to be
// at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
// market open.
// Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
var expectedLiquidationTimeUtc = new DateTime(2020, 6, 22, 13, 32, 0);
if (orderEvent.Direction == OrderDirection.Buy && future.Holdings.Quantity != 0)
{
// We expect the contract to have been liquidated immediately
throw new Exception($"Did not liquidate existing holdings for Symbol {future.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Buy && orderEvent.UtcTime != expectedLiquidationTimeUtc)
{
throw new Exception($"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc:yyyy-MM-dd HH:mm:ss} - found {orderEvent.UtcTime:yyyy-MM-dd HH:mm:ss}");
}
// No way to detect option exercise orders or any other kind of special orders
// other than matching strings, for now.
if (orderEvent.Message.Contains("Option Exercise"))
{
if (orderEvent.FillPrice != 3300m)
{
throw new Exception("Option did not exercise at expected strike price (3300)");
}
if (future.Holdings.Quantity != -1)
{
// Here, we expect to have some holdings in the underlying, but not in the future option anymore.
throw new Exception($"Exercised option contract, but we have no holdings for Future {future.Symbol}");
}
if (optionContract.Holdings.Quantity != 0)
{
throw new Exception($"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}");
}
}
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
{
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
{
throw new Exception($"No holdings were created for option contract {option.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
{
throw new Exception($"Holdings were found after a filled option exercise");
}
if (orderEvent.Message.Contains("Exercise") && option.Holdings.Quantity != 0)
{
throw new Exception($"Holdings were found after exercising option contract {option.Symbol}");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "4.18%"},
{"Average Loss", "-8.27%"},
{"Compounding Annual Return", "-8.879%"},
{"Drawdown", "4.400%"},
{"Expectancy", "-0.247"},
{"Net Profit", "-4.432%"},
{"Sharpe Ratio", "-1.391"},
{"Probabilistic Sharpe Ratio", "0.002%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "0.51"},
{"Alpha", "-0.073"},
{"Beta", "-0.002"},
{"Annual Standard Deviation", "0.052"},
{"Annual Variance", "0.003"},
{"Information Ratio", "0.863"},
{"Tracking Error", "0.179"},
{"Treynor Ratio", "38.46"},
{"Total Fees", "$7.40"},
{"Fitness Score", "0.008"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-0.224"},
{"Return Over Maximum Drawdown", "-2.003"},
{"Portfolio Turnover", "0.023"},
{"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", "-675079082"}
};
}
}

View File

@@ -0,0 +1,225 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests Out of The Money (OTM) future option expiry for puts.
/// We expect 1 order from the algorithm, which are:
///
/// * Initial entry, buy ES Put Option (expiring OTM)
/// - contract expires worthless, not exercised, so never opened a position in the underlying
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
/// <remarks>
/// Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
/// </remarks>
public class FutureOptionPutOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice <= 3150m && x.ID.OptionRight == OptionRight.Put)
.OrderByDescending(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3150m, new DateTime(2020, 6, 19));
if (_esOption != _expectedContract)
{
throw new Exception($"Contract {_expectedContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, 1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
throw new Exception("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM");
}
if (security.Symbol == _expectedContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{orderEvent}");
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
{
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 1)
{
throw new Exception($"No holdings were created for option contract {option.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != 0)
{
throw new Exception("Holdings were found after a filled option exercise");
}
if (orderEvent.Direction == OrderDirection.Sell && !orderEvent.Message.Contains("OTM"))
{
throw new Exception("Contract did not expire OTM");
}
if (orderEvent.Message.Contains("Exercise"))
{
throw new Exception("Exercised option, even though it expires OTM");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "-5.12%"},
{"Compounding Annual Return", "-10.212%"},
{"Drawdown", "5.100%"},
{"Expectancy", "-1"},
{"Net Profit", "-5.116%"},
{"Sharpe Ratio", "-1.26"},
{"Probabilistic Sharpe Ratio", "0.016%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "-0.084"},
{"Beta", "-0.003"},
{"Annual Standard Deviation", "0.066"},
{"Annual Variance", "0.004"},
{"Information Ratio", "0.785"},
{"Tracking Error", "0.184"},
{"Treynor Ratio", "28.158"},
{"Total Fees", "$3.70"},
{"Fitness Score", "0"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "-0.181"},
{"Return Over Maximum Drawdown", "-1.995"},
{"Portfolio Turnover", "0"},
{"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", "515984318"}
};
}
}

View File

@@ -0,0 +1,238 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests In The Money (ITM) future option expiry for short calls.
/// We expect 3 orders from the algorithm, which are:
///
/// * Initial entry, sell ES Call Option (expiring ITM)
/// * Option assignment, sell 1 contract of the underlying (ES)
/// * Future contract expiry, liquidation (buy 1 ES future)
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
public class FutureOptionShortCallITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice <= 3100m && x.ID.OptionRight == OptionRight.Call)
.OrderByDescending(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3100m, new DateTime(2020, 6, 19));
if (_esOption != _expectedContract)
{
throw new Exception($"Contract {_expectedContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, -1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedContract]);
}
else if (security.Symbol == _expectedContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{orderEvent}");
}
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
{
if (orderEvent.Message.Contains("Assignment"))
{
if (orderEvent.FillPrice != 3100m)
{
throw new Exception("Option was not assigned at expected strike price (3100)");
}
if (orderEvent.Direction != OrderDirection.Sell || future.Holdings.Quantity != -1)
{
throw new Exception($"Expected Qty: -1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}");
}
return;
}
if (orderEvent.Direction == OrderDirection.Buy && future.Holdings.Quantity != 0)
{
// We buy back the underlying at expiration, so we expect a neutral position then
throw new Exception($"Expected no holdings when liquidating future contract {future.Symbol}");
}
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
{
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != -1)
{
throw new Exception($"No holdings were created for option contract {option.Symbol}");
}
if (orderEvent.IsAssignment && option.Holdings.Quantity != 0)
{
throw new Exception($"Holdings were found after option contract was assigned: {option.Symbol}");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "10.05%"},
{"Average Loss", "-5.60%"},
{"Compounding Annual Return", "8.121%"},
{"Drawdown", "0.500%"},
{"Expectancy", "0.396"},
{"Net Profit", "3.880%"},
{"Sharpe Ratio", "1.192"},
{"Probabilistic Sharpe Ratio", "58.203%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "1.79"},
{"Alpha", "0.069"},
{"Beta", "0.003"},
{"Annual Standard Deviation", "0.057"},
{"Annual Variance", "0.003"},
{"Information Ratio", "1.641"},
{"Tracking Error", "0.18"},
{"Treynor Ratio", "22.101"},
{"Total Fees", "$7.40"},
{"Fitness Score", "0.021"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "17.255"},
{"Portfolio Turnover", "0.021"},
{"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", "1118389718"}
};
}
}

View File

@@ -0,0 +1,219 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests Out of The Money (OTM) future option expiry for short calls.
/// We expect 1 order from the algorithm, which are:
///
/// * Initial entry, sell ES Call Option (expiring OTM)
/// - Profit the option premium, since the option was not assigned.
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
public class FutureOptionShortCallOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice >= 3400m && x.ID.OptionRight == OptionRight.Call)
.OrderBy(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3400m, new DateTime(2020, 6, 19));
if (_esOption != _expectedContract)
{
throw new Exception($"Contract {_expectedContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, -1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
throw new Exception($"Expected no order events for underlying Symbol {security.Symbol}");
}
if (security.Symbol == _expectedContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{orderEvent}");
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security optionContract)
{
if (orderEvent.Direction == OrderDirection.Sell && optionContract.Holdings.Quantity != -1)
{
throw new Exception($"No holdings were created for option contract {optionContract.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Buy && optionContract.Holdings.Quantity != 0)
{
throw new Exception("Expected no options holdings after closing position");
}
if (orderEvent.IsAssignment)
{
throw new Exception($"Assignment was not expected for {orderEvent.Symbol}");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "1.81%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "3.745%"},
{"Drawdown", "0.000%"},
{"Expectancy", "0"},
{"Net Profit", "1.809%"},
{"Sharpe Ratio", "1.292"},
{"Probabilistic Sharpe Ratio", "65.890%"},
{"Loss Rate", "0%"},
{"Win Rate", "100%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.031"},
{"Beta", "0.001"},
{"Annual Standard Deviation", "0.024"},
{"Annual Variance", "0.001"},
{"Information Ratio", "1.496"},
{"Tracking Error", "0.173"},
{"Treynor Ratio", "27.281"},
{"Total Fees", "$3.70"},
{"Fitness Score", "0"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "95.176"},
{"Portfolio Turnover", "0"},
{"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", "1364902860"}
};
}
}

View File

@@ -0,0 +1,235 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests In The Money (ITM) future option expiry for short puts.
/// We expect 3 orders from the algorithm, which are:
///
/// * Initial entry, sell ES Put Option (expiring ITM)
/// * Option assignment, buy 1 contract of the underlying (ES)
/// * Future contract expiry, liquidation (sell 1 ES future)
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
public class FutureOptionShortPutITMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice <= 3400m && x.ID.OptionRight == OptionRight.Put)
.OrderByDescending(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3400m, new DateTime(2020, 6, 19));
if (_esOption != _expectedContract)
{
throw new Exception($"Contract {_expectedContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, -1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
AssertFutureOptionOrderExercise(orderEvent, security, Securities[_expectedContract]);
}
else if (security.Symbol == _expectedContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{orderEvent}");
}
private void AssertFutureOptionOrderExercise(OrderEvent orderEvent, Security future, Security optionContract)
{
if (orderEvent.Message.Contains("Assignment"))
{
if (orderEvent.FillPrice != 3400)
{
throw new Exception("Option was not assigned at expected strike price (3400)");
}
if (orderEvent.Direction != OrderDirection.Buy || future.Holdings.Quantity != 1)
{
throw new Exception($"Expected Qty: 1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}");
}
}
if (!orderEvent.Message.Contains("Assignment") && orderEvent.Direction == OrderDirection.Sell && future.Holdings.Quantity != 0)
{
// We buy back the underlying at expiration, so we expect a neutral position then
throw new Exception($"Expected no holdings when liquidating future contract {future.Symbol}");
}
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
{
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != -1)
{
throw new Exception($"No holdings were created for option contract {option.Symbol}");
}
if (orderEvent.IsAssignment && option.Holdings.Quantity != 0)
{
throw new Exception($"Holdings were found after option contract was assigned: {option.Symbol}");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "10.18%"},
{"Average Loss", "-8.02%"},
{"Compounding Annual Return", "2.773%"},
{"Drawdown", "0.500%"},
{"Expectancy", "0.135"},
{"Net Profit", "1.343%"},
{"Sharpe Ratio", "0.939"},
{"Probabilistic Sharpe Ratio", "46.842%"},
{"Loss Rate", "50%"},
{"Win Rate", "50%"},
{"Profit-Loss Ratio", "1.27"},
{"Alpha", "0.023"},
{"Beta", "0.002"},
{"Annual Standard Deviation", "0.025"},
{"Annual Variance", "0.001"},
{"Information Ratio", "1.45"},
{"Tracking Error", "0.173"},
{"Treynor Ratio", "14.62"},
{"Total Fees", "$7.40"},
{"Fitness Score", "0.021"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "5.815"},
{"Portfolio Turnover", "0.022"},
{"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", "980293281"}
};
}
}

View File

@@ -0,0 +1,218 @@
/*
* 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 System.Reflection;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// This regression algorithm tests Out of The Money (OTM) future option expiry for short puts.
/// We expect 1 order from the algorithm, which are:
///
/// * Initial entry, sell ES Put Option (expiring OTM)
/// - Profit the option premium, since the option was not assigned.
///
/// Additionally, we test delistings for future options and assert that our
/// portfolio holdings reflect the orders the algorithm has submitted.
/// </summary>
public class FutureOptionShortPutOTMExpiryRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _es19m20;
private Symbol _esOption;
private Symbol _expectedContract;
public override void Initialize()
{
SetStartDate(2020, 1, 5);
SetEndDate(2020, 6, 30);
// We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
// which causes delisting events to never be processed, thus leading to options that might never
// be exercised until the next data point arrives.
AddEquity("AAPL", Resolution.Daily);
_es19m20 = AddFutureContract(
QuantConnect.Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
new DateTime(2020, 6, 19)),
Resolution.Minute).Symbol;
// Select a future option expiring ITM, and adds it to the algorithm.
_esOption = AddFutureOptionContract(OptionChainProvider.GetOptionContractList(_es19m20, Time)
.Where(x => x.ID.StrikePrice <= 3000m && x.ID.OptionRight == OptionRight.Put)
.OrderByDescending(x => x.ID.StrikePrice)
.Take(1)
.Single(), Resolution.Minute).Symbol;
_expectedContract = QuantConnect.Symbol.CreateOption(_es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3000m, new DateTime(2020, 6, 19));
if (_esOption != _expectedContract)
{
throw new Exception($"Contract {_expectedContract} was not found in the chain");
}
Schedule.On(DateRules.Tomorrow, TimeRules.AfterMarketOpen(_es19m20, 1), () =>
{
MarketOrder(_esOption, -1);
});
}
public override void OnData(Slice data)
{
// Assert delistings, so that we can make sure that we receive the delisting warnings at
// the expected time. These assertions detect bug #4872
foreach (var delisting in data.Delistings.Values)
{
if (delisting.Type == DelistingType.Warning)
{
if (delisting.Time != new DateTime(2020, 6, 19))
{
throw new Exception($"Delisting warning issued at unexpected date: {delisting.Time}");
}
}
if (delisting.Type == DelistingType.Delisted)
{
if (delisting.Time != new DateTime(2020, 6, 20))
{
throw new Exception($"Delisting happened at unexpected date: {delisting.Time}");
}
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
// There's lots of noise with OnOrderEvent, but we're only interested in fills.
return;
}
if (!Securities.ContainsKey(orderEvent.Symbol))
{
throw new Exception($"Order event Symbol not found in Securities collection: {orderEvent.Symbol}");
}
var security = Securities[orderEvent.Symbol];
if (security.Symbol == _es19m20)
{
throw new Exception($"Expected no order events for underlying Symbol {security.Symbol}");
}
if (security.Symbol == _expectedContract)
{
AssertFutureOptionContractOrder(orderEvent, security);
}
else
{
throw new Exception($"Received order event for unknown Symbol: {orderEvent.Symbol}");
}
Log($"{orderEvent}");
}
private void AssertFutureOptionContractOrder(OrderEvent orderEvent, Security option)
{
if (orderEvent.Direction == OrderDirection.Sell && option.Holdings.Quantity != -1)
{
throw new Exception($"No holdings were created for option contract {option.Symbol}");
}
if (orderEvent.Direction == OrderDirection.Buy && option.Holdings.Quantity != 0)
{
throw new Exception("Expected no options holdings after closing position");
}
if (orderEvent.IsAssignment)
{
throw new Exception($"Assignment was not expected for {orderEvent.Symbol}");
}
}
/// <summary>
/// Ran at the end of the algorithm to ensure the algorithm has no holdings
/// </summary>
/// <exception cref="Exception">The algorithm has holdings</exception>
public override void OnEndOfAlgorithm()
{
if (Portfolio.Invested)
{
throw new Exception($"Expected no holdings at end of algorithm, but are invested in: {string.Join(", ", Portfolio.Keys)}");
}
}
/// <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", "3.28%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "6.852%"},
{"Drawdown", "0.000%"},
{"Expectancy", "0"},
{"Net Profit", "3.284%"},
{"Sharpe Ratio", "1.319"},
{"Probabilistic Sharpe Ratio", "66.574%"},
{"Loss Rate", "0%"},
{"Win Rate", "100%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.058"},
{"Beta", "0.002"},
{"Annual Standard Deviation", "0.043"},
{"Annual Variance", "0.002"},
{"Information Ratio", "1.614"},
{"Tracking Error", "0.176"},
{"Treynor Ratio", "28.2"},
{"Total Fees", "$3.70"},
{"Fitness Score", "0"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "150.252"},
{"Portfolio Turnover", "0"},
{"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", "-418839052"}
};
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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;
using QuantConnect.Orders;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Regression Algorithm for testing engine behavior with throwing errors in OnOrderEvent
/// Should result in a RunTimeError status.
/// Reference GH Issue #4947
/// </summary>
public class OnOrderEventExceptionRegression : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
/// <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, 11);
AddEquity("SPY", Resolution.Minute);
}
/// <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);
Debug("Purchased Stock");
}
}
/// <summary>
/// OnOrderEvent is called whenever an order is updated
/// </summary>
/// <param name="orderEvent">Order Event</param>
public override void OnOrderEvent(OrderEvent orderEvent)
{
throw new Exception("OnOrderEvent exception");
}
/// <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>
{
};
}
}

View File

@@ -143,8 +143,21 @@
</Compile>
<Compile Include="AddAlphaModelAlgorithm.cs" />
<Compile Include="CustomBuyingPowerModelAlgorithm.cs" />
<Compile Include="AddFutureOptionContractDataStreamingRegressionAlgorithm.cs" />
<Compile Include="AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.cs" />
<Compile Include="AddOptionContractExpiresRegressionAlgorithm.cs" />
<Compile Include="AltData\QuiverWallStreetBetsDataAlgorithm.cs" />
<Compile Include="FutureOptionCallITMGreeksExpiryRegressionAlgorithm.cs" />
<Compile Include="OnOrderEventExceptionRegression.cs" />
<Compile Include="FutureOptionCallITMExpiryRegressionAlgorithm.cs" />
<Compile Include="FutureOptionCallOTMExpiryRegressionAlgorithm.cs" />
<Compile Include="FutureOptionPutITMExpiryRegressionAlgorithm.cs" />
<Compile Include="FutureOptionPutOTMExpiryRegressionAlgorithm.cs" />
<Compile Include="FutureOptionBuySellCallIntradayRegressionAlgorithm.cs" />
<Compile Include="FutureOptionShortCallITMExpiryRegressionAlgorithm.cs" />
<Compile Include="FutureOptionShortCallOTMExpiryRegressionAlgorithm.cs" />
<Compile Include="FutureOptionShortPutOTMExpiryRegressionAlgorithm.cs" />
<Compile Include="FutureOptionShortPutITMExpiryRegressionAlgorithm.cs" />
<Compile Include="ScaledFillForwardDataRegressionAlgorithm.cs" />
<Compile Include="DailyHistoryForDailyResolutionRegressionAlgorithm.cs" />
<Compile Include="DailyHistoryForMinuteResolutionRegressionAlgorithm.cs" />
@@ -368,6 +381,7 @@
<Compile Include="USEnergyInformationAdministrationAlgorithm.cs" />
<Compile Include="UserDefinedUniverseAlgorithm.cs" />
<Compile Include="VolumeWeightedAveragePriceExecutionModelRegressionAlgorithm.cs" />
<Compile Include="WarmUpAfterIntializeRegression.cs" />
<Compile Include="WarmupAlgorithm.cs" />
<Compile Include="WarmupConversionRatesRegressionAlgorithm.cs" />
<Compile Include="WarmupHistoryAlgorithm.cs" />

View File

@@ -32,6 +32,7 @@ namespace QuantConnect.Algorithm.CSharp
{
private Symbol _spy;
private int _reselectedSpy = -1;
private DateTime lastDataTime = DateTime.MinValue;
/// <summary>
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
@@ -57,6 +58,13 @@ namespace QuantConnect.Algorithm.CSharp
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice data)
{
if (lastDataTime == data.Time)
{
throw new Exception("Duplicate time for current data and last data slice");
}
lastDataTime = data.Time;
if (_reselectedSpy == 0)
{
if (!Securities[_spy].IsTradable)
@@ -111,29 +119,29 @@ namespace QuantConnect.Algorithm.CSharp
{"Total Trades", "1"},
{"Average Win", "0%"},
{"Average Loss", "0%"},
{"Compounding Annual Return", "75.079%"},
{"Drawdown", "2.200%"},
{"Compounding Annual Return", "69.904%"},
{"Drawdown", "2.000%"},
{"Expectancy", "0"},
{"Net Profit", "4.711%"},
{"Sharpe Ratio", "5.067"},
{"Probabilistic Sharpe Ratio", "84.391%"},
{"Net Profit", "4.453%"},
{"Sharpe Ratio", "4.805"},
{"Probabilistic Sharpe Ratio", "83.459%"},
{"Loss Rate", "0%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0.562"},
{"Beta", "0.02"},
{"Annual Standard Deviation", "0.113"},
{"Annual Variance", "0.013"},
{"Information Ratio", "0.511"},
{"Tracking Error", "0.159"},
{"Treynor Ratio", "28.945"},
{"Total Fees", "$3.22"},
{"Fitness Score", "0.037"},
{"Alpha", "0.501"},
{"Beta", "0.068"},
{"Annual Standard Deviation", "0.111"},
{"Annual Variance", "0.012"},
{"Information Ratio", "0.284"},
{"Tracking Error", "0.153"},
{"Treynor Ratio", "7.844"},
{"Total Fees", "$3.23"},
{"Fitness Score", "0.038"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "17.868"},
{"Return Over Maximum Drawdown", "34.832"},
{"Portfolio Turnover", "0.037"},
{"Sortino Ratio", "16.857"},
{"Return Over Maximum Drawdown", "34.897"},
{"Portfolio Turnover", "0.038"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
{"Total Insights Analysis Completed", "0"},
@@ -147,7 +155,7 @@ namespace QuantConnect.Algorithm.CSharp
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "836605283"}
{"OrderListHash", "1664042885"}
};
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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>
/// Regression algorithm to test warming up after initialize behavior, should throw if used outside of initialize
/// Reference GH Issue #4939
/// </summary>
public class WarmUpAfterIntializeRegression : QCAlgorithm, IRegressionAlgorithmDefinition
{
public override void Initialize()
{
SetStartDate(2013, 10, 07); //Set Start Date
SetEndDate(2013, 10, 11); //Set End Date
SetCash(100000);
var equity = AddEquity("SPY");
}
public override void OnData(Slice slice)
{
// Should throw and set Algorithm status to be runtime error
SetWarmUp(TimeSpan.FromDays(2));
}
/// <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>
{
};
}
}

View File

@@ -96,9 +96,9 @@ namespace QuantConnect.Algorithm.Framework.Selection
var uniqueUnderlyingSymbols = new HashSet<Symbol>();
foreach (var optionSymbol in _optionChainSymbolSelector(algorithm.UtcTime))
{
if (optionSymbol.SecurityType != SecurityType.Option)
if (optionSymbol.SecurityType != SecurityType.Option && optionSymbol.SecurityType != SecurityType.FutureOption)
{
throw new ArgumentException("optionChainSymbolSelector must return option symbols.");
throw new ArgumentException("optionChainSymbolSelector must return option or futures options symbols.");
}
// prevent creating duplicate option chains -- one per underlying
@@ -118,4 +118,4 @@ namespace QuantConnect.Algorithm.Framework.Selection
return filter;
}
}
}
}

View File

@@ -58,8 +58,8 @@ class OptionUniverseSelectionModel(UniverseSelectionModel):
uniqueUnderlyingSymbols = set()
for optionSymbol in self.optionChainSymbolSelector(algorithm.UtcTime):
if optionSymbol.SecurityType != SecurityType.Option:
raise ValueError("optionChainSymbolSelector must return option symbols.")
if optionSymbol.SecurityType != SecurityType.Option and optionSymbol.SecurityType != SecurityType.FutureOption:
raise ValueError("optionChainSymbolSelector must return option or futures options symbols.")
# prevent creating duplicate option chains -- one per underlying
if optionSymbol.Underlying not in uniqueUnderlyingSymbols:
@@ -73,7 +73,7 @@ class OptionUniverseSelectionModel(UniverseSelectionModel):
symbol: Symbol of the option
Returns:
OptionChainUniverse for the given symbol'''
if symbol.SecurityType != SecurityType.Option:
if symbol.SecurityType != SecurityType.Option and symbol.SecurityType != SecurityType.FutureOption:
raise ValueError("CreateOptionChain requires an option symbol.")
# rewrite non-canonical symbols to be canonical
@@ -122,4 +122,4 @@ class OptionUniverseSelectionModel(UniverseSelectionModel):
def Filter(self, filter):
'''Defines the option chain universe filter'''
# NOP
return filter
return filter

View File

@@ -0,0 +1,103 @@
# 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 datetime import datetime, timedelta
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests that we receive the expected data when
### we add future option contracts individually using <see cref="AddFutureOptionContract"/>
### </summary>
class AddFutureOptionContractDataStreamingRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.onDataReached = False
self.invested = False
self.symbolsReceived = []
self.expectedSymbolsReceived = []
self.dataReceived = {}
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 1, 6)
self.es20h20 = self.AddFutureContract(
Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, datetime(2020, 3, 20)),
Resolution.Minute).Symbol
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, datetime(2020, 6, 19)),
Resolution.Minute).Symbol
optionChains = self.OptionChainProvider.GetOptionContractList(self.es20h20, self.Time)
optionChains += self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time)
for optionContract in optionChains:
self.expectedSymbolsReceived.append(self.AddFutureOptionContract(optionContract, Resolution.Minute).Symbol)
def OnData(self, data: Slice):
if not data.HasData:
return
self.onDataReached = True
hasOptionQuoteBars = False
for qb in data.QuoteBars.Values:
if qb.Symbol.SecurityType != SecurityType.FutureOption:
continue
hasOptionQuoteBars = True
self.symbolsReceived.append(qb.Symbol)
if qb.Symbol not in self.dataReceived:
self.dataReceived[qb.Symbol] = []
self.dataReceived[qb.Symbol].append(qb)
if self.invested or not hasOptionQuoteBars:
return
if data.ContainsKey(self.es20h20) and data.ContainsKey(self.es19m20):
self.SetHoldings(self.es20h20, 0.2)
self.SetHoldings(self.es19m20, 0.2)
self.invested = True
def OnEndOfAlgorithm(self):
super().OnEndOfAlgorithm()
self.symbolsReceived = list(set(self.symbolsReceived))
self.expectedSymbolsReceived = list(set(self.expectedSymbolsReceived))
if not self.onDataReached:
raise AssertionError("OnData() was never called.")
if len(self.symbolsReceived) != len(self.expectedSymbolsReceived):
raise AssertionError(f"Expected {len(self.expectedSymbolsReceived)} option contracts Symbols, found {len(self.symbolsReceived)}")
missingSymbols = [expectedSymbol for expectedSymbol in self.expectedSymbolsReceived if expectedSymbol not in self.symbolsReceived]
if any(missingSymbols):
raise AssertionError(f'Symbols: "{", ".join(missingSymbols)}" were not found in OnData')
for expectedSymbol in self.expectedSymbolsReceived:
data = self.dataReceived[expectedSymbol]
for dataPoint in data:
dataPoint.EndTime = datetime(1970, 1, 1)
nonDupeDataCount = len(set(data))
if nonDupeDataCount < 1000:
raise AssertionError(f"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}")

View File

@@ -0,0 +1,127 @@
# 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 datetime import datetime, timedelta
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import *
### <summary>
### This regression algorithm tests that we only receive the option chain for a single future contract
### in the option universe filter.
### </summary>
class AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.invested = False
self.onDataReached = False
self.optionFilterRan = False
self.symbolsReceived = []
self.expectedSymbolsReceived = []
self.dataReceived = {}
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 1, 6)
self.es = self.AddFuture(Futures.Indices.SP500EMini, Resolution.Minute, Market.CME)
self.es.SetFilter(lambda futureFilter: futureFilter.Expiration(0, 365).ExpirationCycle([3, 6]))
self.AddFutureOption(self.es.Symbol, self.OptionContractUniverseFilterFunction)
def OptionContractUniverseFilterFunction(self, optionContracts: OptionFilterUniverse) -> OptionFilterUniverse:
self.optionFilterRan = True
expiry = list(set([x.Underlying.ID.Date for x in optionContracts]))
expiry = None if not any(expiry) else expiry[0]
symbol = [x.Underlying for x in optionContracts]
symbol = None if not any(symbol) else symbol[0]
if expiry is None or symbol is None:
raise AssertionError("Expected a single Option contract in the chain, found 0 contracts")
enumerator = optionContracts.GetEnumerator()
while enumerator.MoveNext():
self.expectedSymbolsReceived.append(enumerator.Current)
return optionContracts
def OnData(self, data: Slice):
if not data.HasData:
return
self.onDataReached = True
hasOptionQuoteBars = False
for qb in data.QuoteBars.Values:
if qb.Symbol.SecurityType != SecurityType.FutureOption:
continue
hasOptionQuoteBars = True
self.symbolsReceived.append(qb.Symbol)
if qb.Symbol not in self.dataReceived:
self.dataReceived[qb.Symbol] = []
self.dataReceived[qb.Symbol].append(qb)
if self.invested or not hasOptionQuoteBars:
return
for chain in data.OptionChains.Values:
futureInvested = False
optionInvested = False
for option in chain.Contracts.Keys:
if futureInvested and optionInvested:
return
future = option.Underlying
if not optionInvested and data.ContainsKey(option):
self.MarketOrder(option, 1)
self.invested = True
optionInvested = True
if not futureInvested and data.ContainsKey(future):
self.MarketOrder(future, 1)
self.invested = True
futureInvested = True
def OnEndOfAlgorithm(self):
super().OnEndOfAlgorithm()
self.symbolsReceived = list(set(self.symbolsReceived))
self.expectedSymbolsReceived = list(set(self.expectedSymbolsReceived))
if not self.optionFilterRan:
raise AssertionError("Option chain filter was never ran")
if not self.onDataReached:
raise AssertionError("OnData() was never called.")
if len(self.symbolsReceived) != len(self.expectedSymbolsReceived):
raise AssertionError(f"Expected {len(self.expectedSymbolsReceived)} option contracts Symbols, found {len(self.symbolsReceived)}")
missingSymbols = [expectedSymbol for expectedSymbol in self.expectedSymbolsReceived if expectedSymbol not in self.symbolsReceived]
if any(missingSymbols):
raise AssertionError(f'Symbols: "{", ".join(missingSymbols)}" were not found in OnData')
for expectedSymbol in self.expectedSymbolsReceived:
data = self.dataReceived[expectedSymbol]
for dataPoint in data:
dataPoint.EndTime = datetime(1970, 1, 1)
nonDupeDataCount = len(set(data))
if nonDupeDataCount < 1000:
raise AssertionError(f"Received too few data points. Expected >=1000, found {nonDupeDataCount} for {expectedSymbol}")

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
from datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests In The Money (ITM) future option calls across different strike prices.
### We expect 6 orders from the algorithm, which are:
###
### * (1) Initial entry, buy ES Call Option (ES19M20 expiring ITM)
### * (2) Initial entry, sell ES Call Option at different strike (ES20H20 expiring ITM)
### * [2] Option assignment, opens a position in the underlying (ES20H20, Qty: -1)
### * [2] Future contract liquidation, due to impending expiry
### * [1] Option exercise, receive 1 ES19M20 future contract
### * [1] Liquidate ES19M20 contract, due to expiry
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
class FutureOptionBuySellCallIntradayRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es20h20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 3, 20)
),
Resolution.Minute).Symbol
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)
),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOptions = [
self.AddFutureOptionContract(i, Resolution.Minute).Symbol for i in (self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) + self.OptionChainProvider.GetOptionContractList(self.es20h20, self.Time)) if i.ID.StrikePrice == 3200.0 and i.ID.OptionRight == OptionRight.Call
]
self.expectedContracts = [
Symbol.CreateOption(self.es20h20, Market.CME, OptionStyle.American, OptionRight.Call, 3200.0, datetime(2020, 3, 20)),
Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200.0, datetime(2020, 6, 19))
]
for esOption in self.esOptions:
if esOption not in self.expectedContracts:
raise AssertionError(f"Contract {esOption} was not found in the chain")
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduleCallbackBuy)
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.Noon, self.ScheduleCallbackLiquidate)
def ScheduleCallbackBuy(self):
self.MarketOrder(self.esOptions[0], 1)
self.MarketOrder(self.esOptions[1], -1)
def ScheduleCallbackLiquidate(self):
self.Liquidate()
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

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
from datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests In The Money (ITM) future option expiry for calls.
### We expect 3 orders from the algorithm, which are:
###
### * Initial entry, buy ES Call Option (expiring ITM)
### * Option exercise, receiving ES future contracts
### * Future contract liquidation, due to impending expiry
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
class FutureOptionCallITMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)
),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted([x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3200.0 and x.ID.OptionRight == OptionRight.Call], key=lambda x: x.ID.StrikePrice, reverse=True)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3200.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain")
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduleCallback)
def ScheduleCallback(self):
self.MarketOrder(self.esOption, 1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}")
elif delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}")
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
elif security.Symbol == self.expectedContract:
# Expected contract is ES19H21 Call Option expiring ITM @ 3250
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{self.Time} -- {orderEvent.Symbol} :: Price: {self.Securities[orderEvent.Symbol].Holdings.Price} Qty: {self.Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}")
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
# We expect the liquidation to occur on the day of the delisting (while the market is open),
# but currently we liquidate at the next market open (AAPL open) which happens to be
# at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
# market open.
# Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
expectedLiquidationTimeUtc = datetime(2020, 6, 22, 13, 32, 0)
if orderEvent.Direction == OrderDirection.Sell and future.Holdings.Quantity != 0:
# We expect the contract to have been liquidated immediately
raise AssertionError(f"Did not liquidate existing holdings for Symbol {future.Symbol}")
if orderEvent.Direction == OrderDirection.Sell and orderEvent.UtcTime.replace(tzinfo=None) != expectedLiquidationTimeUtc:
raise AssertionError(f"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc} - found {orderEvent.UtcTime.replace(tzinfo=None)}");
# No way to detect option exercise orders or any other kind of special orders
# other than matching strings, for now.
if "Option Exercise" in orderEvent.Message:
if orderEvent.FillPrice != 3200.0:
raise AssertionError("Option did not exercise at expected strike price (3200)")
if future.Holdings.Quantity != 1:
# Here, we expect to have some holdings in the underlying, but not in the future option anymore.
raise AssertionError(f"Exercised option contract, but we have no holdings for Future {future.Symbol}")
if optionContract.Holdings.Quantity != 0:
raise AssertionError(f"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}")
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
raise AssertionError(f"No holdings were created for option contract {option.Symbol}")
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
raise AssertionError(f"Holdings were found after a filled option exercise")
if "Exercise" in orderEvent.Message and option.Holdings.Quantity != 0:
raise AssertionError(f"Holdings were found after exercising option contract {option.Symbol}")
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

View File

@@ -0,0 +1,127 @@
# 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 datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests Out of The Money (OTM) future option expiry for calls.
### We expect 1 order from the algorithm, which are:
###
### * Initial entry, buy ES Call Option (expiring OTM)
### - contract expires worthless, not exercised, so never opened a position in the underlying
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
### <remarks>
### Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
### See related issue: https://github.com/QuantConnect/Lean/issues/4854
### </remarks>
class FutureOptionCallOTMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted(
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice >= 3300.0 and x.ID.OptionRight == OptionRight.Call],
key=lambda x: x.ID.StrikePrice
)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3300.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
def ScheduledMarketOrder(self):
self.MarketOrder(self.esOption, 1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
if delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
raise AssertionError("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM")
# Expected contract is ES19M20 Call Option expiring OTM @ 3300
if (security.Symbol == self.expectedContract):
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{orderEvent}");
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
raise AssertionError("Holdings were found after a filled option exercise");
if orderEvent.Direction == OrderDirection.Sell and "OTM" not in orderEvent.Message:
raise AssertionError("Contract did not expire OTM");
if "Exercise" in orderEvent.Message:
raise AssertionError("Exercised option, even though it expires OTM");
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

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
from datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests In The Money (ITM) future option expiry for puts.
### We expect 3 orders from the algorithm, which are:
###
### * Initial entry, buy ES Put Option (expiring ITM) (buy, qty 1)
### * Option exercise, receiving short ES future contracts (sell, qty -1)
### * Future contract liquidation, due to impending expiry (buy qty 1)
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
class FutureOptionPutITMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)
),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted([x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice >= 3300.0 and x.ID.OptionRight == OptionRight.Put], key=lambda x: x.ID.StrikePrice)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3300.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain")
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduleCallback)
def ScheduleCallback(self):
self.MarketOrder(self.esOption, 1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}")
elif delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}")
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
elif security.Symbol == self.expectedContract:
# Expected contract is ES19M20 Call Option expiring ITM @ 3250
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{self.Time} -- {orderEvent.Symbol} :: Price: {self.Securities[orderEvent.Symbol].Holdings.Price} Qty: {self.Securities[orderEvent.Symbol].Holdings.Quantity} Direction: {orderEvent.Direction} Msg: {orderEvent.Message}")
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
# We expect the liquidation to occur on the day of the delisting (while the market is open),
# but currently we liquidate at the next market open (AAPL open) which happens to be
# at 9:30:00 Eastern Time. For unknown reasons, the delisting happens two minutes after the
# market open.
# Read more about the issue affecting this test here: https://github.com/QuantConnect/Lean/issues/4980
expectedLiquidationTimeUtc = datetime(2020, 6, 22, 13, 32, 0)
if orderEvent.Direction == OrderDirection.Buy and future.Holdings.Quantity != 0:
# We expect the contract to have been liquidated immediately
raise AssertionError(f"Did not liquidate existing holdings for Symbol {future.Symbol}")
if orderEvent.Direction == OrderDirection.Buy and orderEvent.UtcTime.replace(tzinfo=None) != expectedLiquidationTimeUtc:
raise AssertionError(f"Liquidated future contract, but not at the expected time. Expected: {expectedLiquidationTimeUtc} - found {orderEvent.UtcTime.replace(tzinfo=None)}");
# No way to detect option exercise orders or any other kind of special orders
# other than matching strings, for now.
if "Option Exercise" in orderEvent.Message:
if orderEvent.FillPrice != 3300.0:
raise AssertionError("Option did not exercise at expected strike price (3300)")
if future.Holdings.Quantity != -1:
# Here, we expect to have some holdings in the underlying, but not in the future option anymore.
raise AssertionError(f"Exercised option contract, but we have no holdings for Future {future.Symbol}")
if optionContract.Holdings.Quantity != 0:
raise AssertionError(f"Exercised option contract, but we have holdings for Option contract {optionContract.Symbol}")
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
raise AssertionError(f"No holdings were created for option contract {option.Symbol}")
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
raise AssertionError(f"Holdings were found after a filled option exercise")
if "Exercise" in orderEvent.Message and option.Holdings.Quantity != 0:
raise AssertionError(f"Holdings were found after exercising option contract {option.Symbol}")
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

View File

@@ -0,0 +1,127 @@
# 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 datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests Out of The Money (OTM) future option expiry for puts.
### We expect 1 order from the algorithm, which are:
###
### * Initial entry, buy ES Put Option (expiring OTM)
### - contract expires worthless, not exercised, so never opened a position in the underlying
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
### <remarks>
### Total Trades in regression algorithm should be 1, but expiration is counted as a trade.
### </remarks>
class FutureOptionPutOTMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted(
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3150.0 and x.ID.OptionRight == OptionRight.Put],
key=lambda x: x.ID.StrikePrice,
reverse=True
)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3150.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
def ScheduledMarketOrder(self):
self.MarketOrder(self.esOption, 1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
if delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
raise AssertionError("Invalid state: did not expect a position for the underlying to be opened, since this contract expires OTM")
# Expected contract is ES19M20 Put Option expiring OTM @ 3200
if (security.Symbol == self.expectedContract):
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{orderEvent}");
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
if orderEvent.Direction == OrderDirection.Buy and option.Holdings.Quantity != 1:
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != 0:
raise AssertionError("Holdings were found after a filled option exercise");
if orderEvent.Direction == OrderDirection.Sell and "OTM" not in orderEvent.Message:
raise AssertionError("Contract did not expire OTM");
if "Exercise" in orderEvent.Message:
raise AssertionError("Exercised option, even though it expires OTM");
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

View File

@@ -0,0 +1,132 @@
# 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 datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests In The Money (ITM) future option expiry for short calls.
### We expect 3 orders from the algorithm, which are:
###
### * Initial entry, sell ES Call Option (expiring ITM)
### * Option assignment, sell 1 contract of the underlying (ES)
### * Future contract expiry, liquidation (buy 1 ES future)
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
class FutureOptionShortCallITMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted(
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3100.0 and x.ID.OptionRight == OptionRight.Call],
key=lambda x: x.ID.StrikePrice,
reverse=True
)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3100.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
def ScheduledMarketOrder(self):
self.MarketOrder(self.esOption, -1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
if delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
elif security.Symbol == self.expectedContract:
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{orderEvent}");
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
if "Assignment" in orderEvent.Message:
if orderEvent.FillPrice != 3100.0:
raise AssertionError("Option was not assigned at expected strike price (3100)")
if orderEvent.Direction != OrderDirection.Sell or future.Holdings.Quantity != -1:
raise AssertionError(f"Expected Qty: -1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}")
return
if orderEvent.Direction == OrderDirection.Buy and future.Holdings.Quantity != 0:
# We buy back the underlying at expiration, so we expect a neutral position then
raise AssertionError(f"Expected no holdings when liquidating future contract {future.Symbol}")
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != -1:
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
if orderEvent.IsAssignment and option.Holdings.Quantity != 0:
raise AssertionError(f"Holdings were found after option contract was assigned: {option.Symbol}")
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

View File

@@ -0,0 +1,119 @@
# 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 datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests Out of The Money (OTM) future option expiry for short calls.
### We expect 1 order from the algorithm, which are:
###
### * Initial entry, sell ES Call Option (expiring OTM)
### - Profit the option premium, since the option was not assigned.
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
class FutureOptionShortCallOTMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted(
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice >= 3400.0 and x.ID.OptionRight == OptionRight.Call],
key=lambda x: x.ID.StrikePrice
)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Call, 3400.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
def ScheduledMarketOrder(self):
self.MarketOrder(self.esOption, -1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
if delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
raise AssertionError(f"Expected no order events for underlying Symbol {security.Symbol}")
if security.Symbol == self.expectedContract:
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{orderEvent}");
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, optionContract: Security):
if orderEvent.Direction == OrderDirection.Sell and optionContract.Holdings.Quantity != -1:
raise AssertionError(f"No holdings were created for option contract {optionContract.Symbol}")
if orderEvent.Direction == OrderDirection.Buy and optionContract.Holdings.Quantity != 0:
raise AssertionError("Expected no options holdings after closing position")
if orderEvent.IsAssignment:
raise AssertionError(f"Assignment was not expected for {orderEvent.Symbol}")
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

View File

@@ -0,0 +1,132 @@
# 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 datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests In The Money (ITM) future option expiry for short puts.
### We expect 3 orders from the algorithm, which are:
###
### * Initial entry, sell ES Put Option (expiring ITM)
### * Option assignment, buy 1 contract of the underlying (ES)
### * Future contract expiry, liquidation (sell 1 ES future)
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
class FutureOptionShortPutITMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted(
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3400.0 and x.ID.OptionRight == OptionRight.Put],
key=lambda x: x.ID.StrikePrice,
reverse=True
)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3400.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
def ScheduledMarketOrder(self):
self.MarketOrder(self.esOption, -1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
if delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
self.AssertFutureOptionOrderExercise(orderEvent, security, self.Securities[self.expectedContract])
elif security.Symbol == self.expectedContract:
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{orderEvent}");
def AssertFutureOptionOrderExercise(self, orderEvent: OrderEvent, future: Security, optionContract: Security):
if "Assignment" in orderEvent.Message:
if orderEvent.FillPrice != 3400.0:
raise AssertionError("Option was not assigned at expected strike price (3400)")
if orderEvent.Direction != OrderDirection.Buy or future.Holdings.Quantity != 1:
raise AssertionError(f"Expected Qty: 1 futures holdings for assigned future {future.Symbol}, found {future.Holdings.Quantity}")
return
if orderEvent.Direction == OrderDirection.Sell and future.Holdings.Quantity != 0:
# We buy back the underlying at expiration, so we expect a neutral position then
raise AssertionError(f"Expected no holdings when liquidating future contract {future.Symbol}")
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, option: Security):
if orderEvent.Direction == OrderDirection.Sell and option.Holdings.Quantity != -1:
raise AssertionError(f"No holdings were created for option contract {option.Symbol}");
if orderEvent.IsAssignment and option.Holdings.Quantity != 0:
raise AssertionError(f"Holdings were found after option contract was assigned: {option.Symbol}")
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

View File

@@ -0,0 +1,120 @@
# 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 datetime import datetime, timedelta
import clr
from System import *
from System.Reflection import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Orders import *
from QuantConnect.Securities import *
from QuantConnect.Securities.Future import *
from QuantConnect import Market
### <summary>
### This regression algorithm tests Out of The Money (OTM) future option expiry for short puts.
### We expect 1 order from the algorithm, which are:
###
### * Initial entry, sell ES Put Option (expiring OTM)
### - Profit the option premium, since the option was not assigned.
###
### Additionally, we test delistings for future options and assert that our
### portfolio holdings reflect the orders the algorithm has submitted.
### </summary>
class FutureOptionShortPutOTMExpiryRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 1, 5)
self.SetEndDate(2020, 6, 30)
# We add AAPL as a temporary workaround for https://github.com/QuantConnect/Lean/issues/4872
# which causes delisting events to never be processed, thus leading to options that might never
# be exercised until the next data point arrives.
self.AddEquity("AAPL", Resolution.Daily)
self.es19m20 = self.AddFutureContract(
Symbol.CreateFuture(
Futures.Indices.SP500EMini,
Market.CME,
datetime(2020, 6, 19)),
Resolution.Minute).Symbol
# Select a future option expiring ITM, and adds it to the algorithm.
self.esOption = self.AddFutureOptionContract(
list(
sorted(
[x for x in self.OptionChainProvider.GetOptionContractList(self.es19m20, self.Time) if x.ID.StrikePrice <= 3000.0 and x.ID.OptionRight == OptionRight.Put],
key=lambda x: x.ID.StrikePrice,
reverse=True
)
)[0], Resolution.Minute).Symbol
self.expectedContract = Symbol.CreateOption(self.es19m20, Market.CME, OptionStyle.American, OptionRight.Put, 3000.0, datetime(2020, 6, 19))
if self.esOption != self.expectedContract:
raise AssertionError(f"Contract {self.expectedContract} was not found in the chain");
self.Schedule.On(self.DateRules.Tomorrow, self.TimeRules.AfterMarketOpen(self.es19m20, 1), self.ScheduledMarketOrder)
def ScheduledMarketOrder(self):
self.MarketOrder(self.esOption, -1)
def OnData(self, data: Slice):
# Assert delistings, so that we can make sure that we receive the delisting warnings at
# the expected time. These assertions detect bug #4872
for delisting in data.Delistings.Values:
if delisting.Type == DelistingType.Warning:
if delisting.Time != datetime(2020, 6, 19):
raise AssertionError(f"Delisting warning issued at unexpected date: {delisting.Time}");
if delisting.Type == DelistingType.Delisted:
if delisting.Time != datetime(2020, 6, 20):
raise AssertionError(f"Delisting happened at unexpected date: {delisting.Time}");
def OnOrderEvent(self, orderEvent: OrderEvent):
if orderEvent.Status != OrderStatus.Filled:
# There's lots of noise with OnOrderEvent, but we're only interested in fills.
return
if not self.Securities.ContainsKey(orderEvent.Symbol):
raise AssertionError(f"Order event Symbol not found in Securities collection: {orderEvent.Symbol}")
security = self.Securities[orderEvent.Symbol]
if security.Symbol == self.es19m20:
raise AssertionError(f"Expected no order events for underlying Symbol {security.Symbol}")
if security.Symbol == self.expectedContract:
self.AssertFutureOptionContractOrder(orderEvent, security)
else:
raise AssertionError(f"Received order event for unknown Symbol: {orderEvent.Symbol}")
self.Log(f"{orderEvent}");
def AssertFutureOptionContractOrder(self, orderEvent: OrderEvent, optionContract: Security):
if orderEvent.Direction == OrderDirection.Sell and optionContract.Holdings.Quantity != -1:
raise AssertionError(f"No holdings were created for option contract {optionContract.Symbol}")
if orderEvent.Direction == OrderDirection.Buy and optionContract.Holdings.Quantity != 0:
raise AssertionError("Expected no options holdings after closing position")
if orderEvent.IsAssignment:
raise AssertionError(f"Assignment was not expected for {orderEvent.Symbol}")
def OnEndOfAlgorithm(self):
if self.Portfolio.Invested:
raise AssertionError(f"Expected no holdings at end of algorithm, but are invested in: {', '.join([str(i.ID) for i in self.Portfolio.Keys])}")

View File

@@ -46,6 +46,8 @@
<ItemGroup>
<Content Include="AccumulativeInsightPortfolioRegressionAlgorithm.py" />
<Content Include="AddAlphaModelAlgorithm.py" />
<Content Include="AddFutureOptionContractDataStreamingRegressionAlgorithm.py" />
<Content Include="AddFutureOptionSingleOptionChainSelectedInUniverseFilterRegressionAlgorithm.py" />
<Content Include="AddOptionContractExpiresRegressionAlgorithm.py" />
<Content Include="AddOptionContractFromUniverseRegressionAlgorithm.py" />
<Content Include="AddRiskManagementAlgorithm.py" />
@@ -98,6 +100,15 @@
<Content Include="ExtendedMarketTradingRegressionAlgorithm.py" />
<Content Include="FilterUniverseRegressionAlgorithm.py" />
<Content Include="FineFundamentalFilteredUniverseRegressionAlgorithm.py" />
<Content Include="FutureOptionBuySellCallIntradayRegressionAlgorithm.py" />
<Content Include="FutureOptionCallITMExpiryRegressionAlgorithm.py" />
<Content Include="FutureOptionCallOTMExpiryRegressionAlgorithm.py" />
<Content Include="FutureOptionPutITMExpiryRegressionAlgorithm.py" />
<Content Include="FutureOptionPutOTMExpiryRegressionAlgorithm.py" />
<Content Include="FutureOptionShortCallITMExpiryRegressionAlgorithm.py" />
<Content Include="FutureOptionShortCallOTMExpiryRegressionAlgorithm.py" />
<Content Include="FutureOptionShortPutITMExpiryRegressionAlgorithm.py" />
<Content Include="FutureOptionShortPutOTMExpiryRegressionAlgorithm.py" />
<Content Include="KerasNeuralNetworkAlgorithm.py" />
<Content Include="CustomDataUsingMapFileRegressionAlgorithm.py" />
<Content Include="LiquidETFUniverseFrameworkAlgorithm.py" />

View File

@@ -33,13 +33,15 @@ Before we enable python support, follow the [installation instructions](https://
- Value of the variable: python installation path.
4. Install [pandas=0.25.3](https://pandas.pydata.org/) and its [dependencies](https://pandas.pydata.org/pandas-docs/stable/install.html#dependencies).
5. Install [wrapt=1.11.2](https://pypi.org/project/wrapt/) module.
6. Reboot computer to ensure changes are propogated.
6. Install [pyarrow=1.0.1](https://arrow.apache.org/install/) module.
7. Reboot computer to ensure changes are propagated.
#### [macOS](https://github.com/QuantConnect/Lean#macos)
1. Use the macOS x86-64 package installer from [Anaconda](https://repo.anaconda.com/archive/Anaconda3-5.2.0-MacOSX-x86_64.pkg) and follow "[Installing on macOS](https://docs.anaconda.com/anaconda/install/mac-os)" instructions from Anaconda documentation page.
2. Install [pandas=0.25.3](https://pandas.pydata.org/) and its [dependencies](https://pandas.pydata.org/pandas-docs/stable/install.html#dependencies).
3. Install [wrapt=1.11.2](https://pypi.org/project/wrapt/) module.
4. Install [pyarrow=1.0.1](https://arrow.apache.org/install/) 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 found at
@@ -64,6 +66,7 @@ conda update -y python conda pip
conda install -y cython=0.29.11
conda install -y pandas=0.25.3
conda install -y wrapt=1.11.2
pip install pyarrow==1.0.1
```
*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:

View File

@@ -51,9 +51,7 @@ namespace QuantConnect.Algorithm
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
public void SetWarmup(TimeSpan timeSpan)
{
_warmupBarCount = null;
_warmupTimeSpan = timeSpan;
_warmupResolution = null;
SetWarmUp(timeSpan, null);
}
/// <summary>
@@ -70,8 +68,13 @@ namespace QuantConnect.Algorithm
/// </summary>
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
/// <param name="resolution">The resolution to request</param>
public void SetWarmup(TimeSpan timeSpan, Resolution resolution)
public void SetWarmup(TimeSpan timeSpan, Resolution? resolution)
{
if (_locked)
{
throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized");
}
_warmupBarCount = null;
_warmupTimeSpan = timeSpan;
_warmupResolution = resolution;
@@ -82,7 +85,7 @@ namespace QuantConnect.Algorithm
/// </summary>
/// <param name="timeSpan">The amount of time to warm up, this does not take into account market hours/weekends</param>
/// <param name="resolution">The resolution to request</param>
public void SetWarmUp(TimeSpan timeSpan, Resolution resolution)
public void SetWarmUp(TimeSpan timeSpan, Resolution? resolution)
{
SetWarmup(timeSpan, resolution);
}
@@ -96,9 +99,7 @@ namespace QuantConnect.Algorithm
/// <param name="barCount">The number of data points requested for warm up</param>
public void SetWarmup(int barCount)
{
_warmupTimeSpan = null;
_warmupBarCount = barCount;
_warmupResolution = null;
SetWarmUp(barCount, null);
}
/// <summary>
@@ -119,8 +120,13 @@ namespace QuantConnect.Algorithm
/// </summary>
/// <param name="barCount">The number of data points requested for warm up</param>
/// <param name="resolution">The resolution to request</param>
public void SetWarmup(int barCount, Resolution resolution)
public void SetWarmup(int barCount, Resolution? resolution)
{
if (_locked)
{
throw new InvalidOperationException("QCAlgorithm.SetWarmup(): This method cannot be used after algorithm initialized");
}
_warmupTimeSpan = null;
_warmupBarCount = barCount;
_warmupResolution = resolution;
@@ -132,7 +138,7 @@ namespace QuantConnect.Algorithm
/// </summary>
/// <param name="barCount">The number of data points requested for warm up</param>
/// <param name="resolution">The resolution to request</param>
public void SetWarmUp(int barCount, Resolution resolution)
public void SetWarmUp(int barCount, Resolution? resolution)
{
SetWarmup(barCount, resolution);
}
@@ -492,9 +498,9 @@ namespace QuantConnect.Algorithm
var resolution = (Resolution)Math.Max((int)Resolution.Minute, (int)configs.GetHighestResolution());
var isExtendedMarketHours = configs.IsExtendedMarketHours();
// request QuoteBar for Options and Futures
// request QuoteBar for Options, Futures, and Futures Options
var dataType = typeof(BaseData);
if (security.Type == SecurityType.Option || security.Type == SecurityType.Future)
if (security.Type == SecurityType.Option || security.Type == SecurityType.Future || security.Type == SecurityType.FutureOption)
{
dataType = LeanData.GetDataType(resolution, TickType.Quote);
}
@@ -728,4 +734,4 @@ namespace QuantConnect.Algorithm
}
}
}
}
}

View File

@@ -188,6 +188,24 @@ namespace QuantConnect.Algorithm
return AddDataImpl(dataType, symbol, resolution, timeZone, fillDataForward, leverage);
}
/// <summary>
/// Creates and adds a new Future Option contract to the algorithm.
/// </summary>
/// <param name="symbol">The <see cref="Future"/> canonical symbol (i.e. Symbol returned from <see cref="AddFuture"/>)</param>
/// <param name="optionFilter">Filter to apply to option contracts loaded as part of the universe</param>
/// <returns>The new <see cref="Option"/> security, containing a <see cref="Future"/> as its underlying.</returns>
/// <exception cref="ArgumentException">The symbol provided is not canonical.</exception>
public void AddFutureOption(Symbol futureSymbol, PyObject optionFilter)
{
Func<OptionFilterUniverse, OptionFilterUniverse> optionFilterUniverse;
if (!optionFilter.TryConvertToDelegate(out optionFilterUniverse))
{
throw new ArgumentException("Option contract universe filter provided is not a function");
}
AddFutureOption(futureSymbol, optionFilterUniverse);
}
/// <summary>
/// Adds the provided final Symbol with/without underlying set to the algorithm.
/// This method is meant for custom data types that require a ticker, but have no underlying Symbol.
@@ -517,7 +535,7 @@ namespace QuantConnect.Algorithm
catch
{
}
}
// Finally, since above didn't work, just try it as a timespan
// Issue #4668 Fix
@@ -532,7 +550,7 @@ namespace QuantConnect.Algorithm
RegisterIndicator(symbol, indicator, timeSpan, selector);
}
}
catch
catch
{
throw new ArgumentException("Invalid third argument, should be either a valid consolidator or timedelta object");
}
@@ -1216,4 +1234,4 @@ namespace QuantConnect.Algorithm
return pythonIndicator;
}
}
}
}

View File

@@ -559,39 +559,43 @@ namespace QuantConnect.Algorithm
var orders = new List<OrderTicket>();
// setting up the tag text for all orders of one strategy
var strategyTag = $"{strategy.Name} ({strategyQuantity.ToStringInvariant()})";
var tag = $"{strategy.Name} ({strategyQuantity.ToStringInvariant()})";
// walking through all option legs and issuing orders
if (strategy.OptionLegs != null)
{
var underlying = strategy.Underlying;
foreach (var optionLeg in strategy.OptionLegs)
{
var optionSeq = Securities.Where(kv => kv.Key.Underlying == strategy.Underlying &&
kv.Key.ID.OptionRight == optionLeg.Right &&
kv.Key.ID.Date == optionLeg.Expiration &&
kv.Key.ID.StrikePrice == optionLeg.Strike);
// search for both american/european style -- much better than looping through all securities
var american = QuantConnect.Symbol.CreateOption(underlying, underlying.ID.Market,
OptionStyle.American, optionLeg.Right, optionLeg.Strike, optionLeg.Expiration);
if (optionSeq.Count() != 1)
var european = QuantConnect.Symbol.CreateOption(underlying, underlying.ID.Market,
OptionStyle.European, optionLeg.Right, optionLeg.Strike, optionLeg.Expiration);
Security contract;
if (!Securities.TryGetValue(american, out contract) && !Securities.TryGetValue(european, out contract))
{
throw new InvalidOperationException("Couldn't find the option contract in algorithm securities list. " +
Invariant($"Underlying: {strategy.Underlying}, option {optionLeg.Right}, strike {optionLeg.Strike}, ") +
Invariant($"expiration: {optionLeg.Expiration}"));
Invariant($"expiration: {optionLeg.Expiration}")
);
}
var option = optionSeq.First().Key;
var orderQuantity = optionLeg.Quantity * strategyQuantity;
switch (optionLeg.OrderType)
{
case OrderType.Market:
var marketOrder = MarketOrder(option, optionLeg.Quantity * strategyQuantity, tag: strategyTag);
orders.Add(marketOrder);
orders.Add(MarketOrder(contract.Symbol, orderQuantity, tag: tag));
break;
case OrderType.Limit:
var limitOrder = LimitOrder(option, optionLeg.Quantity * strategyQuantity, optionLeg.OrderPrice, tag: strategyTag);
orders.Add(limitOrder);
orders.Add(LimitOrder(contract.Symbol, orderQuantity, optionLeg.OrderPrice, tag));
break;
default:
throw new InvalidOperationException("Order type is not supported in option strategy: " + optionLeg.OrderType.ToString());
throw new InvalidOperationException(Invariant($"Order type is not supported in option strategy: {optionLeg.OrderType}"));
}
}
}
@@ -603,25 +607,28 @@ namespace QuantConnect.Algorithm
{
if (!Securities.ContainsKey(strategy.Underlying))
{
var error = $"Couldn't find the option contract underlying in algorithm securities list. Underlying: {strategy.Underlying}";
throw new InvalidOperationException(error);
throw new InvalidOperationException(
$"Couldn't find the option contract underlying in algorithm securities list. Underlying: {strategy.Underlying}"
);
}
var orderQuantity = underlyingLeg.Quantity * strategyQuantity;
switch (underlyingLeg.OrderType)
{
case OrderType.Market:
var marketOrder = MarketOrder(strategy.Underlying, underlyingLeg.Quantity * strategyQuantity, tag: strategyTag);
orders.Add(marketOrder);
orders.Add(MarketOrder(strategy.Underlying, orderQuantity, tag: tag));
break;
case OrderType.Limit:
var limitOrder = LimitOrder(strategy.Underlying, underlyingLeg.Quantity * strategyQuantity, underlyingLeg.OrderPrice, tag: strategyTag);
orders.Add(limitOrder);
orders.Add(LimitOrder(strategy.Underlying, orderQuantity, underlyingLeg.OrderPrice, tag));
break;
default:
throw new InvalidOperationException("Order type is not supported in option strategy: " + underlyingLeg.OrderType.ToString());
throw new InvalidOperationException(Invariant($"Order type is not supported in option strategy: {underlyingLeg.OrderType}"));
}
}
}
return orders;
}
@@ -763,7 +770,7 @@ namespace QuantConnect.Algorithm
if (request.OrderType == OrderType.OptionExercise)
{
if (security.Type != SecurityType.Option)
if (security.Type != SecurityType.Option && security.Type != SecurityType.FutureOption)
{
return OrderResponse.Error(request, OrderResponseErrorCode.NonExercisableSecurity,
$"The security with symbol '{request.Symbol}' is not exercisable."

View File

@@ -21,6 +21,7 @@ using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Securities.Future;
using QuantConnect.Util;
namespace QuantConnect.Algorithm
@@ -451,6 +452,42 @@ namespace QuantConnect.Algorithm
return AddUniverse(new UserDefinedUniverse(config, universeSettings, resolution.ToTimeSpan(), selector));
}
/// <summary>
/// Adds a new universe that creates options of the security by monitoring any changes in the Universe the provided security is in.
/// Additionally, a filter can be applied to the options generated when the universe of the security changes.
/// </summary>
/// <param name="underlyingSymbol">Underlying Symbol to add as an option. For Futures, the option chain constructed will be per-contract, as long as a canonical Symbol is provided.</param>
/// <param name="optionFilter">User-defined filter used to select the options we want out of the option chain provided.</param>
/// <exception cref="InvalidOperationException">The underlying Symbol's universe is not found.</exception>
public void AddUniverseOptions(Symbol underlyingSymbol, Func<OptionFilterUniverse, OptionFilterUniverse> optionFilter)
{
// We need to load the universe associated with the provided Symbol and provide that universe to the option filter universe.
// The option filter universe will subscribe to any changes in the universe of the underlying Symbol,
// ensuring that we load the option chain for every asset found in the underlying's Universe.
Universe universe;
if (!UniverseManager.TryGetValue(underlyingSymbol, out universe))
{
// The universe might be already added, but not registered with the UniverseManager.
universe = _pendingUniverseAdditions.SingleOrDefault(u => u.Configuration.Symbol == underlyingSymbol);
if (universe == null)
{
underlyingSymbol = AddSecurity(underlyingSymbol).Symbol;
}
// Recheck again, we should have a universe addition pending for the provided Symbol
universe = _pendingUniverseAdditions.SingleOrDefault(u => u.Configuration.Symbol == underlyingSymbol);
if (universe == null)
{
// Should never happen, but it could be that the subscription
// created with AddSecurity is not aligned with the Symbol we're using.
throw new InvalidOperationException($"Universe not found for underlying Symbol: {underlyingSymbol}.");
}
}
// Allow all option contracts through without filtering if we're provided a null filter.
AddUniverseOptions(universe, optionFilter ?? (_ => _));
}
/// <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

View File

@@ -1475,13 +1475,13 @@ namespace QuantConnect.Algorithm
/// <param name="fillDataForward">If true, returns the last available data even if none in that timeslice.</param>
/// <param name="leverage">leverage for this security</param>
/// <param name="extendedMarketHours">ExtendedMarketHours send in data from 4am - 8pm, not used for FOREX</param>
/// <returns></returns>
/// <returns>The new Security that was added to the algorithm</returns>
public Security AddSecurity(Symbol symbol, Resolution? resolution = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage, bool extendedMarketHours = false)
{
var isCanonical = symbol.IsCanonical();
// Short-circuit to AddOptionContract because it will add the underlying if required
if (!isCanonical && symbol.SecurityType == SecurityType.Option)
if (!isCanonical && (symbol.SecurityType == SecurityType.Option || symbol.SecurityType == SecurityType.FutureOption))
{
return AddOptionContract(symbol, resolution, fillDataForward, leverage);
}
@@ -1504,7 +1504,7 @@ namespace QuantConnect.Algorithm
if (!UniverseManager.TryGetValue(symbol, out universe) && _pendingUniverseAdditions.All(u => u.Configuration.Symbol != symbol))
{
var settings = new UniverseSettings(configs.First().Resolution, leverage, true, false, TimeSpan.Zero);
if (symbol.SecurityType == SecurityType.Option)
if (symbol.SecurityType == SecurityType.Option || symbol.SecurityType == SecurityType.FutureOption)
{
universe = new OptionChainUniverse((Option)security, settings, LiveMode);
}
@@ -1556,13 +1556,53 @@ namespace QuantConnect.Algorithm
}
}
var underlyingSymbol = QuantConnect.Symbol.Create(underlying, SecurityType.Equity, market);
return AddOption(underlyingSymbol, resolution, market, fillDataForward, leverage);
}
/// <summary>
/// Creates and adds a new <see cref="Option"/> security to the algorithm.
/// This method can be used to add options with non-equity asset classes
/// to the algorithm (e.g. Future Options).
/// </summary>
/// <param name="underlying">Underlying asset Symbol to use as the option's underlying</param>
/// <param name="resolution">The <see cref="Resolution"/> of market data, Tick, Second, Minute, Hour, or Daily. Default is <see cref="Resolution.Minute"/></param>
/// <param name="market">The option's market, <seealso cref="Market"/>. Default value is null, but will be resolved using BrokerageModel.DefaultMarkets in <see cref="AddSecurity{T}"/></param>
/// <param name="fillDataForward">If true, data will be provided to the algorithm every Second, Minute, Hour, or Day, while the asset is open and depending on the Resolution this option was configured to use.</param>
/// <param name="leverage">The requested leverage for the </param>
/// <returns></returns>
/// <exception cref="KeyNotFoundException"></exception>
public Option AddOption(Symbol underlying, Resolution? resolution = null, string market = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage)
{
var optionType = SecurityType.Option;
if (underlying.SecurityType == SecurityType.Future)
{
optionType = SecurityType.FutureOption;
}
if (market == null)
{
if (!BrokerageModel.DefaultMarkets.TryGetValue(optionType, out market))
{
throw new KeyNotFoundException($"No default market set for security type: {optionType}");
}
}
Symbol canonicalSymbol;
var alias = "?" + underlying;
var alias = "?" + underlying.Value;
if (!SymbolCache.TryGetSymbol(alias, out canonicalSymbol) ||
canonicalSymbol.ID.Market != market ||
canonicalSymbol.SecurityType != SecurityType.Option)
(canonicalSymbol.SecurityType != SecurityType.Option &&
canonicalSymbol.SecurityType != SecurityType.FutureOption))
{
canonicalSymbol = QuantConnect.Symbol.Create(underlying, SecurityType.Option, market, alias);
canonicalSymbol = QuantConnect.Symbol.CreateOption(
underlying,
underlying.ID.Market,
default(OptionStyle),
default(OptionRight),
0,
SecurityIdentifier.DefaultDate,
alias);
}
return (Option)AddSecurity(canonicalSymbol, resolution, fillDataForward, leverage);
@@ -1613,6 +1653,42 @@ namespace QuantConnect.Algorithm
return (Future)AddSecurity(symbol, resolution, fillDataForward, leverage);
}
/// <summary>
/// Creates and adds a new Future Option contract to the algorithm.
/// </summary>
/// <param name="symbol">The <see cref="Future"/> canonical symbol (i.e. Symbol returned from <see cref="AddFuture"/>)</param>
/// <param name="optionFilter">Filter to apply to option contracts loaded as part of the universe</param>
/// <returns>The new <see cref="Option"/> security, containing a <see cref="Future"/> as its underlying.</returns>
/// <exception cref="ArgumentException">The symbol provided is not canonical.</exception>
public void AddFutureOption(Symbol symbol, Func<OptionFilterUniverse, OptionFilterUniverse> optionFilter = null)
{
if (!symbol.IsCanonical())
{
throw new ArgumentException("Symbol provided must be canonical (i.e. the Symbol returned from AddFuture(), not AddFutureContract().");
}
AddUniverseOptions(symbol, optionFilter);
}
/// <summary>
/// Adds a future option contract to the algorithm.
/// </summary>
/// <param name="symbol">Option contract Symbol</param>
/// <param name="resolution">Resolution of the option contract, i.e. the granularity of the data</param>
/// <param name="fillDataForward">If true, this will fill in missing data points with the previous data point</param>
/// <param name="leverage">The leverage to apply to the option contract</param>
/// <returns>Option security</returns>
/// <exception cref="ArgumentException">Symbol is canonical (i.e. a generic Symbol returned from <see cref="AddFuture"/> or <see cref="AddOption"/>)</exception>
public Option AddFutureOptionContract(Symbol symbol, Resolution? resolution = null, bool fillDataForward = true, decimal leverage = Security.NullLeverage)
{
if (symbol.IsCanonical())
{
throw new ArgumentException("Expected non-canonical Symbol (i.e. a Symbol representing a specific Future contract");
}
return AddOptionContract(symbol, resolution, fillDataForward, leverage);
}
/// <summary>
/// Creates and adds a new single <see cref="Option"/> contract to the algorithm
/// </summary>
@@ -1625,14 +1701,13 @@ namespace QuantConnect.Algorithm
{
var configs = SubscriptionManager.SubscriptionDataConfigService.Add(symbol, resolution, fillDataForward, dataNormalizationMode:DataNormalizationMode.Raw);
var option = (Option)Securities.CreateSecurity(symbol, configs, leverage);
// add underlying if not present
var underlying = option.Symbol.Underlying;
Security equity;
Security underlyingSecurity;
List<SubscriptionDataConfig> underlyingConfigs;
if (!Securities.TryGetValue(underlying, out equity))
if (!Securities.TryGetValue(underlying, out underlyingSecurity))
{
equity = AddEquity(underlying.Value, resolution, underlying.ID.Market, false);
underlyingSecurity = AddSecurity(underlying, resolution, fillDataForward, leverage);
underlyingConfigs = SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(underlying);
}
@@ -1652,11 +1727,12 @@ namespace QuantConnect.Algorithm
);
}
}
underlyingConfigs.SetDataNormalizationMode(DataNormalizationMode.Raw);
// For backward compatibility we need to refresh the security DataNormalizationMode Property
equity.RefreshDataNormalizationModeProperty();
underlyingSecurity.RefreshDataNormalizationModeProperty();
option.Underlying = equity;
option.Underlying = underlyingSecurity;
Securities.Add(option);
// get or create the universe

View File

@@ -20,6 +20,7 @@ using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Securities.Future;
namespace QuantConnect.Algorithm.Selection
{
@@ -59,8 +60,17 @@ namespace QuantConnect.Algorithm.Selection
// the universe we were watching changed, this will trigger a call to CreateUniverses
_nextRefreshTimeUtc = DateTime.MinValue;
// We must create the new option Symbol using the CreateOption(Symbol, ...) overload.
// Otherwise, we'll end up loading equity data for the selected Symbol, which won't
// work whenever we're loading options data for any non-equity underlying asset class.
_currentSymbols = ((Universe.SelectionEventArgs)args).CurrentSelection
.Select(symbol => Symbol.Create(symbol.Value, SecurityType.Option, symbol.ID.Market, $"?{symbol.Value}"))
.Select(symbol => Symbol.CreateOption(
symbol,
symbol.ID.Market,
default(OptionStyle),
default(OptionRight),
0m,
SecurityIdentifier.DefaultDate))
.ToList();
};
}

View File

@@ -65,7 +65,7 @@ namespace QuantConnect.Algorithm.Selection
_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)
if (removedSymbol.SecurityType == SecurityType.Option || removedSymbol.SecurityType == SecurityType.FutureOption)
{
Remove(removedSymbol.Underlying);
}

View File

@@ -16,7 +16,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -997,5 +996,26 @@ namespace QuantConnect.Api
ApiConnection.TryRequest(request, out result);
return result;
}
/// <summary>
/// Will read the organization account status
/// </summary>
/// <param name="organizationId">The target organization id, if null will return default organization</param>
public Account ReadAccount(string organizationId = null)
{
var request = new RestRequest("account/read/", Method.POST)
{
RequestFormat = DataFormat.Json
};
if (organizationId != null)
{
request.AddParameter("application/json", JsonConvert.SerializeObject(new { organizationId }), ParameterType.RequestBody);
}
Account account;
ApiConnection.TryRequest(request, out account);
return account;
}
}
}

View File

@@ -60,7 +60,7 @@ namespace QuantConnect.Brokerages.Backtesting
algorithm.UtcTime - _lastUpdate > _securitiesRescanPeriod)
{
var expirations = algorithm.Securities.Select(x => x.Key)
.Where(x => x.ID.SecurityType == SecurityType.Option &&
.Where(x => (x.ID.SecurityType == SecurityType.Option || x.ID.SecurityType == SecurityType.FutureOption) &&
x.ID.Date > algorithm.Time &&
x.ID.Date - algorithm.Time <= _securitiesRescanPeriod)
.Select(x => x.ID.Date)
@@ -136,7 +136,7 @@ namespace QuantConnect.Brokerages.Backtesting
algorithm.Securities
// we take only options that expire soon
.Where(x => x.Key.ID.SecurityType == SecurityType.Option &&
.Where(x => (x.Key.ID.SecurityType == SecurityType.Option || x.Key.ID.SecurityType == SecurityType.FutureOption) &&
x.Key.ID.Date - algorithm.UtcTime <= _priorExpiration)
// we look into short positions only (short for user means long for us)
.Where(x => x.Value.Holdings.IsShort)

View File

@@ -37,6 +37,8 @@ using NodaTime;
using QuantConnect.IBAutomater;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.TimeInForces;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.FutureOption;
using QuantConnect.Securities.Option;
using Bar = QuantConnect.Data.Market.Bar;
using HistoryRequest = QuantConnect.Data.HistoryRequest;
@@ -939,7 +941,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
/// <param name="exchange">The exchange to send the order to, defaults to "Smart" to use IB's smart routing</param>
private void IBPlaceOrder(Order order, bool needsNewId, string exchange = null)
{
// MOO/MOC require directed option orders
// MOO/MOC require directed option orders.
// We resolve non-equity markets in the `CreateContract` method.
if (exchange == null &&
order.Symbol.SecurityType == SecurityType.Option &&
(order.Type == OrderType.MarketOnOpen || order.Type == OrderType.MarketOnClose))
@@ -1018,6 +1021,15 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
return details.Contract.TradingClass;
}
if (symbol.SecurityType == SecurityType.FutureOption)
{
// Futures options trading class is the same as the FOP ticker.
// This is required in order to resolve the contract details successfully.
// We let this method complete even though we assign twice so that the
// contract details are added to the cache and won't require another lookup.
contract.TradingClass = symbol.ID.Symbol;
}
details = GetContractDetails(contract, symbol);
if (details == null)
{
@@ -1824,12 +1836,13 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
/// <returns>A new IB contract for the order</returns>
private Contract CreateContract(Symbol symbol, bool includeExpired, string exchange = null)
{
var securityType = ConvertSecurityType(symbol.ID.SecurityType);
var securityType = ConvertSecurityType(symbol.SecurityType);
var ibSymbol = _symbolMapper.GetBrokerageSymbol(symbol);
var contract = new Contract
{
Symbol = ibSymbol,
Exchange = exchange ?? "Smart",
Exchange = exchange ?? GetSymbolExchange(symbol),
SecType = securityType,
Currency = Currencies.USD
};
@@ -1846,18 +1859,23 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
contract.PrimaryExch = GetPrimaryExchange(contract, symbol);
}
if (symbol.ID.SecurityType == SecurityType.Option)
if (symbol.ID.SecurityType == SecurityType.Option || symbol.ID.SecurityType == SecurityType.FutureOption)
{
contract.LastTradeDateOrContractMonth = symbol.ID.Date.ToStringInvariant(DateFormat.EightCharacter);
contract.Right = symbol.ID.OptionRight == OptionRight.Call ? IB.RightType.Call : IB.RightType.Put;
contract.Strike = Convert.ToDouble(symbol.ID.StrikePrice);
contract.Symbol = ibSymbol;
contract.Multiplier = _securityProvider.GetSecurity(symbol)?.SymbolProperties.ContractMultiplier.ToString(CultureInfo.InvariantCulture) ?? "100";
contract.TradingClass = GetTradingClass(contract, symbol);
contract.Multiplier = _symbolPropertiesDatabase.GetSymbolProperties(
symbol.ID.Market,
symbol,
symbol.SecurityType,
_algorithm.Portfolio.CashBook.AccountCurrency)
.ContractMultiplier
.ToStringInvariant();
contract.TradingClass = GetTradingClass(contract, symbol);
contract.IncludeExpired = includeExpired;
}
if (symbol.ID.SecurityType == SecurityType.Future)
{
// we convert Market.* markets into IB exchanges if we have them in our map
@@ -1871,8 +1889,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
symbol.ID.Market,
symbol.ID.Symbol,
SecurityType.Future,
symbol,
symbol.SecurityType,
Currencies.USD);
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
@@ -2087,7 +2105,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
}
/// <summary>
/// Maps SecurityType enum
/// Maps SecurityType enum to an IBApi SecurityType value
/// </summary>
private static string ConvertSecurityType(SecurityType type)
{
@@ -2097,7 +2115,10 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
return IB.SecurityType.Stock;
case SecurityType.Option:
return IB.SecurityType.Option;
return IB.SecurityType.Option;
case SecurityType.FutureOption:
return IB.SecurityType.FutureOption;
case SecurityType.Forex:
return IB.SecurityType.Cash;
@@ -2123,6 +2144,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
case IB.SecurityType.Option:
return SecurityType.Option;
case IB.SecurityType.FutureOption:
return SecurityType.FutureOption;
case IB.SecurityType.Cash:
return SecurityType.Forex;
@@ -2224,21 +2248,38 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var ibSymbol = securityType == SecurityType.Forex ? contract.Symbol + contract.Currency : contract.Symbol;
var market = InteractiveBrokersBrokerageModel.DefaultMarketMap[securityType];
var isFutureOption = contract.SecType == IB.SecurityType.FutureOption;
if (securityType == SecurityType.Future)
// Handle future options as a Future, up until we actually return the future.
if (isFutureOption || securityType == SecurityType.Future)
{
var leanSymbol = _symbolMapper.GetLeanRootSymbol(ibSymbol);
var defaultMarket = market;
if (!_symbolPropertiesDatabase.TryGetMarket(leanSymbol, securityType, out market))
if (!_symbolPropertiesDatabase.TryGetMarket(leanSymbol, SecurityType.Future, out market))
{
market = defaultMarket;
}
var contractDate = DateTime.ParseExact(contract.LastTradeDateOrContractMonth, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
var contractExpiryDate = DateTime.ParseExact(contract.LastTradeDateOrContractMonth, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
return _symbolMapper.GetLeanSymbol(ibSymbol, securityType, market, contractDate);
if (!isFutureOption)
{
return _symbolMapper.GetLeanSymbol(ibSymbol, SecurityType.Future, market, contractExpiryDate);
}
// Create a canonical future Symbol for lookup in the FuturesExpiryFunctions helper class.
// We then get the delta between the futures option's expiry month vs. the future's expiry month.
var canonicalFutureSymbol = _symbolMapper.GetLeanSymbol(ibSymbol, SecurityType.Future, market, SecurityIdentifier.DefaultDate);
var futureExpiryFunction = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol);
var futureContractExpiryDate = futureExpiryFunction(FuturesOptionsExpiryFunctions.GetFutureContractMonth(canonicalFutureSymbol, contractExpiryDate));
var futureSymbol = Symbol.CreateFuture(canonicalFutureSymbol.ID.Symbol, canonicalFutureSymbol.ID.Market, futureContractExpiryDate);
var right = contract.Right == IB.RightType.Call ? OptionRight.Call : OptionRight.Put;
var strike = Convert.ToDecimal(contract.Strike);
return Symbol.CreateOption(futureSymbol, market, OptionStyle.American, right, strike, contractExpiryDate);
}
else if (securityType == SecurityType.Option)
if (securityType == SecurityType.Option)
{
var expiryDate = DateTime.ParseExact(contract.LastTradeDateOrContractMonth, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
var right = contract.Right == IB.RightType.Call ? OptionRight.Call : OptionRight.Put;
@@ -2324,7 +2365,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var subscribeSymbol = symbol;
// we subscribe to the underlying
if (symbol.ID.SecurityType == SecurityType.Option && symbol.IsCanonical())
if ((symbol.ID.SecurityType == SecurityType.Option || symbol.ID.SecurityType == SecurityType.FutureOption) && symbol.IsCanonical())
{
subscribeSymbol = symbol.Underlying;
_underlyings.Add(subscribeSymbol, symbol);
@@ -2398,7 +2439,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
{
Log.Trace("InteractiveBrokersBrokerage.Unsubscribe(): Unsubscribe Request: " + symbol.Value);
if (symbol.ID.SecurityType == SecurityType.Option && symbol.ID.StrikePrice == 0.0m)
if ((symbol.ID.SecurityType == SecurityType.Option || symbol.ID.SecurityType == SecurityType.FutureOption) && symbol.ID.StrikePrice == 0.0m)
{
_underlyings.Remove(symbol.Underlying);
}
@@ -2450,10 +2491,13 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
if (symbol.Value.IndexOfInvariant("universe", true) != -1) return false;
// Include future options as a special case with no matching market, otherwise
// our subscriptions are removed without any sort of notice.
return
(securityType == SecurityType.Equity && market == Market.USA) ||
(securityType == SecurityType.Forex && market == Market.Oanda) ||
(securityType == SecurityType.Option && market == Market.USA) ||
(securityType == SecurityType.FutureOption) ||
(securityType == SecurityType.Future);
}
@@ -2677,7 +2721,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
case IBApi.TickType.OPTION_CALL_OPEN_INTEREST:
case IBApi.TickType.OPTION_PUT_OPEN_INTEREST:
if (symbol.ID.SecurityType != SecurityType.Option && symbol.ID.SecurityType != SecurityType.Future)
if (symbol.ID.SecurityType != SecurityType.Option && symbol.ID.SecurityType != SecurityType.FutureOption && symbol.ID.SecurityType != SecurityType.Future)
{
return;
}
@@ -2717,18 +2761,32 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
/// <summary>
/// Method returns a collection of Symbols that are available at the broker.
/// </summary>
/// <param name="lookupName">String representing the name to lookup</param>
/// <param name="securityType">Expected security type of the returned symbols (if any)</param>
/// <param name="symbol">Symbol to search future/option chain for</param>
/// <param name="includeExpired">Include expired contracts</param>
/// <param name="securityCurrency">Expected security currency(if any)</param>
/// <param name="securityExchange">Expected security exchange name(if any)</param>
/// <returns></returns>
public IEnumerable<Symbol> LookupSymbols(string lookupName, SecurityType securityType, bool includeExpired, string securityCurrency = null, string securityExchange = null)
/// <returns>Future/Option chain associated with the Symbol provided</returns>
public IEnumerable<Symbol> LookupSymbols(Symbol symbol, bool includeExpired, string securityCurrency = null)
{
// setting up exchange defaults and filters
var exchangeSpecifier = securityType == SecurityType.Future ? securityExchange ?? "" : securityExchange ?? "Smart";
var exchangeSpecifier = GetSymbolExchange(symbol);
var futuresExchanges = _futuresExchanges.Values.Reverse().ToArray();
Func<string, int> exchangeFilter = exchange => securityType == SecurityType.Future ? Array.IndexOf(futuresExchanges, exchange) : 0;
Func<string, int> exchangeFilter = exchange => symbol.SecurityType == SecurityType.Future ? Array.IndexOf(futuresExchanges, exchange) : 0;
var lookupName = symbol.Value;
if (symbol.SecurityType == SecurityType.Future)
{
lookupName = symbol.ID.Symbol;
}
else if (symbol.SecurityType == SecurityType.Option)
{
lookupName = symbol.Underlying.Value;
}
else if (symbol.SecurityType == SecurityType.FutureOption)
{
// Futures Options use the underlying Symbol ticker for their ticker on IB.
lookupName = symbol.Underlying.ID.Symbol;
}
// setting up lookup request
var contract = new Contract
@@ -2736,7 +2794,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
Symbol = _symbolMapper.GetBrokerageRootSymbol(lookupName),
Currency = securityCurrency ?? Currencies.USD,
Exchange = exchangeSpecifier,
SecType = ConvertSecurityType(securityType),
SecType = ConvertSecurityType(symbol.SecurityType),
IncludeExpired = includeExpired
};
@@ -2744,22 +2802,22 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
var symbols = new List<Symbol>();
if (securityType == SecurityType.Option)
if (symbol.SecurityType == SecurityType.Option || symbol.SecurityType == SecurityType.FutureOption)
{
// IB requests for full option chains are rate limited and responses can be delayed up to a minute for each underlying,
// so we fetch them from the OCC website instead of using the IB API.
var underlyingSymbol = Symbol.Create(contract.Symbol, SecurityType.Equity, Market.USA);
symbols.AddRange(_algorithm.OptionChainProvider.GetOptionContractList(underlyingSymbol, DateTime.Today));
// For futures options, we fetch the option chain from CME.
symbols.AddRange(_algorithm.OptionChainProvider.GetOptionContractList(symbol.Underlying, DateTime.Today));
}
else if (securityType == SecurityType.Future)
else if (symbol.SecurityType == SecurityType.Future)
{
string market;
if (_symbolPropertiesDatabase.TryGetMarket(lookupName, securityType, out market))
if (_symbolPropertiesDatabase.TryGetMarket(lookupName, symbol.SecurityType, out market))
{
var symbolProperties = _symbolPropertiesDatabase.GetSymbolProperties(
market,
lookupName,
securityType,
symbol,
symbol.SecurityType,
Currencies.USD);
contract.Multiplier = Convert.ToInt32(symbolProperties.ContractMultiplier).ToStringInvariant();
@@ -2785,7 +2843,9 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
// Try to remove options or futures contracts that have expired
if (!includeExpired)
{
if (securityType == SecurityType.Option || securityType == SecurityType.Future)
if (symbol.SecurityType == SecurityType.Option ||
symbol.SecurityType == SecurityType.Future ||
symbol.SecurityType == SecurityType.FutureOption)
{
var removedSymbols = symbols.Where(x => x.ID.Date < GetRealTimeTickTime(x).Date).ToHashSet();
@@ -2828,6 +2888,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
// skipping universe and canonical symbols
if (!CanSubscribe(request.Symbol) ||
(request.Symbol.ID.SecurityType == SecurityType.Option && request.Symbol.IsCanonical()) ||
(request.Symbol.ID.SecurityType == SecurityType.FutureOption && request.Symbol.IsCanonical()) ||
(request.Symbol.ID.SecurityType == SecurityType.Future && request.Symbol.IsCanonical()))
{
yield break;
@@ -2838,6 +2899,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
request.Symbol.SecurityType != SecurityType.Forex &&
request.Symbol.SecurityType != SecurityType.Cfd &&
request.Symbol.SecurityType != SecurityType.Future &&
request.Symbol.SecurityType != SecurityType.FutureOption &&
request.Symbol.SecurityType != SecurityType.Option)
{
yield break;
@@ -3022,6 +3084,30 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
return history;
}
/// <summary>
/// Gets the exchange the Symbol should be routed to
/// </summary>
/// <param name="symbol">Symbol to route</param>
private string GetSymbolExchange(Symbol symbol)
{
switch (symbol.SecurityType)
{
case SecurityType.Option:
// Regular equity options uses default, in this case "Smart"
goto default;
// Futures options share the same market as the underlying Symbol
case SecurityType.FutureOption:
case SecurityType.Future:
return _futuresExchanges.ContainsKey(symbol.ID.Market)
? _futuresExchanges[symbol.ID.Market]
: symbol.ID.Market;
default:
return "Smart";
}
}
/// <summary>
/// Returns whether the brokerage should perform the cash synchronization
/// </summary>
@@ -3143,4 +3229,5 @@ 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

@@ -18,6 +18,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.FutureOption;
namespace QuantConnect.Brokerages.InteractiveBrokers
{
@@ -26,7 +28,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
/// </summary>
public class InteractiveBrokersSymbolMapper : ISymbolMapper
{
// we have a special treatment of futures, because IB renamed several exchange tickers (like GBP instead of 6B). We fix this:
// we have a special treatment of futures, because IB renamed several exchange tickers (like GBP instead of 6B). We fix this:
// We map those tickers back to their original names using the map below
private readonly Dictionary<string, string> _ibNameMap = new Dictionary<string, string>();
@@ -71,6 +73,7 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
if (symbol.ID.SecurityType != SecurityType.Forex &&
symbol.ID.SecurityType != SecurityType.Equity &&
symbol.ID.SecurityType != SecurityType.Option &&
symbol.ID.SecurityType != SecurityType.FutureOption &&
symbol.ID.SecurityType != SecurityType.Future)
throw new ArgumentException("Invalid security type: " + symbol.ID.SecurityType);
@@ -80,8 +83,16 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
switch (symbol.ID.SecurityType)
{
case SecurityType.Option:
// Final case is for equities. We use the mapped value to select
// the equity we want to trade.
return symbol.Underlying.Value;
case SecurityType.FutureOption:
// We use the underlying Future Symbol since IB doesn't use
// the Futures Options' ticker, but rather uses the underlying's
// Symbol, mapped to the brokerage.
return GetBrokerageSymbol(symbol.Underlying);
case SecurityType.Future:
return GetBrokerageRootSymbol(symbol.ID.Symbol);
@@ -110,7 +121,8 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
if (securityType != SecurityType.Forex &&
securityType != SecurityType.Equity &&
securityType != SecurityType.Option &&
securityType != SecurityType.Future)
securityType != SecurityType.Future &&
securityType != SecurityType.FutureOption)
throw new ArgumentException("Invalid security type: " + securityType);
try
@@ -123,6 +135,22 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
case SecurityType.Option:
return Symbol.CreateOption(brokerageSymbol, market, OptionStyle.American, optionRight, strike, expirationDate);
case SecurityType.FutureOption:
var canonicalFutureSymbol = Symbol.Create(GetLeanRootSymbol(brokerageSymbol), SecurityType.Future, market);
var futureContractMonth = FuturesOptionsExpiryFunctions.GetFutureContractMonth(canonicalFutureSymbol, expirationDate);
var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol)(futureContractMonth);
return Symbol.CreateOption(
Symbol.CreateFuture(
brokerageSymbol,
market,
futureExpiry),
market,
OptionStyle.American,
optionRight,
strike,
expirationDate);
case SecurityType.Equity:
brokerageSymbol = brokerageSymbol.Replace(" ", ".");
break;
@@ -136,7 +164,6 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
}
}
/// <summary>
/// IB specific versions of the symbol mapping (GetBrokerageRootSymbol) for future root symbols
/// </summary>
@@ -160,4 +187,4 @@ namespace QuantConnect.Brokerages.InteractiveBrokers
}
}
}
}

View File

@@ -156,7 +156,7 @@
<HintPath>Fxcm\QuantConnect.Fxcm.dll</HintPath>
</Reference>
<Reference Include="QuantConnect.IBAutomater, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\QuantConnect.IBAutomater.1.0.34\lib\net45\QuantConnect.IBAutomater.exe</HintPath>
<HintPath>..\packages\QuantConnect.IBAutomater.1.0.35\lib\net45\QuantConnect.IBAutomater.exe</HintPath>
</Reference>
<Reference Include="RestSharp, Version=106.6.10.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.106.6.10\lib\net452\RestSharp.dll</HintPath>
@@ -717,9 +717,9 @@
<Error Condition="!Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.3\build\Microsoft.NetCore.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetCore.Analyzers.2.9.3\build\Microsoft.NetCore.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.3\build\Microsoft.NetFramework.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetFramework.Analyzers.2.9.3\build\Microsoft.NetFramework.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.3\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.3\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props'))" />
<Error Condition="!Exists('..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets'))" />
<Error Condition="!Exists('..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets'))" />
</Target>
<Import Project="..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets" Condition="Exists('..\packages\QuantConnect.IBAutomater.1.0.34\build\QuantConnect.IBAutomater.targets')" />
<Import Project="..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets" Condition="Exists('..\packages\QuantConnect.IBAutomater.1.0.35\build\QuantConnect.IBAutomater.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -9,7 +9,7 @@
<package id="NATS.Client" version="0.8.1" targetFramework="net452" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
<package id="NodaTime" version="1.3.4" targetFramework="net452" />
<package id="QuantConnect.IBAutomater" version="1.0.34" targetFramework="net462" />
<package id="QuantConnect.IBAutomater" version="1.0.35" targetFramework="net462" />
<package id="RestSharp" version="106.6.10" targetFramework="net452" />
<package id="System.Net.Http" version="4.3.4" targetFramework="net462" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />

68
Common/Api/Account.cs Normal file
View File

@@ -0,0 +1,68 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Api
{
/// <summary>
/// Account information for an organization
/// </summary>
public class Account : RestResponse
{
/// <summary>
/// The organization Id
/// </summary>
[JsonProperty(PropertyName = "organizationId")]
public string OrganizationId { get; set; }
/// <summary>
/// The current account balance
/// </summary>
[JsonProperty(PropertyName = "creditBalance")]
public decimal CreditBalance { get; set; }
/// <summary>
/// The current organizations credit card
/// </summary>
[JsonProperty(PropertyName = "card")]
public Card Card { get; set; }
}
/// <summary>
/// Credit card
/// </summary>
public class Card
{
/// <summary>
/// Credit card brand
/// </summary>
[JsonProperty(PropertyName = "brand")]
public string Brand { get; set; }
/// <summary>
/// The credit card expiration
/// </summary>
[JsonProperty(PropertyName = "expiration")]
public DateTime Expiration { get; set; }
/// <summary>
/// The last 4 digits of the card
/// </summary>
[JsonProperty(PropertyName = "last4")]
public decimal LastFourDigits { get; set; }
}
}

199
Common/BinaryComparison.cs Normal file
View File

@@ -0,0 +1,199 @@
/*
* 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.Expressions;
using static QuantConnect.Util.ExpressionBuilder;
namespace QuantConnect
{
/// <summary>
/// Enumeration class defining binary comparisons and providing access to expressions and functions
/// capable of evaluating a particular comparison for any type. If a particular type does not implement
/// a binary comparison than an exception will be thrown.
/// </summary>
public class BinaryComparison
{
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.Equal"/>
/// </summary>
public static readonly BinaryComparison Equal = new BinaryComparison(ExpressionType.Equal);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.NotEqual"/>
/// </summary>
public static readonly BinaryComparison NotEqual = new BinaryComparison(ExpressionType.NotEqual);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.LessThan"/>
/// </summary>
public static readonly BinaryComparison LessThan = new BinaryComparison(ExpressionType.LessThan);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.GreaterThan"/>
/// </summary>
public static readonly BinaryComparison GreaterThan = new BinaryComparison(ExpressionType.GreaterThan);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.LessThanOrEqual"/>
/// </summary>
public static readonly BinaryComparison LessThanOrEqual = new BinaryComparison(ExpressionType.LessThanOrEqual);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> equivalent of <see cref="ExpressionType.GreaterThanOrEqual"/>
/// </summary>
public static readonly BinaryComparison GreaterThanOrEqual = new BinaryComparison(ExpressionType.GreaterThanOrEqual);
/// <summary>
/// Gets the <see cref="BinaryComparison"/> matching the provided <paramref name="type"/>
/// </summary>
public static BinaryComparison FromExpressionType(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal: return Equal;
case ExpressionType.NotEqual: return NotEqual;
case ExpressionType.LessThan: return LessThan;
case ExpressionType.LessThanOrEqual: return LessThanOrEqual;
case ExpressionType.GreaterThan: return GreaterThan;
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqual;
default:
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
}
}
/// <summary>
/// Gets the expression type defining the binary comparison.
/// </summary>
public ExpressionType Type { get; }
private BinaryComparison(ExpressionType type)
{
Type = type;
}
/// <summary>
/// Evaluates the specified <paramref name="left"/> and <paramref name="right"/> according to this <see cref="BinaryComparison"/>
/// </summary>
public bool Evaluate<T>(T left, T right)
=> OfType<T>.GetFunc(Type)(left, right);
/// <summary>
/// Gets a function capable of performing this <see cref="BinaryComparison"/>
/// </summary>
public Func<T, T, bool> GetEvaluator<T>()
=> OfType<T>.GetFunc(Type);
/// <summary>
/// Gets an expression representing this <see cref="BinaryComparison"/>
/// </summary>
public Expression<Func<T, T, bool>> GetExpression<T>()
=> OfType<T>.GetExpression(Type);
/// <summary>
/// Flips the logic ordering of the comparison's operands. For example, <see cref="LessThan"/>
/// is converted into <see cref="GreaterThan"/>
/// </summary>
public BinaryComparison FlipOperands()
{
switch (Type)
{
case ExpressionType.Equal: return this;
case ExpressionType.NotEqual: return this;
case ExpressionType.LessThan: return GreaterThan;
case ExpressionType.LessThanOrEqual: return GreaterThanOrEqual;
case ExpressionType.GreaterThan: return LessThan;
case ExpressionType.GreaterThanOrEqual: return LessThanOrEqual;
default:
throw new Exception(
"The skies are falling and the oceans are rising! " +
"If you've made it here then this exception is the least of your worries! " +
$"ExpressionType: {Type}"
);
}
}
/// <summary>Returns a string that represents the current object.</summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return Type.ToString();
}
/// <summary>
/// Provides thread-safe lookups of expressions and functions for binary comparisons by type.
/// MUCH faster than using a concurrency dictionary, for example, as it's expanded at runtime
/// and hard-linked, no look-up is actually performed!
/// </summary>
private static class OfType<T>
{
private static readonly Expression<Func<T, T, bool>> EqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.Equal);
private static readonly Expression<Func<T, T, bool>> NotEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.NotEqual);
private static readonly Expression<Func<T, T, bool>> LessThanExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.LessThan);
private static readonly Expression<Func<T, T, bool>> LessThanOrEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.LessThanOrEqual);
private static readonly Expression<Func<T, T, bool>> GreaterThanExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.GreaterThan);
private static readonly Expression<Func<T, T, bool>> GreaterThanOrEqualExpr = MakeBinaryComparisonLambdaOrNull(ExpressionType.GreaterThanOrEqual);
public static Expression<Func<T, T, bool>> GetExpression(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal: return EqualExpr;
case ExpressionType.NotEqual: return NotEqualExpr;
case ExpressionType.LessThan: return LessThanExpr;
case ExpressionType.LessThanOrEqual: return LessThanOrEqualExpr;
case ExpressionType.GreaterThan: return GreaterThanExpr;
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqualExpr;
default:
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
}
}
private static readonly Func<T, T, bool> EqualFunc = EqualExpr?.Compile();
private static readonly Func<T, T, bool> NotEqualFunc = NotEqualExpr?.Compile();
private static readonly Func<T, T, bool> LessThanFunc = LessThanExpr?.Compile();
private static readonly Func<T, T, bool> LessThanOrEqualFunc = LessThanOrEqualExpr?.Compile();
private static readonly Func<T, T, bool> GreaterThanFunc = GreaterThanExpr?.Compile();
private static readonly Func<T, T, bool> GreaterThanOrEqualFunc = GreaterThanOrEqualExpr?.Compile();
public static Func<T, T, bool> GetFunc(ExpressionType type)
{
switch (type)
{
case ExpressionType.Equal: return EqualFunc;
case ExpressionType.NotEqual: return NotEqualFunc;
case ExpressionType.LessThan: return LessThanFunc;
case ExpressionType.LessThanOrEqual: return LessThanOrEqualFunc;
case ExpressionType.GreaterThan: return GreaterThanFunc;
case ExpressionType.GreaterThanOrEqual: return GreaterThanOrEqualFunc;
default:
throw new InvalidOperationException($"The specified ExpressionType '{type}' is not a binary comparison.");
}
}
private static Expression<Func<T, T, bool>> MakeBinaryComparisonLambdaOrNull(ExpressionType type)
{
try
{
return MakeBinaryComparisonLambda<T>(type);
}
catch
{
return null;
}
}
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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.Collections.Immutable;
using System.Linq.Expressions;
namespace QuantConnect
{
/// <summary>
/// Provides convenience extension methods for applying a <see cref="BinaryComparison"/> to collections.
/// </summary>
public static class BinaryComparisonExtensions
{
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static TCollection Filter<T, TCollection>(
this BinaryComparison comparison,
TCollection values,
T reference
)
where TCollection : ICollection<T>, new()
{
var result = new TCollection();
var evaluator = comparison.GetEvaluator<T>();
foreach (var value in values)
{
if (evaluator(value, reference))
{
result.Add(value);
}
}
return result;
}
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static SortedDictionary<TKey, TValue> Filter<TKey, TValue>(
this BinaryComparison comparison,
SortedDictionary<TKey, TValue> values,
TKey reference
)
{
SortedDictionary<TKey, TValue> result;
if (comparison.Type == ExpressionType.NotEqual)
{
result = new SortedDictionary<TKey, TValue>(values);
result.Remove(reference);
return result;
}
result = new SortedDictionary<TKey, TValue>();
if (comparison.Type == ExpressionType.Equal)
{
TValue value;
if (values.TryGetValue(reference, out value))
{
result.Add(reference, value);
}
return result;
}
// since we're enumerating a sorted collection, once we receive
// a mismatch it means we'll never again receive a match
var breakAfterFailure =
comparison == BinaryComparison.LessThanOrEqual ||
comparison == BinaryComparison.LessThanOrEqual;
var evaluator = comparison.GetEvaluator<TKey>();
foreach (var kvp in values)
{
if (evaluator(kvp.Key, reference))
{
result.Add(kvp.Key, kvp.Value);
}
else if (breakAfterFailure)
{
break;
}
}
return result;
}
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static ImmutableSortedDictionary<TKey, TValue> Filter<TKey, TValue>(
this BinaryComparison comparison,
ImmutableSortedDictionary<TKey, TValue> values,
TKey reference
)
{
if (comparison.Type == ExpressionType.NotEqual)
{
return values.Remove(reference);
}
var result = ImmutableSortedDictionary<TKey, TValue>.Empty;
if (comparison.Type == ExpressionType.Equal)
{
TValue value;
if (values.TryGetValue(reference, out value))
{
result = result.Add(reference, value);
}
return result;
}
// since we're enumerating a sorted collection, once we receive
// a mismatch it means we'll never again receive a match
var breakAfterFailure =
comparison == BinaryComparison.LessThanOrEqual ||
comparison == BinaryComparison.LessThanOrEqual;
var evaluator = comparison.GetEvaluator<TKey>();
foreach (var kvp in values)
{
if (evaluator(kvp.Key, reference))
{
result = result.Add(kvp.Key, kvp.Value);
}
else if (breakAfterFailure)
{
break;
}
}
return result;
}
/// <summary>
/// Filters the provided <paramref name="values"/> according to this <see cref="BinaryComparison"/>
/// and the specified <paramref name="reference"/> value. The <paramref name="reference"/> value is
/// used as the RIGHT side of the binary comparison. Consider the binary comparison is LessThan and
/// we call Filter(values, 42). We're looking for keys that are less than 42.
/// </summary>
public static Tuple<ImmutableSortedDictionary<TKey, TValue>, ImmutableSortedDictionary<TKey, TValue>> SplitBy<TKey, TValue>(
this BinaryComparison comparison,
ImmutableSortedDictionary<TKey, TValue> values,
TKey reference
)
{
var matches = ImmutableSortedDictionary<TKey, TValue>.Empty;
var removed = ImmutableSortedDictionary<TKey, TValue>.Empty;
if (comparison.Type == ExpressionType.NotEqual)
{
var match = values.Remove(reference);
removed = BinaryComparison.Equal.Filter(values, reference);
return Tuple.Create(match, removed);
}
if (comparison.Type == ExpressionType.Equal)
{
TValue value;
if (values.TryGetValue(reference, out value))
{
matches = matches.Add(reference, value);
removed = BinaryComparison.NotEqual.Filter(values, reference);
return Tuple.Create(matches, removed);
}
// no matches
return Tuple.Create(ImmutableSortedDictionary<TKey, TValue>.Empty, values);
}
var evaluator = comparison.GetEvaluator<TKey>();
foreach (var kvp in values)
{
if (evaluator(kvp.Key, reference))
{
matches = matches.Add(kvp.Key, kvp.Value);
}
else
{
removed = removed.Add(kvp.Key, kvp.Value);
}
}
return Tuple.Create(matches, removed);
}
}
}

View File

@@ -79,6 +79,7 @@ namespace QuantConnect.Brokerages
case SecurityType.Base:
case SecurityType.Commodity:
case SecurityType.Option:
case SecurityType.FutureOption:
case SecurityType.Future:
default:
return 1m;
@@ -92,4 +93,4 @@ namespace QuantConnect.Brokerages
/// <returns>The settlement model for this brokerage</returns>
public override ISettlementModel GetSettlementModel(Security security) => new ImmediateSettlementModel();
}
}
}

View File

@@ -43,6 +43,7 @@ namespace QuantConnect.Brokerages
{SecurityType.Equity, Market.USA},
{SecurityType.Option, Market.USA},
{SecurityType.Future, Market.CME},
{SecurityType.FutureOption, Market.CME},
{SecurityType.Forex, Market.Oanda},
{SecurityType.Cfd, Market.FXCM},
{SecurityType.Crypto, Market.GDAX}
@@ -174,6 +175,7 @@ namespace QuantConnect.Brokerages
case SecurityType.Base:
case SecurityType.Commodity:
case SecurityType.Option:
case SecurityType.FutureOption:
case SecurityType.Future:
default:
return 1m;
@@ -195,6 +197,8 @@ namespace QuantConnect.Brokerages
return new EquityFillModel();
case SecurityType.Option:
break;
case SecurityType.FutureOption:
break;
case SecurityType.Commodity:
break;
case SecurityType.Forex:
@@ -230,6 +234,7 @@ namespace QuantConnect.Brokerages
case SecurityType.Equity:
case SecurityType.Option:
case SecurityType.Future:
case SecurityType.FutureOption:
return new InteractiveBrokersFeeModel();
case SecurityType.Commodity:
@@ -258,6 +263,7 @@ namespace QuantConnect.Brokerages
case SecurityType.Commodity:
case SecurityType.Option:
case SecurityType.FutureOption:
case SecurityType.Future:
default:
return new ConstantSlippageModel(0);
@@ -321,6 +327,9 @@ namespace QuantConnect.Brokerages
case SecurityType.Option:
model = new OptionMarginModel(RequiredFreeBuyingPowerPercent);
break;
case SecurityType.FutureOption:
model = new FuturesOptionsMarginModel(RequiredFreeBuyingPowerPercent, (Option)security);
break;
case SecurityType.Future:
model = new FutureMarginModel(RequiredFreeBuyingPowerPercent, security);
break;

View File

@@ -40,6 +40,7 @@ namespace QuantConnect.Brokerages
{SecurityType.Equity, Market.USA},
{SecurityType.Option, Market.USA},
{SecurityType.Future, Market.CME},
{SecurityType.FutureOption, Market.CME},
{SecurityType.Forex, Market.Oanda},
{SecurityType.Cfd, Market.Oanda}
}.ToReadOnlyDictionary();
@@ -95,7 +96,8 @@ namespace QuantConnect.Brokerages
if (security.Type != SecurityType.Equity &&
security.Type != SecurityType.Forex &&
security.Type != SecurityType.Option &&
security.Type != SecurityType.Future)
security.Type != SecurityType.Future &&
security.Type != SecurityType.FutureOption)
{
message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
Invariant($"The {nameof(InteractiveBrokersBrokerageModel)} does not support {security.Type} security type.")

View File

@@ -63,6 +63,11 @@ namespace QuantConnect.Data
/// </summary>
protected static readonly List<Resolution> MinuteResolution = new List<Resolution> { Resolution.Minute };
/// <summary>
/// A list of high <see cref="Resolution"/>, including minute, second, and tick.
/// </summary>
protected static readonly List<Resolution> HighResolution = new List<Resolution> { Resolution.Minute, Resolution.Second, Resolution.Tick };
/// <summary>
/// Market Data Type of this data - does it come in individual price packets or is it grouped into OHLC.
/// </summary>
@@ -198,7 +203,8 @@ namespace QuantConnect.Data
/// <returns>True indicates mapping should be used</returns>
public virtual bool RequiresMapping()
{
return Symbol.SecurityType == SecurityType.Equity || Symbol.SecurityType == SecurityType.Option;
return Symbol.SecurityType == SecurityType.Equity ||
Symbol.SecurityType == SecurityType.Option;
}
/// <summary>
@@ -232,11 +238,16 @@ namespace QuantConnect.Data
/// custom data types can override it</remarks>
public virtual List<Resolution> SupportedResolutions()
{
if (Symbol.SecurityType == SecurityType.Option)
if (Symbol.SecurityType == SecurityType.Option || Symbol.SecurityType == SecurityType.FutureOption)
{
return MinuteResolution;
}
if (Symbol.SecurityType == SecurityType.Future)
{
return HighResolution;
}
return AllResolutions;
}

View File

@@ -61,5 +61,10 @@ namespace QuantConnect.Data.Custom.SmartInsider
/// </summary>
[EnumMember(Value = "Independent 3rd Party")]
ThirdParty,
/// <summary>
/// The field was not found in this enum
/// </summary>
Error
}
}

View File

@@ -62,8 +62,9 @@ namespace QuantConnect.Data.Custom.SmartInsider
SatisfyStockVesting,
/// <summary>
/// Error, but should actually be SatisfyStockVesting
/// The field was not found in the enum, or is representative of a SatisfyStockVesting entry.
/// </summary>
/// <remarks>The EnumMember attribute is kept for backwards compatibility</remarks>
[EnumMember(Value = "Missing Lookup Formula for BuybackHoldingTypeId 10.00")]
Error
}

View File

@@ -41,6 +41,11 @@ namespace QuantConnect.Data.Custom.SmartInsider
/// Under a specific agreement between the issuer and shareholder
/// </summary>
[EnumMember(Value = "Off Market Agreement")]
OffMarket
OffMarket,
/// <summary>
/// Field is not in this enum
/// </summary>
Error
}
}

View File

@@ -141,7 +141,7 @@ namespace QuantConnect.Data.Custom.SmartInsider
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderIntention.FromRawData: New unexpected entry found {tsv[1]}. Parsed as NotSpecified.");
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found {tsv[1]}. Parsed as NotSpecified.");
}
}
@@ -171,10 +171,54 @@ namespace QuantConnect.Data.Custom.SmartInsider
TimeProcessedUtc = string.IsNullOrWhiteSpace(tsv[41]) ? (DateTime?)null : ParseDate(tsv[41]);
AnnouncedIn = string.IsNullOrWhiteSpace(tsv[42]) ? null : tsv[42];
Execution = string.IsNullOrWhiteSpace(tsv[43]) ? (SmartInsiderExecution?)null : JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[43]}\"");
ExecutionEntity = string.IsNullOrWhiteSpace(tsv[44]) ? (SmartInsiderExecutionEntity?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[44]}\"");
ExecutionHolding = string.IsNullOrWhiteSpace(tsv[45]) ? (SmartInsiderExecutionHolding?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[45]}\"");
ExecutionHolding = ExecutionHolding == SmartInsiderExecutionHolding.Error ? SmartInsiderExecutionHolding.SatisfyStockVesting : ExecutionHolding;
Execution = null;
if (!string.IsNullOrWhiteSpace(tsv[43]))
{
try
{
Execution = JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[43]}\"");
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found for Execution: {tsv[43]}. Parsed as Error.");
Execution = SmartInsiderExecution.Error;
}
}
ExecutionEntity = null;
if (!string.IsNullOrWhiteSpace(tsv[44]))
{
try
{
ExecutionEntity = JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[44]}\"");
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found for ExecutionEntity: {tsv[44]}. Parsed as Error.");
ExecutionEntity = SmartInsiderExecutionEntity.Error;
}
}
ExecutionHolding = null;
if (!string.IsNullOrWhiteSpace(tsv[45]))
{
try
{
ExecutionHolding = JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[45]}\"");
if (ExecutionHolding == SmartInsiderExecutionHolding.Error)
{
// This error in particular represents a SatisfyStockVesting field.
ExecutionHolding = SmartInsiderExecutionHolding.SatisfyStockVesting;
}
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderIntention.FromRawData(): New unexpected entry found for ExecutionHolding: {tsv[45]}. Parsed as Error.");
ExecutionHolding = SmartInsiderExecutionHolding.Error;
}
}
Amount = string.IsNullOrWhiteSpace(tsv[46]) ? (int?)null : Convert.ToInt32(tsv[46], CultureInfo.InvariantCulture);
ValueCurrency = string.IsNullOrWhiteSpace(tsv[47]) ? null : tsv[47];
AmountValue = string.IsNullOrWhiteSpace(tsv[48]) ? (long?)null : Convert.ToInt64(tsv[48], CultureInfo.InvariantCulture);

View File

@@ -17,6 +17,7 @@ using Newtonsoft.Json;
using System;
using System.Globalization;
using System.IO;
using QuantConnect.Logging;
namespace QuantConnect.Data.Custom.SmartInsider
{
@@ -154,7 +155,18 @@ namespace QuantConnect.Data.Custom.SmartInsider
var tsv = line.Split('\t');
TransactionID = string.IsNullOrWhiteSpace(tsv[0]) ? null : tsv[0];
EventType = string.IsNullOrWhiteSpace(tsv[1]) ? (SmartInsiderEventType?)null : JsonConvert.DeserializeObject<SmartInsiderEventType>($"\"{tsv[1]}\"");
EventType = SmartInsiderEventType.NotSpecified;
if (!string.IsNullOrWhiteSpace(tsv[1]))
{
try
{
EventType = JsonConvert.DeserializeObject<SmartInsiderEventType>($"\"{tsv[1]}\"");
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for EventType: {tsv[1]}. Parsed as NotSpecified.");
}
}
LastUpdate = DateTime.ParseExact(tsv[2], "yyyy-MM-dd", CultureInfo.InvariantCulture);
LastIDsUpdate = string.IsNullOrWhiteSpace(tsv[3]) ? (DateTime?)null : DateTime.ParseExact(tsv[3], "yyyy-MM-dd", CultureInfo.InvariantCulture);
ISIN = string.IsNullOrWhiteSpace(tsv[4]) ? null : tsv[4];
@@ -175,10 +187,54 @@ namespace QuantConnect.Data.Custom.SmartInsider
TickerSymbol = string.IsNullOrWhiteSpace(tsv[19]) ? null : tsv[19];
BuybackDate = string.IsNullOrWhiteSpace(tsv[20]) ? (DateTime?)null : DateTime.ParseExact(tsv[20], "yyyy-MM-dd", CultureInfo.InvariantCulture);
Execution = string.IsNullOrWhiteSpace(tsv[21]) ? (SmartInsiderExecution?)null : JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[21]}\"");
ExecutionEntity = string.IsNullOrWhiteSpace(tsv[22]) ? (SmartInsiderExecutionEntity?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[22]}\"");
ExecutionHolding = string.IsNullOrWhiteSpace(tsv[23]) ? (SmartInsiderExecutionHolding?)null : JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[23]}\"");
ExecutionHolding = ExecutionHolding == SmartInsiderExecutionHolding.Error ? SmartInsiderExecutionHolding.SatisfyStockVesting : ExecutionHolding;
Execution = null;
if (!string.IsNullOrWhiteSpace(tsv[21]))
{
try
{
Execution = JsonConvert.DeserializeObject<SmartInsiderExecution>($"\"{tsv[21]}\"");
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for Execution: {tsv[21]}. Parsed as Error.");
Execution = SmartInsiderExecution.Error;
}
}
ExecutionEntity = null;
if (!string.IsNullOrWhiteSpace(tsv[22]))
{
try
{
ExecutionEntity = JsonConvert.DeserializeObject<SmartInsiderExecutionEntity>($"\"{tsv[22]}\"");
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for ExecutionEntity: {tsv[22]}. Parsed as Error.");
ExecutionEntity = SmartInsiderExecutionEntity.Error;
}
}
ExecutionHolding = null;
if (!string.IsNullOrWhiteSpace(tsv[23]))
{
try
{
ExecutionHolding = JsonConvert.DeserializeObject<SmartInsiderExecutionHolding>($"\"{tsv[23]}\"");
if (ExecutionHolding == SmartInsiderExecutionHolding.Error)
{
// This error in particular represents a SatisfyStockVesting field.
ExecutionHolding = SmartInsiderExecutionHolding.SatisfyStockVesting;
}
}
catch (JsonSerializationException)
{
Log.Error($"SmartInsiderTransaction.FromRawData(): New unexpected entry found for ExecutionHolding: {tsv[23]}. Parsed as Error.");
ExecutionHolding = SmartInsiderExecutionHolding.Error;
}
}
Currency = string.IsNullOrWhiteSpace(tsv[24]) ? null : tsv[24];
ExecutionPrice = string.IsNullOrWhiteSpace(tsv[25]) ? (decimal?)null : Convert.ToDecimal(tsv[25], CultureInfo.InvariantCulture);
Amount = string.IsNullOrWhiteSpace(tsv[26]) ? (decimal?)null : Convert.ToDecimal(tsv[26], CultureInfo.InvariantCulture);

View File

@@ -68,7 +68,7 @@ namespace QuantConnect.Data.Fundamental
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var source =
Path.Combine(Globals.DataFolder, Invariant(
Path.Combine(Globals.CacheDataFolder, Invariant(
$"equity/{config.Market}/fundamental/fine/{config.Symbol.Value.ToLowerInvariant()}/{date:yyyyMMdd}.zip"
));

View File

@@ -54,17 +54,8 @@ namespace QuantConnect.Data.Market
/// <param name="time">The time this data was emitted.</param>
public DataDictionary(DateTime time)
{
#pragma warning disable 618 // This assignment is left here until the Time property is removed.
Time = time;
#pragma warning restore 618
}
/// <summary>
/// Gets or sets the time associated with this collection of data
/// </summary>
[Obsolete("The DataDictionary<T> Time property is now obsolete. All algorithms should use algorithm.Time instead.")]
public DateTime Time { get; set; }
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
@@ -300,4 +291,4 @@ namespace QuantConnect.Data.Market
dictionary.Add(data.Symbol, data);
}
}
}
}

View File

@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new Delisting this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new Delisting this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the Delisting with the specified Symbol.
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new Delisting this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new Delisting this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}

View File

@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new Dividend this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new Dividend this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the Dividend with the specified Symbol.
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new Dividend this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new Dividend this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}

View File

@@ -45,7 +45,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new FuturesChain this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new FuturesChain this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the FuturesChain with the specified Symbol.
@@ -55,6 +55,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new FuturesChain this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new FuturesChain this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}
}

View File

@@ -137,6 +137,7 @@ namespace QuantConnect.Data.Market
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
if (config.SecurityType == SecurityType.Option ||
config.SecurityType == SecurityType.FutureOption ||
config.SecurityType == SecurityType.Future)
{
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
@@ -153,4 +154,4 @@ namespace QuantConnect.Data.Market
return new OpenInterest(this);
}
}
}
}

View File

@@ -45,7 +45,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new OptionChain this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new OptionChain this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the OptionChain with the specified Symbol.
@@ -55,6 +55,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new OptionChain this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new OptionChain this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}
}

View File

@@ -298,6 +298,7 @@ namespace QuantConnect.Data.Market
return ParseCfd(config, stream, date);
case SecurityType.Option:
case SecurityType.FutureOption:
return ParseOption(config, stream, date);
case SecurityType.Future:
@@ -344,6 +345,7 @@ namespace QuantConnect.Data.Market
return ParseCfd(config, line, date);
case SecurityType.Option:
case SecurityType.FutureOption:
return ParseOption(config, line, date);
case SecurityType.Future:
@@ -455,7 +457,7 @@ namespace QuantConnect.Data.Market
/// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
public QuoteBar ParseOption(SubscriptionDataConfig config, string line, DateTime date)
{
return ParseQuote(config, date, line, true);
return ParseQuote(config, date, line, config.Symbol.SecurityType == SecurityType.Option);
}
/// <summary>
@@ -467,7 +469,7 @@ namespace QuantConnect.Data.Market
/// <returns><see cref="QuoteBar"/> with the bid/ask set to same values</returns>
public QuoteBar ParseOption(SubscriptionDataConfig config, StreamReader streamReader, DateTime date)
{
return ParseQuote(config, date, streamReader, true);
return ParseQuote(config, date, streamReader, config.Symbol.SecurityType == SecurityType.Option);
}
/// <summary>
@@ -552,6 +554,7 @@ namespace QuantConnect.Data.Market
/// <returns><see cref="QuoteBar"/> with the bid/ask prices set appropriately</returns>
private QuoteBar ParseQuote(SubscriptionDataConfig config, DateTime date, StreamReader streamReader, bool useScaleFactor)
{
// Non-equity asset classes will not use scaling, including options that have a non-equity underlying asset class.
var scaleFactor = useScaleFactor
? _scaleFactor
: 1;
@@ -712,7 +715,8 @@ namespace QuantConnect.Data.Market
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
if (config.SecurityType == SecurityType.Option ||
config.SecurityType == SecurityType.Future)
config.SecurityType == SecurityType.Future ||
config.SecurityType == SecurityType.FutureOption)
{
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
}

View File

@@ -45,7 +45,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new QuoteBar this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new QuoteBar this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the QuoteBar with the specified Symbol.
@@ -55,6 +55,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new QuoteBar this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new QuoteBar this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}
}

View File

@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new Split this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new Split this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the Split with the specified Symbol.
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new Split this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new Split this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}
}

View File

@@ -47,7 +47,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new SymbolChangedEvent this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new SymbolChangedEvent this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the SymbolChangedEvent with the specified Symbol.
@@ -57,6 +57,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new SymbolChangedEvent this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new SymbolChangedEvent this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}

View File

@@ -280,7 +280,7 @@ namespace QuantConnect.Data.Market
DataType = MarketDataType.Tick;
Symbol = symbol;
Time = baseDate.Date.AddMilliseconds(csv[0].ToInt32());
Value = csv[1].ToDecimal() / GetScaleFactor(symbol.SecurityType);
Value = csv[1].ToDecimal() / GetScaleFactor(symbol);
TickType = TickType.Trade;
Quantity = csv[2].ToDecimal();
Exchange = csv[3].Trim();
@@ -302,7 +302,7 @@ namespace QuantConnect.Data.Market
Symbol = config.Symbol;
// Which security type is this data feed:
var scaleFactor = GetScaleFactor(config.SecurityType);
var scaleFactor = GetScaleFactor(config.Symbol);
switch (config.SecurityType)
{
@@ -387,6 +387,7 @@ namespace QuantConnect.Data.Market
}
case SecurityType.Future:
case SecurityType.Option:
case SecurityType.FutureOption:
{
TickType = config.TickType;
Time = date.Date.AddMilliseconds((double)reader.GetDecimal())
@@ -440,7 +441,7 @@ namespace QuantConnect.Data.Market
Symbol = config.Symbol;
// Which security type is this data feed:
var scaleFactor = GetScaleFactor(config.SecurityType);
var scaleFactor = GetScaleFactor(config.Symbol);
switch (config.SecurityType)
{
@@ -530,6 +531,7 @@ namespace QuantConnect.Data.Market
}
case SecurityType.Future:
case SecurityType.Option:
case SecurityType.FutureOption:
{
var csv = line.ToCsv(7);
TickType = config.TickType;
@@ -632,7 +634,8 @@ namespace QuantConnect.Data.Market
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
if (config.SecurityType == SecurityType.Option ||
config.SecurityType == SecurityType.Future)
config.SecurityType == SecurityType.Future ||
config.SecurityType == SecurityType.FutureOption)
{
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
}
@@ -714,10 +717,16 @@ namespace QuantConnect.Data.Market
}
}
private static decimal GetScaleFactor(SecurityType securityType)
/// <summary>
/// Gets the scaling factor according to the <see cref="SecurityType"/> of the <see cref="Symbol"/> provided.
/// Non-equity data will not be scaled, including options with an underlying non-equity asset class.
/// </summary>
/// <param name="symbol">Symbol to get scaling factor for</param>
/// <returns>Scaling factor</returns>
private static decimal GetScaleFactor(Symbol symbol)
{
return securityType == SecurityType.Equity || securityType == SecurityType.Option ? 10000m : 1;
return symbol.SecurityType == SecurityType.Equity || symbol.SecurityType == SecurityType.Option ? 10000m : 1;
}
}
}
}

View File

@@ -48,7 +48,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new List<Tick> this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new List<Tick> this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the list of Tick with the specified Symbol.
@@ -58,6 +58,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new List<Tick> this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new List<Tick> this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}

View File

@@ -221,6 +221,7 @@ namespace QuantConnect.Data.Market
return ParseCfd(config, line, date);
case SecurityType.Option:
case SecurityType.FutureOption:
return ParseOption(config, line, date);
case SecurityType.Future:
@@ -278,6 +279,7 @@ namespace QuantConnect.Data.Market
return ParseCfd(config, stream, date);
case SecurityType.Option:
case SecurityType.FutureOption:
return ParseOption(config, stream, date);
case SecurityType.Future:
@@ -682,10 +684,11 @@ namespace QuantConnect.Data.Market
tradeBar.Time = date.Date.AddMilliseconds(csv[0].ToInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
}
tradeBar.Open = csv[1].ToDecimal() * _scaleFactor;
tradeBar.High = csv[2].ToDecimal() * _scaleFactor;
tradeBar.Low = csv[3].ToDecimal() * _scaleFactor;
tradeBar.Close = csv[4].ToDecimal() * _scaleFactor;
var scalingFactor = GetScaleFactor(config.Symbol);
tradeBar.Open = csv[1].ToDecimal() * scalingFactor;
tradeBar.High = csv[2].ToDecimal() * scalingFactor;
tradeBar.Low = csv[3].ToDecimal() * scalingFactor;
tradeBar.Close = csv[4].ToDecimal() * scalingFactor;
tradeBar.Volume = csv[5].ToDecimal();
return tradeBar;
@@ -719,10 +722,11 @@ namespace QuantConnect.Data.Market
tradeBar.Time = date.Date.AddMilliseconds(streamReader.GetInt32()).ConvertTo(config.DataTimeZone, config.ExchangeTimeZone);
}
tradeBar.Open = streamReader.GetDecimal() * _scaleFactor;
tradeBar.High = streamReader.GetDecimal() * _scaleFactor;
tradeBar.Low = streamReader.GetDecimal() * _scaleFactor;
tradeBar.Close = streamReader.GetDecimal() * _scaleFactor;
var scalingFactor = GetScaleFactor(config.Symbol);
tradeBar.Open = streamReader.GetDecimal() * scalingFactor;
tradeBar.High = streamReader.GetDecimal() * scalingFactor;
tradeBar.Low = streamReader.GetDecimal() * scalingFactor;
tradeBar.Close = streamReader.GetDecimal() * scalingFactor;
tradeBar.Volume = streamReader.GetDecimal();
return tradeBar;
@@ -889,7 +893,8 @@ namespace QuantConnect.Data.Market
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
if (config.SecurityType == SecurityType.Option ||
config.SecurityType == SecurityType.Future)
config.SecurityType == SecurityType.Future ||
config.SecurityType == SecurityType.FutureOption)
{
source += "#" + LeanData.GenerateZipEntryName(config.Symbol, date, config.Resolution, config.TickType);
}
@@ -936,6 +941,17 @@ namespace QuantConnect.Data.Market
$"V: {Volume.SmartRounding()}";
}
/// <summary>
/// Gets the scaling factor according to the <see cref="SecurityType"/> of the <see cref="Symbol"/> provided.
/// Non-equity data will not be scaled, including options with an underlying non-equity asset class.
/// </summary>
/// <param name="symbol">Symbol to get scaling factor for</param>
/// <returns>Scaling factor</returns>
private static decimal GetScaleFactor(Symbol symbol)
{
return symbol.SecurityType == SecurityType.Equity || symbol.SecurityType == SecurityType.Option ? _scaleFactor : 1;
}
/// <summary>
/// Initializes this bar with a first data point
/// </summary>

View File

@@ -46,7 +46,7 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="ticker">The ticker of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new TradeBar this[string ticker] { get { return base[ticker]; } set { base[ticker] = value; } }
public new TradeBar this[string ticker] { get { return base[ticker]; } internal set { base[ticker] = value; } }
/// <summary>
/// Gets or sets the TradeBar with the specified Symbol.
@@ -56,6 +56,6 @@ namespace QuantConnect.Data.Market
/// </returns>
/// <param name="symbol">The Symbol of the element to get or set.</param>
/// <remarks>Wraps the base implementation to enable indexing in python algorithms due to pythonnet limitations</remarks>
public new TradeBar this[Symbol symbol] { get { return base[symbol]; } set { base[symbol] = value; } }
public new TradeBar this[Symbol symbol] { get { return base[symbol]; } internal set { base[symbol] = value; } }
}
}
}

View File

@@ -203,15 +203,15 @@ namespace QuantConnect.Data
/// <param name="time">The timestamp for this slice of data</param>
/// <param name="data">The raw data in this slice</param>
public Slice(DateTime time, List<BaseData> data)
: this(time, data, CreateCollection<TradeBars, TradeBar>(time, data),
CreateCollection<QuoteBars, QuoteBar>(time, data),
CreateTicksCollection(time, data),
CreateCollection<OptionChains, OptionChain>(time, data),
CreateCollection<FuturesChains, FuturesChain>(time, data),
CreateCollection<Splits, Split>(time, data),
CreateCollection<Dividends, Dividend>(time, data),
CreateCollection<Delistings, Delisting>(time, data),
CreateCollection<SymbolChangedEvents, SymbolChangedEvent>(time, data))
: this(time, data, CreateCollection<TradeBars, TradeBar>(data),
CreateCollection<QuoteBars, QuoteBar>(data),
CreateTicksCollection(data),
CreateCollection<OptionChains, OptionChain>(data),
CreateCollection<FuturesChains, FuturesChain>(data),
CreateCollection<Splits, Split>(data),
CreateCollection<Dividends, Dividend>(data),
CreateCollection<Delistings, Delisting>(data),
CreateCollection<SymbolChangedEvents, SymbolChangedEvent>(data))
{
}
@@ -500,9 +500,9 @@ namespace QuantConnect.Data
/// <summary>
/// Dynamically produces a <see cref="Ticks"/> data dictionary using the provided data
/// </summary>
private static Ticks CreateTicksCollection(DateTime time, IEnumerable<BaseData> data)
private static Ticks CreateTicksCollection(IEnumerable<BaseData> data)
{
var ticks = new Ticks(time);
var ticks = new Ticks();
foreach (var tick in data.OfType<Tick>())
{
List<Tick> listTicks;
@@ -523,16 +523,11 @@ namespace QuantConnect.Data
/// <param name="time">The current slice time</param>
/// <param name="data">The data to create the collection</param>
/// <returns>The data dictionary of <typeparamref name="TItem"/> containing all the data of that type in this slice</returns>
private static T CreateCollection<T, TItem>(DateTime time, IEnumerable<BaseData> data)
private static T CreateCollection<T, TItem>(IEnumerable<BaseData> data)
where T : DataDictionary<TItem>, new()
where TItem : BaseData
{
var collection = new T
{
#pragma warning disable 618 // This assignment is left here until the Time property is removed.
Time = time
#pragma warning restore 618
};
var collection = new T();
foreach (var item in data.OfType<TItem>())
{
collection[item.Symbol] = item;

View File

@@ -104,7 +104,7 @@ namespace QuantConnect.Data
{
get
{
return Symbol.ID.SecurityType == SecurityType.Option ?
return (Symbol.ID.SecurityType == SecurityType.Option || Symbol.ID.SecurityType == SecurityType.FutureOption) ?
(Symbol.HasUnderlying ? Symbol.Underlying.Value : Symbol.Value) :
Symbol.Value;
}

View File

@@ -1,11 +1,11 @@
/*
* 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");
*
* 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.
@@ -20,7 +20,7 @@ using System.Linq;
namespace QuantConnect.Data
{
/// <summary>
/// Provides convenient methods for holding several <see cref="SubscriptionDataConfig"/>
/// Provides convenient methods for holding several <see cref="SubscriptionDataConfig"/>
/// </summary>
public class SubscriptionDataConfigList : List<SubscriptionDataConfig>
{
@@ -56,9 +56,9 @@ namespace QuantConnect.Data
/// <param name="normalizationMode"></param>
public void SetDataNormalizationMode(DataNormalizationMode normalizationMode)
{
if (Symbol.SecurityType == SecurityType.Option && normalizationMode != DataNormalizationMode.Raw)
if ((Symbol.SecurityType == SecurityType.Option || Symbol.SecurityType == SecurityType.FutureOption) && normalizationMode != DataNormalizationMode.Raw)
{
throw new ArgumentException("DataNormalizationMode.Raw must be used with options");
throw new ArgumentException($"DataNormalizationMode.Raw must be used with SecurityType {Symbol.SecurityType}");
}
foreach (var config in this)

View File

@@ -212,6 +212,7 @@ namespace QuantConnect.Data
{SecurityType.Forex, new List<TickType> {TickType.Quote}},
{SecurityType.Equity, new List<TickType> {TickType.Trade, TickType.Quote}},
{SecurityType.Option, new List<TickType> {TickType.Quote, TickType.Trade, TickType.OpenInterest}},
{SecurityType.FutureOption, new List<TickType> {TickType.Quote, TickType.Trade, TickType.OpenInterest}},
{SecurityType.Cfd, new List<TickType> {TickType.Quote}},
{SecurityType.Future, new List<TickType> {TickType.Quote, TickType.Trade, TickType.OpenInterest}},
{SecurityType.Commodity, new List<TickType> {TickType.Trade}},

View File

@@ -167,6 +167,11 @@ namespace QuantConnect.Data.UniverseSelection
sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlying, market, 0, 0, 0);
break;
case SecurityType.FutureOption:
var underlyingFuture = SecurityIdentifier.GenerateFuture(SecurityIdentifier.DefaultDate, ticker, market);
sid = SecurityIdentifier.GenerateOption(SecurityIdentifier.DefaultDate, underlyingFuture, market, 0, 0, 0);
break;
case SecurityType.Forex:
sid = SecurityIdentifier.GenerateForex(ticker, market);
break;

View File

@@ -17,6 +17,7 @@ using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -49,6 +50,8 @@ using Timer = System.Timers.Timer;
using static QuantConnect.StringExtensions;
using Microsoft.IO;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.FutureOption;
using QuantConnect.Securities.Option;
namespace QuantConnect
@@ -64,6 +67,18 @@ namespace QuantConnect
private static readonly Dictionary<IntPtr, PythonActivator> PythonActivators
= new Dictionary<IntPtr, PythonActivator>();
/// <summary>
/// Safe multiplies a decimal by 100
/// </summary>
/// <param name="value">The decimal to multiply</param>
/// <returns>The result, maxed out at decimal.MaxValue</returns>
public static decimal SafeMultiply100(this decimal value)
{
const decimal max = decimal.MaxValue / 100m;
if (value >= max) return decimal.MaxValue;
return value * 100m;
}
/// <summary>
/// Will return a memory stream using the <see cref="RecyclableMemoryStreamManager"/> instance.
/// </summary>
@@ -103,6 +118,10 @@ namespace QuantConnect
{
Guids.Add(guid);
}
/// Unix epoch (1970-01-01 00:00:00.000000000Z)
/// </summary>
public static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
/// <summary>
/// Serialize a list of ticks using protobuf
@@ -640,6 +659,116 @@ namespace QuantConnect
list.Add(element);
}
/// <summary>
/// Adds the specified element to the collection with the specified key. If an entry does not exist for the
/// specified key then one will be created.
/// </summary>
/// <typeparam name="TKey">The key type</typeparam>
/// <typeparam name="TElement">The collection element type</typeparam>
/// <param name="dictionary">The source dictionary to be added to</param>
/// <param name="key">The key</param>
/// <param name="element">The element to be added</param>
public static ImmutableDictionary<TKey, ImmutableHashSet<TElement>> Add<TKey, TElement>(
this ImmutableDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet<TElement> set;
if (!dictionary.TryGetValue(key, out set))
{
set = ImmutableHashSet<TElement>.Empty.Add(element);
return dictionary.Add(key, set);
}
return dictionary.SetItem(key, set.Add(element));
}
/// <summary>
/// Adds the specified element to the collection with the specified key. If an entry does not exist for the
/// specified key then one will be created.
/// </summary>
/// <typeparam name="TKey">The key type</typeparam>
/// <typeparam name="TElement">The collection element type</typeparam>
/// <param name="dictionary">The source dictionary to be added to</param>
/// <param name="key">The key</param>
/// <param name="element">The element to be added</param>
public static ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> Add<TKey, TElement>(
this ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet<TElement> set;
if (!dictionary.TryGetValue(key, out set))
{
set = ImmutableHashSet<TElement>.Empty.Add(element);
return dictionary.Add(key, set);
}
return dictionary.SetItem(key, set.Add(element));
}
/// <summary>
/// Removes the specified element to the collection with the specified key. If the entry's count drops to
/// zero, then the entry will be removed.
/// </summary>
/// <typeparam name="TKey">The key type</typeparam>
/// <typeparam name="TElement">The collection element type</typeparam>
/// <param name="dictionary">The source dictionary to be added to</param>
/// <param name="key">The key</param>
/// <param name="element">The element to be added</param>
public static ImmutableDictionary<TKey, ImmutableHashSet<TElement>> Remove<TKey, TElement>(
this ImmutableDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet<TElement> set;
if (!dictionary.TryGetValue(key, out set))
{
return dictionary;
}
set = set.Remove(element);
if (set.Count == 0)
{
return dictionary.Remove(key);
}
return dictionary.SetItem(key, set);
}
/// <summary>
/// Removes the specified element to the collection with the specified key. If the entry's count drops to
/// zero, then the entry will be removed.
/// </summary>
/// <typeparam name="TKey">The key type</typeparam>
/// <typeparam name="TElement">The collection element type</typeparam>
/// <param name="dictionary">The source dictionary to be added to</param>
/// <param name="key">The key</param>
/// <param name="element">The element to be added</param>
public static ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> Remove<TKey, TElement>(
this ImmutableSortedDictionary<TKey, ImmutableHashSet<TElement>> dictionary,
TKey key,
TElement element
)
{
ImmutableHashSet<TElement> set;
if (!dictionary.TryGetValue(key, out set))
{
return dictionary;
}
set = set.Remove(element);
if (set.Count == 0)
{
return dictionary.Remove(key);
}
return dictionary.SetItem(key, set);
}
/// <summary>
/// Adds the specified Tick to the Ticks collection. If an entry does not exist for the specified key then one will be created.
/// </summary>
@@ -818,6 +947,26 @@ namespace QuantConnect
return new decimal(lo, mid, 0, isNegative, (byte)(hasDecimals ? decimalPlaces : 0));
}
/// <summary>
/// Extension method for faster string to normalized decimal conversion, i.e. 20.0% should be parsed into 0.2
/// </summary>
/// <param name="str">String to be converted to positive decimal value</param>
/// <remarks>
/// Leading and trailing whitespace chars are ignored
/// </remarks>
/// <returns>Decimal value of the string</returns>
public static decimal ToNormalizedDecimal(this string str)
{
var trimmed = str.Trim();
var value = str.TrimEnd('%').ToDecimal();
if (trimmed.EndsWith("%"))
{
value /= 100;
}
return value;
}
/// <summary>
/// Extension method for string to decimal conversion where string can represent a number with exponent xe-y
/// </summary>
@@ -1153,14 +1302,13 @@ namespace QuantConnect
/// <param name="to">The time zone to be converted to</param>
/// <param name="strict">True for strict conversion, this will throw during ambiguitities, false for lenient conversion</param>
/// <returns>The time in terms of the to time zone</returns>
public static DateTime ConvertTo(this DateTime time, DateTimeZone from, DateTimeZone to, bool strict = false)
public static DateTime ConvertTo(this DateTime time, DateTimeZone from, DateTimeZone to)
{
if (strict)
{
return from.AtStrictly(LocalDateTime.FromDateTime(time)).WithZone(to).ToDateTimeUnspecified();
}
var instant = new Instant(time.Ticks - UnixEpoch.Ticks);
var fromOffset = from.GetUtcOffset(instant).ToTimeSpan();
var toOffset = to.GetUtcOffset(instant).ToTimeSpan();
return from.AtLeniently(LocalDateTime.FromDateTime(time)).WithZone(to).ToDateTimeUnspecified();
return time - (fromOffset - toOffset);
}
/// <summary>
@@ -1170,11 +1318,12 @@ namespace QuantConnect
/// <param name="to">The destinatio time zone</param>
/// <param name="strict">True for strict conversion, this will throw during ambiguitities, false for lenient conversion</param>
/// <returns>The time in terms of the <paramref name="to"/> time zone</returns>
public static DateTime ConvertFromUtc(this DateTime time, DateTimeZone to, bool strict = false)
public static DateTime ConvertFromUtc(this DateTime time, DateTimeZone to)
{
return time.ConvertTo(TimeZones.Utc, to, strict);
return time + to.GetUtcOffset(new Instant(time.Ticks - UnixEpoch.Ticks)).ToTimeSpan();
}
/// <summary>
/// Converts the specified time from the <paramref name="from"/> time zone to <see cref="TimeZones.Utc"/>
/// </summary>
@@ -1182,14 +1331,9 @@ namespace QuantConnect
/// <param name="from">The time zone the specified <paramref name="time"/> is in</param>
/// <param name="strict">True for strict conversion, this will throw during ambiguitities, false for lenient conversion</param>
/// <returns>The time in terms of the to time zone</returns>
public static DateTime ConvertToUtc(this DateTime time, DateTimeZone from, bool strict = false)
public static DateTime ConvertToUtc(this DateTime time, DateTimeZone from)
{
if (strict)
{
return from.AtStrictly(LocalDateTime.FromDateTime(time)).ToDateTimeUtc();
}
return from.AtLeniently(LocalDateTime.FromDateTime(time)).ToDateTimeUtc();
return time.Subtract(from.GetUtcOffset(Instant.FromTicksSinceUnixEpoch((time.Ticks - UnixEpoch.Ticks))).ToTimeSpan());
}
/// <summary>
@@ -1512,6 +1656,7 @@ namespace QuantConnect
case SecurityType.Base:
case SecurityType.Equity:
case SecurityType.Option:
case SecurityType.FutureOption:
case SecurityType.Commodity:
case SecurityType.Forex:
case SecurityType.Future:
@@ -1559,6 +1704,8 @@ namespace QuantConnect
return "equity";
case SecurityType.Option:
return "option";
case SecurityType.FutureOption:
return "futureoption";
case SecurityType.Commodity:
return "commodity";
case SecurityType.Forex:
@@ -2181,6 +2328,27 @@ namespace QuantConnect
}
}
/// <summary>
/// Gets the delisting date for the provided Symbol
/// </summary>
/// <param name="symbol">The symbol to lookup the last trading date</param>
/// <param name="mapFile">Map file to use for delisting date. Defaults to SID.DefaultDate if no value is passed and is equity.</param>
/// <returns></returns>
public static DateTime GetDelistingDate(this Symbol symbol, MapFile mapFile = null)
{
switch (symbol.ID.SecurityType)
{
case SecurityType.Future:
return symbol.ID.Date;
case SecurityType.Option:
return OptionSymbol.GetLastDayOfTrading(symbol);
case SecurityType.FutureOption:
return FutureOptionSymbol.GetLastDayOfTrading(symbol);
default:
return mapFile?.DelistingDate ?? SecurityIdentifier.DefaultDate;
}
}
/// <summary>
/// Scale data based on factor function
/// </summary>
@@ -2202,6 +2370,7 @@ namespace QuantConnect
var securityType = data.Symbol.SecurityType;
if (securityType != SecurityType.Equity &&
securityType != SecurityType.Option &&
securityType != SecurityType.FutureOption &&
securityType != SecurityType.Future)
{
break;
@@ -2357,7 +2526,7 @@ namespace QuantConnect
/// <returns><see cref="OptionChainUniverse"/> for the given symbol</returns>
public static OptionChainUniverse CreateOptionChain(this IAlgorithm algorithm, Symbol symbol, Func<OptionFilterUniverse, OptionFilterUniverse> filter, UniverseSettings universeSettings = null)
{
if (symbol.SecurityType != SecurityType.Option)
if (symbol.SecurityType != SecurityType.Option && symbol.SecurityType != SecurityType.FutureOption)
{
throw new ArgumentException("CreateOptionChain requires an option symbol.");
}
@@ -2367,8 +2536,20 @@ namespace QuantConnect
var underlying = symbol.Underlying;
if (!symbol.IsCanonical())
{
// The underlying can be a non-equity Symbol, so we must explicitly
// initialize the Symbol using the CreateOption(Symbol, ...) overload
// to ensure that the underlying SecurityType is preserved and not
// written as SecurityType.Equity.
var alias = $"?{underlying.Value}";
symbol = Symbol.Create(underlying.Value, SecurityType.Option, market, alias);
symbol = Symbol.CreateOption(
underlying,
market,
default(OptionStyle),
default(OptionRight),
0m,
SecurityIdentifier.DefaultDate,
alias);
}
// resolve defaults if not specified
@@ -2401,5 +2582,32 @@ namespace QuantConnect
return new OptionChainUniverse(optionChain, settings, algorithm.LiveMode);
}
/// <summary>
/// Inverts the specified <paramref name="right"/>
/// </summary>
public static OptionRight Invert(this OptionRight right)
{
switch (right)
{
case OptionRight.Call: return OptionRight.Put;
case OptionRight.Put: return OptionRight.Call;
default:
throw new ArgumentOutOfRangeException(nameof(right), right, null);
}
}
/// <summary>
/// Compares two values using given operator
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="op">Comparison operator</param>
/// <param name="arg1">The first value</param>
/// <param name="arg2">The second value</param>
/// <returns>Returns true if its left-hand operand meets the operator value to its right-hand operand, false otherwise</returns>
public static bool Compare<T>(this ComparisonOperatorTypes op, T arg1, T arg2) where T : IComparable
{
return ComparisonOperator.Compare(op, arg1, arg2);
}
}
}

View File

@@ -313,7 +313,19 @@ namespace QuantConnect
/// <summary>
/// Cryptocurrency Security Type.
/// </summary>
Crypto
Crypto,
/// <summary>
/// Futures Options Security Type.
/// </summary>
/// <remarks>
/// Futures options function similar to equity options, but with a few key differences.
/// Firstly, the contract unit of trade is 1x, rather than 100x. This means that each
/// option represents the right to buy or sell 1 future contract at expiry/exercise.
/// The contract multiplier for Futures Options plays a big part in determining the premium
/// of the option, which can also differ from the underlying future's multiplier.
/// </remarks>
FutureOption
}
/// <summary>
@@ -444,6 +456,27 @@ namespace QuantConnect
Daily
}
/// <summary>
/// Specifies what side a position is on, long/short
/// </summary>
public enum PositionSide
{
/// <summary>
/// A short position, quantity less than zero
/// </summary>
Short = -1,
/// <summary>
/// No position, quantity equals zero
/// </summary>
None = 0,
/// <summary>
/// A long position, quantity greater than zero
/// </summary>
Long = 1
}
/// <summary>
/// Specifies the different types of options
/// </summary>

View File

@@ -26,13 +26,11 @@ namespace QuantConnect.Interfaces
/// <summary>
/// Method returns a collection of Symbols that are available at the data source.
/// </summary>
/// <param name="lookupName">String representing the name to lookup</param>
/// <param name="securityType">Expected security type of the returned symbols (if any)</param>
/// <param name="symbol">Symbol to lookup</param>
/// <param name="includeExpired">Include expired contracts</param>
/// <param name="securityCurrency">Expected security currency(if any)</param>
/// <param name="securityExchange">Expected security exchange name(if any)</param>
/// <returns></returns>
IEnumerable<Symbol> LookupSymbols(string lookupName, SecurityType securityType, bool includeExpired, string securityCurrency = null, string securityExchange = null);
/// <returns>Enumerable of Symbols, that are associated with the provided Symbol</returns>
IEnumerable<Symbol> LookupSymbols(Symbol symbol, bool includeExpired, string securityCurrency = null);
/// <summary>
/// Returns whether the time can be advanced or not.

View File

@@ -36,6 +36,7 @@ namespace QuantConnect.Orders.Fees
{SecurityType.Forex, 0.000002m},
// Commission plus clearing fee
{SecurityType.Future, 0.4m + 0.1m},
{SecurityType.FutureOption, 0.4m + 0.1m},
{SecurityType.Option, 0.4m + 0.1m},
{SecurityType.Cfd, 0m}
};
@@ -62,11 +63,12 @@ namespace QuantConnect.Orders.Fees
var market = security.Symbol.ID.Market;
decimal feeRate;
switch (security.Type)
{
case SecurityType.Option:
case SecurityType.Future:
case SecurityType.FutureOption:
case SecurityType.Cfd:
_feeRates.TryGetValue(security.Type, out feeRate);
return new OrderFee(new CashAmount(feeRate * order.AbsoluteQuantity, Currencies.USD));
@@ -125,9 +127,9 @@ namespace QuantConnect.Orders.Fees
{
tradeFee = maximumPerOrder;
}
return new OrderFee(new CashAmount(Math.Abs(tradeFee), equityFee.Currency));
default:
// unsupported security type
throw new ArgumentException(Invariant($"Unsupported security type: {security.Type}"));
@@ -156,4 +158,4 @@ namespace QuantConnect.Orders.Fees
}
}
}
}
}

View File

@@ -68,8 +68,9 @@ namespace QuantConnect.Orders.Fees
{
var optionOrder = (OptionExerciseOrder)order;
if (optionOrder.Symbol.ID.SecurityType == SecurityType.Option &&
optionOrder.Symbol.ID.Underlying.SecurityType == SecurityType.Equity)
// For Futures Options, contracts are charged the standard commission at expiration of the contract.
// Read more here: https://www1.interactivebrokers.com/en/index.php?f=14718#trading-related-fees
if (optionOrder.Symbol.ID.SecurityType == SecurityType.Option)
{
return OrderFee.Zero;
}
@@ -102,6 +103,8 @@ namespace QuantConnect.Orders.Fees
break;
case SecurityType.Future:
case SecurityType.FutureOption:
// The futures options fee model is exactly the same as futures' fees on IB.
if (market == Market.Globex || market == Market.NYMEX
|| market == Market.CBOT || market == Market.ICE
|| market == Market.CBOE || market == Market.COMEX

View File

@@ -251,7 +251,7 @@ namespace QuantConnect.Orders
message += Invariant($" Message: {Message}");
}
if (Symbol.SecurityType == SecurityType.Option)
if (Symbol.SecurityType == SecurityType.Option || Symbol.SecurityType == SecurityType.FutureOption)
{
message += Invariant($" IsAssignment: {IsAssignment}");
}

View File

@@ -67,6 +67,7 @@ namespace QuantConnect.Orders.TimeInForces
case SecurityType.Equity:
case SecurityType.Option:
case SecurityType.Future:
case SecurityType.FutureOption:
default:
// expires at market close
expired = time >= exchangeHours.GetNextMarketClose(orderTime, false);

View File

@@ -76,6 +76,7 @@ namespace QuantConnect.Orders.TimeInForces
case SecurityType.Equity:
case SecurityType.Option:
case SecurityType.Future:
case SecurityType.FutureOption:
default:
// expires at market close of expiry date
expired = time >= exchangeHours.GetNextMarketClose(Expiry.Date, false);

View File

@@ -143,6 +143,12 @@ namespace QuantConnect.Packets
[JsonProperty(PropertyName = "dataResolutionPermissions")]
public HashSet<Resolution> DataResolutionPermissions;
/// <summary>
/// The cost associated with running this job
/// </summary>
[JsonProperty(PropertyName = "iCreditCost")]
public uint CreditCost;
/// <summary>
/// Initializes a new default instance of the <see cref="Controls"/> class
/// </summary>

View File

@@ -153,6 +153,15 @@ namespace QuantConnect.Packets
AlphaHeartbeat,
/// Used when debugging to send status updates
DebuggingStatus
DebuggingStatus,
/// Optimization Node Packet:
OptimizationNode,
/// Optimization Estimate Packet:
OptimizationEstimate,
/// Optimization work status update
OptimizationStatus
}
}

View File

@@ -87,6 +87,12 @@ namespace QuantConnect.Parameters
string parameterValue;
if (!parameters.TryGetValue(parameterName, out parameterValue)) continue;
if (string.IsNullOrEmpty(parameterValue))
{
Log.Error($"ParameterAttribute.ApplyAttributes(): parameter '{parameterName}' provided value is null/empty, skipping");
continue;
}
// if it's a read-only property with a parameter value we can't really do anything, bail
if (propertyInfo != null && !propertyInfo.CanWrite)
{

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.Buffers;
using System.Collections.Generic;
using System.Linq;
using Apache.Arrow.Memory;
namespace QuantConnect.Python
{
public class PandasArrowMemoryAllocator : NativeMemoryAllocator, IDisposable
{
private bool _disposed;
private readonly List<PandasMemoryOwner> _free = new List<PandasMemoryOwner>();
private readonly List<PandasMemoryOwner> _used = new List<PandasMemoryOwner>();
public PandasArrowMemoryAllocator() : base()
{
}
protected override IMemoryOwner<byte> AllocateInternal(int length, out int bytesAllocated)
{
PandasMemoryOwner owner;
var memoryResizeIndexes = new List<KeyValuePair<int, int>>();
for (var i = 0; i < _free.Count; i++)
{
var memory = _free[i];
if (length > memory.Original.Memory.Length)
{
memoryResizeIndexes.Add(new KeyValuePair<int, int>(i, memory.Original.Memory.Length));
continue;
}
owner = memory;
bytesAllocated = 0;
_free.Remove(owner);
_used.Add(owner);
owner.Reset();
if (length != memory.Original.Memory.Length)
{
owner.Slice(0, length);
}
return owner;
}
if (memoryResizeIndexes.Count != 0)
{
// Get the smallest resizable instance, and reallocate a larger buffer.
var resizeIndex = memoryResizeIndexes.OrderBy(x => x.Value).First();
var resizable = _free[resizeIndex.Key];
resizable.Resize(base.AllocateInternal(length, out bytesAllocated));
_used.Add(resizable);
_free.RemoveAt(resizeIndex.Key);
return resizable;
}
// New allocation, should only be called a few times when we start using the allocator
owner = new PandasMemoryOwner(base.AllocateInternal(length, out bytesAllocated));
_used.Add(owner);
return owner;
}
/// <summary>
/// Frees the underlying memory buffers so that they can be re-used
/// </summary>
public void Free()
{
foreach (var used in _used)
{
_free.Add(used);
}
_used.Clear();
}
private class PandasMemoryOwner : IMemoryOwner<byte>
{
private bool _disposed;
/// <summary>
/// Original memory owner containing the full-length byte-array
/// we initially allocated.
/// </summary>
public IMemoryOwner<byte> Original { get; private set; }
/// <summary>
/// Slice of the original memory owner containing the contents of
/// the buffer Arrow will use. We slice the original memory so
/// that Arrow doesn't panic when it receives a slice with a length
/// longer than it expects when serializing its internal buffer.
/// </summary>
public Memory<byte> Memory { get; private set; }
public PandasMemoryOwner(IMemoryOwner<byte> memory)
{
Original = memory;
Memory = Original.Memory;
}
/// <summary>
/// Creates a slice of the original MemoryOwner and stores the result in <see cref="Memory"/>
/// </summary>
/// <param name="start">Index start of the slice</param>
/// <param name="length">Length of the slice</param>
public void Slice(int start, int length)
{
Memory = Original.Memory.Slice(start, length);
}
/// <summary>
/// Restores the <see cref="Memory"/> slice to its initial value
/// </summary>
public void Reset()
{
Memory = null;
Memory = Original.Memory;
}
/// <summary>
/// Resizes the instance to the new memory size
/// </summary>
/// <param name="newMemory"></param>
public void Resize(IMemoryOwner<byte> newMemory)
{
Original.Dispose();
Original = newMemory;
Memory = null;
Memory = Original.Memory;
}
public void Free()
{
Original.Dispose();
Memory = null;
Original = null;
}
/// <summary>
/// no-op dispose because we want to re-use the MemoryOwner instance after we dispose of a RecordBatch.
/// To dispose of the resources this class owns, use <see cref="Free"/>
/// </summary>
public void Dispose()
{
}
}
public void Dispose()
{
if (_disposed)
{
throw new ObjectDisposedException("PandasArrowMemoryAllocator has already been disposed");
}
foreach (var free in _free)
{
free.Free();
}
foreach (var used in _used)
{
used.Free();
}
_free.Clear();
_used.Clear();
_disposed = true;
}
}
}

1531
Common/Python/PandasConverter.cs Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,609 +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 Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Util;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace QuantConnect.Python
{
/// <summary>
/// Organizes a list of data to create pandas.DataFrames
/// </summary>
public class PandasData
{
private static dynamic _pandas;
private readonly static HashSet<string> _baseDataProperties = typeof(BaseData).GetProperties().ToHashSet(x => x.Name.ToLowerInvariant());
private readonly static ConcurrentDictionary<Type, List<MemberInfo>> _membersByType = new ConcurrentDictionary<Type, List<MemberInfo>>();
private readonly Symbol _symbol;
private readonly Dictionary<string, Tuple<List<DateTime>, List<object>>> _series;
private readonly List<MemberInfo> _members;
/// <summary>
/// Gets true if this is a custom data request, false for normal QC data
/// </summary>
public bool IsCustomData { get; }
/// <summary>
/// Implied levels of a multi index pandas.Series (depends on the security type)
/// </summary>
public int Levels { get; } = 2;
/// <summary>
/// Initializes an instance of <see cref="PandasData"/>
/// </summary>
public PandasData(object data)
{
if (_pandas == null)
{
using (Py.GIL())
{
// this python Remapper class will work as a proxy and adjust the
// input to its methods using the provided 'mapper' callable object
_pandas = PythonEngine.ModuleFromString("remapper",
@"import pandas as pd
from pandas.core.resample import Resampler, DatetimeIndexResampler, PeriodIndexResampler, TimedeltaIndexResampler
from pandas.core.groupby.generic import DataFrameGroupBy, SeriesGroupBy
from pandas.core.indexes.frozen import FrozenList as pdFrozenList
from pandas.core.window import Expanding, EWM, Rolling, Window
from pandas.core.computation.ops import UndefinedVariableError
from inspect import getmembers, isfunction, isgenerator
from functools import partial
from sys import modules
from clr import AddReference
AddReference(""QuantConnect.Common"")
from QuantConnect import *
def mapper(key):
'''Maps a Symbol object or a Symbol Ticker (string) to the string representation of
Symbol SecurityIdentifier. If cannot map, returns the object
'''
keyType = type(key)
if keyType is Symbol:
return str(key.ID)
if keyType is str:
kvp = SymbolCache.TryGetSymbol(key, None)
if kvp[0]:
return str(kvp[1].ID)
if keyType is list:
return [mapper(x) for x in key]
if keyType is tuple:
return tuple([mapper(x) for x in key])
if keyType is dict:
return {k:mapper(v) for k,v in key.items()}
return key
def try_wrap_as_index(obj):
'''Tries to wrap object if it is one of pandas' index objects.'''
objType = type(obj)
if objType is pd.Index:
return True, Index(obj)
if objType is pd.MultiIndex:
result = object.__new__(MultiIndex)
result._set_levels(obj.levels, copy=obj.copy, validate=False)
result._set_codes(obj.codes, copy=obj.copy, validate=False)
result._set_names(obj.names)
result.sortorder = obj.sortorder
return True, result
if objType is pdFrozenList:
return True, FrozenList(obj)
return False, obj
def try_wrap_as_pandas(obj):
'''Tries to wrap object if it is a pandas' object.'''
success, obj = try_wrap_as_index(obj)
if success:
return success, obj
objType = type(obj)
if objType is pd.DataFrame:
return True, DataFrame(data=obj)
if objType is pd.Series:
return True, Series(data=obj)
if objType is tuple:
anySuccess = False
results = list()
for item in obj:
success, result = try_wrap_as_pandas(item)
anySuccess |= success
results.append(result)
if anySuccess:
return True, tuple(results)
return False, obj
def try_wrap_resampler(obj, self):
'''Tries to wrap object if it is a pandas' Resampler object.'''
if not isinstance(obj, Resampler):
return False, obj
klass = CreateWrapperClass(type(obj))
return True, klass(self, groupby=obj.groupby, kind=obj.kind, axis=obj.axis)
def wrap_function(f):
'''Wraps function f with g.
Function g converts the args/kwargs to use alternative index keys
and the result of the f function call to the wrapper objects
'''
def g(*args, **kwargs):
if len(args) > 1:
args = mapper(args)
if len(kwargs) > 0:
kwargs = mapper(kwargs)
try:
result = f(*args, **kwargs)
except UndefinedVariableError as e:
# query/eval methods needs to look for a scope variable at a higher level
# since the wrapper classes are children of pandas classes
kwargs['level'] = kwargs.pop('level', 0) + 1
result = f(*args, **kwargs)
success, result = try_wrap_as_pandas(result)
if success:
return result
success, result = try_wrap_resampler(result, args[0])
if success:
return result
if isgenerator(result):
return ( (k, try_wrap_as_pandas(v)[1]) for k, v in result)
return result
g.__name__ = f.__name__
return g
def wrap_special_function(name, cls, fcls, gcls = None):
'''Replaces the special function of a given class by g that wraps fcls
This is how pandas implements them.
gcls represents an alternative for fcls
if the keyword argument has 'win_type' key for the Rolling/Window case
'''
fcls = CreateWrapperClass(fcls)
if gcls is not None:
gcls = CreateWrapperClass(fcls)
def g(*args, **kwargs):
if kwargs.get('win_type', None):
return gcls(*args, **kwargs)
return fcls(*args, **kwargs)
g.__name__ = name
setattr(cls, g.__name__, g)
def CreateWrapperClass(cls: type):
'''Creates wrapper classes.
Members of the original class are wrapped to allow alternative index look-up
'''
# Define a new class
klass = type(f'{cls.__name__}', (cls,) + cls.__bases__, dict(cls.__dict__))
def g(self, name):
'''Wrap '__getattribute__' to handle indices
Only need to wrap columns, index and levels attributes
'''
attr = object.__getattribute__(self, name)
if name in ['columns', 'index', 'levels']:
_, attr = try_wrap_as_index(attr)
return attr
g.__name__ = '__getattribute__'
g.__qualname__ = g.__name__
setattr(klass, g.__name__, g)
def wrap_union(f):
'''Wraps function f (union) with g.
Special case: The union method from index objects needs to
receive pandas' index objects to avoid infity recursion.
Function g converts the args/kwargs objects to one of pandas index objects
and the result of the f function call back to wrapper indexes objects
'''
def unwrap_index(obj):
'''Tries to unwrap object if it is one of this module wrapper's index objects.'''
objType = type(obj)
if objType is Index:
return pd.Index(obj)
if objType is MultiIndex:
result = object.__new__(pd.MultiIndex)
result._set_levels(obj.levels, copy=obj.copy, validate=False)
result._set_codes(obj.codes, copy=obj.copy, validate=False)
result._set_names(obj.names)
result.sortorder = obj.sortorder
return result
if objType is FrozenList:
return pdFrozenList(obj)
return obj
def g(*args, **kwargs):
args = tuple([unwrap_index(x) for x in args])
result = f(*args, **kwargs)
_, result = try_wrap_as_index(result)
return result
g.__name__ = f.__name__
return g
# We allow the wraopping of slot methods that are not inherited from object
# It will include operation methods like __add__ and __contains__
allow_list = set(x for x in dir(klass) if x.startswith('__')) - set(dir(object))
# Wrap class members of the newly created class
for name, member in getmembers(klass):
if name.startswith('_') and name not in allow_list:
continue
if isfunction(member):
if name == 'union':
member = wrap_union(member)
else:
member = wrap_function(member)
setattr(klass, name, member)
elif type(member) is property:
if type(member.fget) is partial:
func = CreateWrapperClass(member.fget.func)
fget = partial(func, name)
else:
fget = wrap_function(member.fget)
member = property(fget, member.fset, member.fdel, member.__doc__)
setattr(klass, name, member)
return klass
FrozenList = CreateWrapperClass(pdFrozenList)
Index = CreateWrapperClass(pd.Index)
MultiIndex = CreateWrapperClass(pd.MultiIndex)
Series = CreateWrapperClass(pd.Series)
DataFrame = CreateWrapperClass(pd.DataFrame)
wrap_special_function('groupby', Series, SeriesGroupBy)
wrap_special_function('groupby', DataFrame, DataFrameGroupBy)
wrap_special_function('ewm', Series, EWM)
wrap_special_function('ewm', DataFrame, EWM)
wrap_special_function('expanding', Series, Expanding)
wrap_special_function('expanding', DataFrame, Expanding)
wrap_special_function('rolling', Series, Rolling, Window)
wrap_special_function('rolling', DataFrame, Rolling, Window)
CreateSeries = pd.Series
setattr(modules[__name__], 'concat', wrap_function(pd.concat))");
}
}
var enumerable = data as IEnumerable;
if (enumerable != null)
{
foreach (var item in enumerable)
{
data = item;
}
}
var type = data.GetType();
IsCustomData = type.Namespace != typeof(Bar).Namespace;
_members = new List<MemberInfo>();
_symbol = ((IBaseData)data).Symbol;
if (_symbol.SecurityType == SecurityType.Future) Levels = 3;
if (_symbol.SecurityType == SecurityType.Option) Levels = 5;
var columns = new HashSet<string>
{
"open", "high", "low", "close", "lastprice", "volume",
"askopen", "askhigh", "asklow", "askclose", "askprice", "asksize", "quantity", "suspicious",
"bidopen", "bidhigh", "bidlow", "bidclose", "bidprice", "bidsize", "exchange", "openinterest"
};
if (IsCustomData)
{
var keys = (data as DynamicData)?.GetStorageDictionary().ToHashSet(x => x.Key);
// C# types that are not DynamicData type
if (keys == null)
{
if (_membersByType.TryGetValue(type, out _members))
{
keys = _members.ToHashSet(x => x.Name.ToLowerInvariant());
}
else
{
var members = type.GetMembers().Where(x => x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property).ToList();
var duplicateKeys = members.GroupBy(x => x.Name.ToLowerInvariant()).Where(x => x.Count() > 1).Select(x => x.Key);
foreach (var duplicateKey in duplicateKeys)
{
throw new ArgumentException($"PandasData.ctor(): More than one \'{duplicateKey}\' member was found in \'{type.FullName}\' class.");
}
// If the custom data derives from a Market Data (e.g. Tick, TradeBar, QuoteBar), exclude its keys
keys = members.ToHashSet(x => x.Name.ToLowerInvariant());
keys.ExceptWith(_baseDataProperties);
keys.ExceptWith(GetPropertiesNames(typeof(QuoteBar), type));
keys.ExceptWith(GetPropertiesNames(typeof(TradeBar), type));
keys.ExceptWith(GetPropertiesNames(typeof(Tick), type));
keys.Add("value");
_members = members.Where(x => keys.Contains(x.Name.ToLowerInvariant())).ToList();
_membersByType.TryAdd(type, _members);
}
}
columns.Add("value");
columns.UnionWith(keys);
}
_series = columns.ToDictionary(k => k, v => Tuple.Create(new List<DateTime>(), new List<object>()));
}
/// <summary>
/// Adds security data object to the end of the lists
/// </summary>
/// <param name="baseData"><see cref="IBaseData"/> object that contains security data</param>
public void Add(object baseData)
{
foreach (var member in _members)
{
var key = member.Name.ToLowerInvariant();
var endTime = ((IBaseData) baseData).EndTime;
var propertyMember = member as PropertyInfo;
if (propertyMember != null)
{
AddToSeries(key, endTime, propertyMember.GetValue(baseData));
continue;
}
var fieldMember = member as FieldInfo;
if (fieldMember != null)
{
AddToSeries(key, endTime, fieldMember.GetValue(baseData));
}
}
var storage = (baseData as DynamicData)?.GetStorageDictionary();
if (storage != null)
{
var endTime = ((IBaseData) baseData).EndTime;
var value = ((IBaseData) baseData).Value;
AddToSeries("value", endTime, value);
foreach (var kvp in storage)
{
AddToSeries(kvp.Key, endTime, kvp.Value);
}
}
else
{
var ticks = new List<Tick> { baseData as Tick };
var tradeBar = baseData as TradeBar;
var quoteBar = baseData as QuoteBar;
Add(ticks, tradeBar, quoteBar);
}
}
/// <summary>
/// Adds Lean data objects to the end of the lists
/// </summary>
/// <param name="ticks">List of <see cref="Tick"/> object that contains tick information of the security</param>
/// <param name="tradeBar"><see cref="TradeBar"/> object that contains trade bar information of the security</param>
/// <param name="quoteBar"><see cref="QuoteBar"/> object that contains quote bar information of the security</param>
public void Add(IEnumerable<Tick> ticks, TradeBar tradeBar, QuoteBar quoteBar)
{
if (tradeBar != null)
{
var time = tradeBar.EndTime;
AddToSeries("open", time, tradeBar.Open);
AddToSeries("high", time, tradeBar.High);
AddToSeries("low", time, tradeBar.Low);
AddToSeries("close", time, tradeBar.Close);
AddToSeries("volume", time, tradeBar.Volume);
}
if (quoteBar != null)
{
var time = quoteBar.EndTime;
if (tradeBar == null)
{
AddToSeries("open", time, quoteBar.Open);
AddToSeries("high", time, quoteBar.High);
AddToSeries("low", time, quoteBar.Low);
AddToSeries("close", time, quoteBar.Close);
}
if (quoteBar.Ask != null)
{
AddToSeries("askopen", time, quoteBar.Ask.Open);
AddToSeries("askhigh", time, quoteBar.Ask.High);
AddToSeries("asklow", time, quoteBar.Ask.Low);
AddToSeries("askclose", time, quoteBar.Ask.Close);
AddToSeries("asksize", time, quoteBar.LastAskSize);
}
if (quoteBar.Bid != null)
{
AddToSeries("bidopen", time, quoteBar.Bid.Open);
AddToSeries("bidhigh", time, quoteBar.Bid.High);
AddToSeries("bidlow", time, quoteBar.Bid.Low);
AddToSeries("bidclose", time, quoteBar.Bid.Close);
AddToSeries("bidsize", time, quoteBar.LastBidSize);
}
}
if (ticks != null)
{
foreach (var tick in ticks)
{
if (tick == null) continue;
var time = tick.EndTime;
var column = tick.TickType == TickType.OpenInterest
? "openinterest"
: "lastprice";
if (tick.TickType == TickType.Quote)
{
AddToSeries("askprice", time, tick.AskPrice);
AddToSeries("asksize", time, tick.AskSize);
AddToSeries("bidprice", time, tick.BidPrice);
AddToSeries("bidsize", time, tick.BidSize);
}
AddToSeries("exchange", time, tick.Exchange);
AddToSeries("suspicious", time, tick.Suspicious);
AddToSeries("quantity", time, tick.Quantity);
AddToSeries(column, time, tick.LastPrice);
}
}
}
/// <summary>
/// Get the pandas.DataFrame of the current <see cref="PandasData"/> state
/// </summary>
/// <param name="levels">Number of levels of the multi index</param>
/// <returns>pandas.DataFrame object</returns>
public PyObject ToPandasDataFrame(int levels = 2)
{
var empty = new PyString(string.Empty);
var list = Enumerable.Repeat<PyObject>(empty, 5).ToList();
list[3] = _symbol.ID.ToString().ToPython();
if (_symbol.SecurityType == SecurityType.Future)
{
list[0] = _symbol.ID.Date.ToPython();
list[3] = _symbol.ID.ToString().ToPython();
}
if (_symbol.SecurityType == SecurityType.Option)
{
list[0] = _symbol.ID.Date.ToPython();
list[1] = _symbol.ID.StrikePrice.ToPython();
list[2] = _symbol.ID.OptionRight.ToString().ToPython();
list[3] = _symbol.ID.ToString().ToPython();
}
// Create the index labels
var names = "expiry,strike,type,symbol,time";
if (levels == 2)
{
names = "symbol,time";
list.RemoveRange(0, 3);
}
if (levels == 3)
{
names = "expiry,symbol,time";
list.RemoveRange(1, 2);
}
Func<object, bool> filter = x =>
{
var isNaNOrZero = x is double && ((double)x).IsNaNOrZero();
var isNullOrWhiteSpace = x is string && string.IsNullOrWhiteSpace((string)x);
var isFalse = x is bool && !(bool)x;
return x == null || isNaNOrZero || isNullOrWhiteSpace || isFalse;
};
Func<DateTime, PyTuple> selector = x =>
{
list[list.Count - 1] = x.ToPython();
return new PyTuple(list.ToArray());
};
// creating the pandas MultiIndex is expensive so we keep a cash
var indexCache = new Dictionary<List<DateTime>, dynamic>(new ListComparer<DateTime>());
using (Py.GIL())
{
// Returns a dictionary keyed by column name where values are pandas.Series objects
var pyDict = new PyDict();
var splitNames = names.Split(',');
foreach (var kvp in _series)
{
var values = kvp.Value.Item2;
if (values.All(filter)) continue;
dynamic index;
if (!indexCache.TryGetValue(kvp.Value.Item1, out index))
{
var tuples = kvp.Value.Item1.Select(selector).ToArray();
index = _pandas.MultiIndex.from_tuples(tuples, names: splitNames);
indexCache[kvp.Value.Item1] = index;
}
// Adds pandas.Series value keyed by the column name
// CreateSeries will create an original pandas.Series
// We are not using the wrapper class to avoid unnecessary and expensive
// index wrapping operations when the Series are packed into a DataFrame
pyDict.SetItem(kvp.Key, _pandas.CreateSeries(values, index));
}
_series.Clear();
// Create a DataFrame with wrapper class.
// This is the starting point. The types of all DataFrame and Series that result from any operation will
// be wrapper classes. Index and MultiIndex will be converted when required by index operations such as
// stack, unstack, merge, union, etc.
return _pandas.DataFrame(pyDict);
}
}
/// <summary>
/// Adds data to dictionary
/// </summary>
/// <param name="key">The key of the value to get</param>
/// <param name="time"><see cref="DateTime"/> object to add to the value associated with the specific key</param>
/// <param name="input"><see cref="Object"/> to add to the value associated with the specific key. Can be null.</param>
private void AddToSeries(string key, DateTime time, object input)
{
Tuple<List<DateTime>, List<object>> value;
if (_series.TryGetValue(key, out value))
{
value.Item1.Add(time);
value.Item2.Add(input is decimal ? input.ConvertInvariant<double>() : input);
}
else
{
throw new ArgumentException($"PandasData.AddToSeries(): {key} key does not exist in series dictionary.");
}
}
/// <summary>
/// Get the lower-invariant name of properties of the type that a another type is assignable from
/// </summary>
/// <param name="baseType">The type that is assignable from</param>
/// <param name="type">The type that is assignable by</param>
/// <returns>List of string. Empty list if not assignable from</returns>
private static IEnumerable<string> GetPropertiesNames(Type baseType, Type type)
{
return baseType.IsAssignableFrom(type)
? baseType.GetProperties().Select(x => x.Name.ToLowerInvariant())
: Enumerable.Empty<string>();
}
}
}

View File

@@ -25,6 +25,7 @@
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<CodeAnalysisRuleSet>..\QuantConnect.ruleset</CodeAnalysisRuleSet>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -106,6 +107,9 @@
<CodeAnalysisRuleSet>..\QuantConnect.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Apache.Arrow, Version=2.0.0.0, Culture=neutral, PublicKeyToken=204f54e5a45c07df, processorArchitecture=MSIL">
<HintPath>..\packages\gsalaz98.Unofficial.Apache.Arrow.2.0.1-SNAPSHOT\lib\net461\Apache.Arrow.dll</HintPath>
</Reference>
<Reference Include="CloneExtensions, Version=1.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\CloneExtensions.1.3.0\lib\net461\CloneExtensions.dll</HintPath>
</Reference>
@@ -124,6 +128,11 @@
<Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.3.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IO.RecyclableMemoryStream.1.3.5\lib\net46\Microsoft.IO.RecyclableMemoryStream.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
@@ -166,6 +175,10 @@
<Reference Include="System.ServiceModel.Primitives, Version=4.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.ServiceModel.Primitives.4.7.0\lib\net461\System.ServiceModel.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@@ -235,6 +248,7 @@
<Compile Include="Algorithm\Framework\Alphas\InsightScoreType.cs" />
<Compile Include="Algorithm\Framework\Portfolio\PortfolioTargetCollection.cs" />
<Compile Include="AlphaRuntimeStatistics.cs" />
<Compile Include="Api\Account.cs" />
<Compile Include="Api\AuthenticationResponse.cs" />
<Compile Include="Api\Backtest.cs" />
<Compile Include="Api\Compile.cs" />
@@ -256,6 +270,8 @@
<Compile Include="Benchmarks\FuncBenchmark.cs" />
<Compile Include="Benchmarks\IBenchmark.cs" />
<Compile Include="Benchmarks\SecurityBenchmark.cs" />
<Compile Include="BinaryComparison.cs" />
<Compile Include="BinaryComparisonExtensions.cs" />
<Compile Include="Brokerages\AlpacaBrokerageModel.cs" />
<Compile Include="Brokerages\AlphaStreamsBrokerageModel.cs" />
<Compile Include="Brokerages\BinanceBrokerageModel.cs" />
@@ -398,6 +414,7 @@
<Compile Include="Packets\LeakyBucketControlParameters.cs" />
<Compile Include="Packets\LiveResultParameters.cs" />
<Compile Include="Python\BrokerageMessageHandlerPythonWrapper.cs" />
<Compile Include="Python\PandasArrowMemoryAllocator.cs" />
<Compile Include="Python\PythonConsolidator.cs" />
<Compile Include="Python\MarginCallModelPythonWrapper.cs" />
<Compile Include="Python\PythonInitializer.cs" />
@@ -411,8 +428,41 @@
<Compile Include="Securities\BuyingPowerModelExtensions.cs" />
<Compile Include="Securities\BuyingPowerParameters.cs" />
<Compile Include="Securities\ContractSecurityFilterUniverse.cs" />
<Compile Include="Securities\FutureOption\Api\CMEOptionChainQuotes.cs" />
<Compile Include="Securities\FutureOption\Api\CMEOptionsCategoryList.cs" />
<Compile Include="Securities\FutureOption\Api\CMEProductSlateV2.cs" />
<Compile Include="Securities\FutureOption\Api\CMEStrikePriceScalingFactors.cs" />
<Compile Include="Securities\FutureOption\FutureOption.cs" />
<Compile Include="Securities\FutureOption\FutureOptionSymbol.cs" />
<Compile Include="Securities\FutureOption\FuturesOptionsMarginModel.cs" />
<Compile Include="Securities\FutureOption\FuturesOptionsExpiryFunctions.cs" />
<Compile Include="Securities\FutureOption\FuturesOptionsSymbolMappings.cs" />
<Compile Include="Securities\Future\FutureSymbol.cs" />
<Compile Include="Securities\GetMaximumOrderQuantityForDeltaBuyingPowerParameters.cs" />
<Compile Include="Securities\Option\StrategyMatcher\AbsoluteRiskOptionPositionCollectionEnumerator.cs" />
<Compile Include="Securities\Option\StrategyMatcher\ConstantOptionStrategyLegPredicateReferenceValue.cs" />
<Compile Include="Securities\Option\StrategyMatcher\DefaultOptionPositionCollectionEnumerator.cs" />
<Compile Include="Securities\Option\StrategyMatcher\DescendingByLegCountOptionStrategyDefinitionEnumerator.cs" />
<Compile Include="Securities\Option\StrategyMatcher\FunctionalOptionPositionCollectionEnumerator.cs" />
<Compile Include="Securities\Option\StrategyMatcher\IdentityOptionStrategyDefinitionEnumerator.cs" />
<Compile Include="Securities\Option\StrategyMatcher\IOptionPositionCollectionEnumerator.cs" />
<Compile Include="Securities\Option\StrategyMatcher\IOptionStrategyDefinitionEnumerator.cs" />
<Compile Include="Securities\Option\StrategyMatcher\IOptionStrategyLegPredicateReferenceValue.cs" />
<Compile Include="Securities\Option\StrategyMatcher\IOptionStrategyMatchObjectiveFunction.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyDefinition.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyDefinitionMatch.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyDefinitions.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegDefinitionMatch.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegPredicateReferenceValue.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionPosition.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionPositionCollection.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegDefinition.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyLegPredicate.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyMatch.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyMatcher.cs" />
<Compile Include="Securities\Option\StrategyMatcher\OptionStrategyMatcherOptions.cs" />
<Compile Include="Securities\Option\StrategyMatcher\PredicateTargetValue.cs" />
<Compile Include="Securities\Option\StrategyMatcher\UnmatchedPositionCountOptionStrategyMatchObjectiveFunction.cs" />
<Compile Include="Securities\RegisteredSecurityDataTypesProvider.cs" />
<Compile Include="Securities\ErrorCurrencyConverter.cs" />
<Compile Include="Securities\GetMaximumOrderQuantityForTargetBuyingPowerParameters.cs" />
@@ -479,7 +529,6 @@
<Compile Include="Python\PythonActivator.cs" />
<Compile Include="Python\PythonSlice.cs" />
<Compile Include="Python\VolatilityModelPythonWrapper.cs" />
<Compile Include="Python\PandasData.cs" />
<Compile Include="Python\SecurityInitializerPythonWrapper.cs" />
<Compile Include="Python\SlippageModelPythonWrapper.cs" />
<Compile Include="Python\FillModelPythonWrapper.cs" />
@@ -839,6 +888,7 @@
<Compile Include="Util\BusyBlockingCollection.cs" />
<Compile Include="Util\BusyCollection.cs" />
<Compile Include="Util\CircularQueue.cs" />
<Compile Include="Util\ComparisonOperator.cs" />
<Compile Include="Util\Composer.cs" />
<Compile Include="Util\ConcurrentSet.cs" />
<Compile Include="Util\DateTimeJsonConverter.cs" />
@@ -850,6 +900,7 @@
<Compile Include="Util\FixedSizeHashQueue.cs" />
<Compile Include="Util\FuncTextWriter.cs" />
<Compile Include="Util\JsonRoundingConverter.cs" />
<Compile Include="Util\ComparisonOperatorTypes.cs" />
<Compile Include="Util\RateLimit\BusyWaitSleepStrategy.cs" />
<Compile Include="Util\RateLimit\FixedIntervalRefillStrategy.cs" />
<Compile Include="Util\RateLimit\IRefillStrategy.cs" />
@@ -871,6 +922,7 @@
<Compile Include="Util\SeriesJsonConverter.cs" />
<Compile Include="Util\StreamReaderEnumerable.cs" />
<Compile Include="Util\StreamReaderExtensions.cs" />
<Compile Include="Util\StringDecimalJsonConverter.cs" />
<Compile Include="Util\TypeChangeJsonConverter.cs" />
<Compile Include="Util\LinqExtensions.cs" />
<Compile Include="Util\MemoizingEnumerable.cs" />
@@ -912,7 +964,6 @@
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.3\analyzers\dotnet\cs\Microsoft.NetFramework.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.3\analyzers\dotnet\cs\Microsoft.NetFramework.CSharp.Analyzers.dll" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Import Project="..\packages\QuantConnect.pythonnet.1.0.5.30\build\QuantConnect.pythonnet.targets" Condition="Exists('..\packages\QuantConnect.pythonnet.1.0.5.30\build\QuantConnect.pythonnet.targets')" />
@@ -934,4 +985,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -338,15 +338,15 @@ namespace QuantConnect.Securities.Future
/// Gets the number of months between the contract month and the expiry date.
/// </summary>
/// <param name="underlying">The future symbol ticker</param>
/// <param name="expiryDate">Expiry date to use to look up contract month delta. Only used for dairy, since we need to lookup its contract month in a pre-defined table.</param>
/// <param name="futureExpiryDate">Expiry date to use to look up contract month delta. Only used for dairy, since we need to lookup its contract month in a pre-defined table.</param>
/// <returns>The number of months between the contract month and the contract expiry</returns>
public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? expiryDate = null)
public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? futureExpiryDate = null)
{
int value;
if (expiryDate != null && _dairyUnderlying.Contains(underlying))
if (futureExpiryDate != null && _dairyUnderlying.Contains(underlying))
{
// Dairy can expire in the month following the contract month.
var dairyReportDate = expiryDate.Value.Date.AddDays(1);
var dairyReportDate = futureExpiryDate.Value.Date.AddDays(1);
if (_reverseDairyReportDates.ContainsKey(dairyReportDate))
{
var contractMonth = _reverseDairyReportDates[dairyReportDate];

View File

@@ -0,0 +1,45 @@
/*
* 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 Newtonsoft.Json;
using QuantConnect.Util;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// CME Option Chain Quotes API call root response
/// </summary>
public class CMEOptionChainQuotes
{
/// <summary>
/// The future options contracts with/without settlements
/// </summary>
[JsonProperty("optionContractQuotes")]
public List<CMEOptionChainQuoteEntry> Quotes { get; private set; }
}
/// <summary>
/// Option chain entry quotes, containing strike price
/// </summary>
public class CMEOptionChainQuoteEntry
{
/// <summary>
/// Strike price of the future option quote entry
/// </summary>
[JsonProperty("strikePrice"), JsonConverter(typeof(StringDecimalJsonConverter), true)]
public decimal StrikePrice { get; private set; }
}
}

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 System.Collections.Generic;
using Newtonsoft.Json;
using QuantConnect.Util;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// CME options trades, dates, and expiration list API call root response
/// </summary>
/// <remarks>Returned as a List of this class</remarks>
public class CMEOptionsTradeDatesAndExpiration
{
/// <summary>
/// Describes the type of future option this entry is
/// </summary>
[JsonProperty("label")]
public string Label { get; private set; }
/// <summary>
/// Name of the product
/// </summary>
[JsonProperty("name")]
public string Name { get; private set; }
/// <summary>
/// Option type. "AME" for American, "EUR" for European.
/// Note that there are other types such as weekly, but we
/// only support American options for now.
/// </summary>
[JsonProperty("optionType")]
public string OptionType { get; private set; }
/// <summary>
/// Product ID of the option
/// </summary>
[JsonProperty("productId")]
public int ProductId { get; private set; }
/// <summary>
/// Is Daily option
/// </summary>
[JsonProperty("daily")]
public bool Daily { get; private set; }
/// <summary>
/// ???
/// </summary>
[JsonProperty("sto")]
public bool Sto { get; private set; }
/// <summary>
/// Is weekly option
/// </summary>
[JsonProperty("weekly")]
public bool Weekly { get; private set; }
/// <summary>
/// Expirations of the future option
/// </summary>
[JsonProperty("expirations")]
public List<CMEOptionsExpiration> Expirations { get; private set; }
}
/// <summary>
/// Future options Expiration entries. These are useful because we can derive the
/// future chain from this data, since FOP and FUT share a 1-1 expiry code.
/// </summary>
public class CMEOptionsExpiration
{
/// <summary>
/// Date of expiry
/// </summary>
[JsonProperty("label")]
public string Label { get; private set; }
/// <summary>
/// Product ID of the expiring asset (usually future option)
/// </summary>
[JsonProperty("productId")]
public int ProductId { get; private set; }
/// <summary>
/// Contract ID of the asset
/// </summary>
/// <remarks>Used to search settlements for the option chain</remarks>
[JsonProperty("contractId")]
public string ContractId { get; private set; }
/// <summary>
/// Contract month code formatted as [FUTURE_MONTH_LETTER(1)][YEAR(1)]
/// </summary>
[JsonProperty("expiration")]
public CMEOptionExpirationEntry Expiration { get; private set; }
}
public class CMEOptionExpirationEntry
{
/// <summary>
/// Month of expiry
/// </summary>
[JsonProperty("month")]
public int Month { get; private set; }
/// <summary>
/// Year of expiry
/// </summary>
[JsonProperty("year")]
public int Year { get; private set; }
/// <summary>
/// Expiration code (two letter)
/// </summary>
[JsonProperty("code")]
public string Code { get; private set; }
/// <summary>
/// Expiration code (three letter)
/// </summary>
[JsonProperty("twoDigitsCode")]
public string TwoDigitsCode { get; private set; }
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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 Newtonsoft.Json;
namespace QuantConnect.Securities.FutureOption.Api
{
/// <summary>
/// Product slate API call root response
/// </summary>
public class CMEProductSlateV2ListResponse
{
/// <summary>
/// Products matching the search criteria
/// </summary>
[JsonProperty("products")]
public List<CMEProductSlateV2ListEntry> Products { get; private set; }
}
/// <summary>
/// Product entry describing the asset matching the search criteria
/// </summary>
public class CMEProductSlateV2ListEntry
{
/// <summary>
/// CME ID for the asset
/// </summary>
[JsonProperty("id")]
public int Id { get; private set; }
/// <summary>
/// Name of the product (e.g. E-mini NASDAQ futures)
/// </summary>
[JsonProperty("name")]
public string Name { get; private set; }
/// <summary>
/// Clearing code
/// </summary>
[JsonProperty("clearing")]
public string Clearing { get; private set; }
/// <summary>
/// GLOBEX ticker
/// </summary>
[JsonProperty("globex")]
public string Globex { get; private set; }
/// <summary>
/// Is traded in the GLOBEX venue
/// </summary>
[JsonProperty("globexTraded")]
public bool GlobexTraded { get; private set; }
/// <summary>
/// Venues this asset trades on
/// </summary>
[JsonProperty("venues")]
public string Venues { get; private set; }
/// <summary>
/// Asset type this product is cleared as (i.e. "Futures", "Options")
/// </summary>
[JsonProperty("cleared")]
public string Cleared { get; private set; }
/// <summary>
/// Exchange the asset trades on (i.e. CME, NYMEX, COMEX, CBOT)
/// </summary>
[JsonProperty("exch")]
public string Exchange { get; private set; }
/// <summary>
/// Asset class group ID - describes group of asset class (e.g. equities, agriculture, etc.)
/// </summary>
[JsonProperty("groupId")]
public int GroupId { get; private set; }
/// <summary>
/// More specific ID describing product
/// </summary>
[JsonProperty("subGroupId")]
public int subGroupId { get; private set; }
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Provides a means to get the scaling factor for CME's quotes API
/// </summary>
public class CMEStrikePriceScalingFactors
{
/// <summary>
/// CME's option chain quotes strike price scaling factor
/// </summary>
private static readonly IReadOnlyDictionary<string, decimal> _scalingFactors = new Dictionary<string, decimal>
{
{ "ES", 100m },
{ "NQ", 100m },
{ "HG", 100m },
{ "SI", 100m },
{ "CL", 100m },
{ "NG", 1000m },
{ "DC", 100m }
};
/// <summary>
/// Gets the option chain strike price scaling factor for the quote response from CME
/// </summary>
/// <param name="underlyingFuture">Underlying future Symbol to normalize</param>
/// <returns>Scaling factor for the strike price</returns>
public static decimal GetScaleFactor(Symbol underlyingFuture)
{
return _scalingFactors.ContainsKey(underlyingFuture.ID.Symbol)
? _scalingFactors[underlyingFuture.ID.Symbol]
: 1m;
}
}
}

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.
*/
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities.Option;
namespace QuantConnect.Securities.FutureOption
{
/// <summary>
/// Futures Options security
/// </summary>
public class FutureOption : Option.Option
{
/// <summary>
/// Constructor for the future option security
/// </summary>
/// <param name="symbol">Symbol of the future option</param>
/// <param name="exchangeHours">Exchange hours of the future option</param>
/// <param name="quoteCurrency">Quoted currency of the future option</param>
/// <param name="symbolProperties">Symbol properties of the future option</param>
/// <param name="currencyConverter">Currency converter</param>
/// <param name="registeredTypes">Provides all data types registered to the algorithm</param>
/// <param name="securityCache">Cache of security objects</param>
public FutureOption(Symbol symbol,
SecurityExchangeHours exchangeHours,
Cash quoteCurrency,
OptionSymbolProperties symbolProperties,
ICurrencyConverter currencyConverter,
IRegisteredSecurityDataTypesProvider registeredTypes,
SecurityCache securityCache)
: base(symbol,
quoteCurrency,
symbolProperties,
new OptionExchange(exchangeHours),
securityCache,
new OptionPortfolioModel(),
new ImmediateFillModel(),
new InteractiveBrokersFeeModel(),
new ConstantSlippageModel(0),
new ImmediateSettlementModel(),
Securities.VolatilityModel.Null,
new FuturesOptionsMarginModel(),
new OptionDataFilter(),
new SecurityPriceVariationModel(),
currencyConverter,
registeredTypes
)
{
}
}
}

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