eb1181f5f7
* 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
1119 lines
52 KiB
C#
1119 lines
52 KiB
C#
/*
|
|
* 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.Algorithm.Framework.Portfolio;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Orders;
|
|
using QuantConnect.Securities;
|
|
using QuantConnect.Securities.Option;
|
|
using static QuantConnect.StringExtensions;
|
|
|
|
namespace QuantConnect.Algorithm
|
|
{
|
|
public partial class QCAlgorithm
|
|
{
|
|
private int _maxOrders = 10000;
|
|
private bool _isMarketOnOpenOrderWarningSent = false;
|
|
|
|
/// <summary>
|
|
/// Transaction Manager - Process transaction fills and order management.
|
|
/// </summary>
|
|
public SecurityTransactionManager Transactions { get; set; }
|
|
|
|
/// <summary>
|
|
/// Buy Stock (Alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">string Symbol of the asset to trade</param>
|
|
/// <param name="quantity">int Quantity of the asset to trade</param>
|
|
/// <seealso cref="Buy(Symbol, double)"/>
|
|
public OrderTicket Buy(Symbol symbol, int quantity)
|
|
{
|
|
return Order(symbol, (decimal)Math.Abs(quantity));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buy Stock (Alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">string Symbol of the asset to trade</param>
|
|
/// <param name="quantity">double Quantity of the asset to trade</param>
|
|
/// <seealso cref="Buy(Symbol, decimal)"/>
|
|
public OrderTicket Buy(Symbol symbol, double quantity)
|
|
{
|
|
return Order(symbol, Math.Abs(quantity).SafeDecimalCast());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buy Stock (Alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">string Symbol of the asset to trade</param>
|
|
/// <param name="quantity">decimal Quantity of the asset to trade</param>
|
|
/// <seealso cref="Order(Symbol, int)"/>
|
|
public OrderTicket Buy(Symbol symbol, decimal quantity)
|
|
{
|
|
return Order(symbol, Math.Abs(quantity));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buy Stock (Alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">string Symbol of the asset to trade</param>
|
|
/// <param name="quantity">float Quantity of the asset to trade</param>
|
|
/// <seealso cref="Buy(Symbol, decimal)"/>
|
|
public OrderTicket Buy(Symbol symbol, float quantity)
|
|
{
|
|
return Order(symbol, (decimal)Math.Abs(quantity));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sell stock (alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">string Symbol of the asset to trade</param>
|
|
/// <param name="quantity">int Quantity of the asset to trade</param>
|
|
/// <seealso cref="Sell(Symbol, decimal)"/>
|
|
public OrderTicket Sell(Symbol symbol, int quantity)
|
|
{
|
|
return Order(symbol, (decimal)Math.Abs(quantity) * -1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sell stock (alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol to sell</param>
|
|
/// <param name="quantity">Quantity to order</param>
|
|
/// <returns>int Order Id.</returns>
|
|
public OrderTicket Sell(Symbol symbol, double quantity)
|
|
{
|
|
return Order(symbol, Math.Abs(quantity).SafeDecimalCast() * -1m);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sell stock (alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol</param>
|
|
/// <param name="quantity">Quantity to sell</param>
|
|
/// <returns>int order id</returns>
|
|
public OrderTicket Sell(Symbol symbol, float quantity)
|
|
{
|
|
return Order(symbol, (decimal)Math.Abs(quantity) * -1m);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sell stock (alias of Order)
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol to sell</param>
|
|
/// <param name="quantity">Quantity to sell</param>
|
|
/// <returns>Int Order Id.</returns>
|
|
public OrderTicket Sell(Symbol symbol, decimal quantity)
|
|
{
|
|
return Order(symbol, Math.Abs(quantity) * -1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Issue an order/trade for asset: Alias wrapper for Order(string, int);
|
|
/// </summary>
|
|
/// <seealso cref="Order(Symbol, decimal)"/>
|
|
public OrderTicket Order(Symbol symbol, double quantity)
|
|
{
|
|
return Order(symbol, quantity.SafeDecimalCast());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Issue an order/trade for asset
|
|
/// </summary>
|
|
/// <remarks></remarks>
|
|
public OrderTicket Order(Symbol symbol, int quantity)
|
|
{
|
|
return MarketOrder(symbol, (decimal)quantity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Issue an order/trade for asset
|
|
/// </summary>
|
|
/// <remarks></remarks>
|
|
public OrderTicket Order(Symbol symbol, decimal quantity)
|
|
{
|
|
return MarketOrder(symbol, quantity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper for market order method: submit a new order for quantity of symbol using type order.
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol of the MarketType Required.</param>
|
|
/// <param name="quantity">Number of shares to request.</param>
|
|
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it fills</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <seealso cref="MarketOrder(Symbol, decimal, bool, string)"/>
|
|
public OrderTicket Order(Symbol symbol, decimal quantity, bool asynchronous = false, string tag = "")
|
|
{
|
|
return MarketOrder(symbol, quantity, asynchronous, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market order implementation: Send a market order and wait for it to be filled.
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol of the MarketType Required.</param>
|
|
/// <param name="quantity">Number of shares to request.</param>
|
|
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it fills</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>int Order id</returns>
|
|
public OrderTicket MarketOrder(Symbol symbol, int quantity, bool asynchronous = false, string tag = "")
|
|
{
|
|
return MarketOrder(symbol, (decimal)quantity, asynchronous, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market order implementation: Send a market order and wait for it to be filled.
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol of the MarketType Required.</param>
|
|
/// <param name="quantity">Number of shares to request.</param>
|
|
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it fills</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>int Order id</returns>
|
|
public OrderTicket MarketOrder(Symbol symbol, double quantity, bool asynchronous = false, string tag = "")
|
|
{
|
|
return MarketOrder(symbol, quantity.SafeDecimalCast(), asynchronous, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market order implementation: Send a market order and wait for it to be filled.
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol of the MarketType Required.</param>
|
|
/// <param name="quantity">Number of shares to request.</param>
|
|
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it fills</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>int Order id</returns>
|
|
public OrderTicket MarketOrder(Symbol symbol, decimal quantity, bool asynchronous = false, string tag = "")
|
|
{
|
|
var security = Securities[symbol];
|
|
|
|
// check the exchange is open before sending a market order, if it's not open
|
|
// then convert it into a market on open order
|
|
if (!security.Exchange.ExchangeOpen)
|
|
{
|
|
var mooTicket = MarketOnOpenOrder(security.Symbol, quantity, tag);
|
|
if (!_isMarketOnOpenOrderWarningSent)
|
|
{
|
|
var anyNonDailySubscriptions = security.Subscriptions.Any(x => x.Resolution != Resolution.Daily);
|
|
if (mooTicket.SubmitRequest.Response.IsSuccess && !anyNonDailySubscriptions)
|
|
{
|
|
Debug("Warning: all market orders sent using daily data, or market orders sent after hours are automatically converted into MarketOnOpen orders.");
|
|
_isMarketOnOpenOrderWarningSent = true;
|
|
}
|
|
}
|
|
return mooTicket;
|
|
}
|
|
|
|
var request = CreateSubmitOrderRequest(OrderType.Market, security, quantity, tag, DefaultOrderProperties?.Clone());
|
|
|
|
// If warming up, do not submit
|
|
if (IsWarmingUp)
|
|
{
|
|
return OrderTicket.InvalidWarmingUp(Transactions, request);
|
|
}
|
|
|
|
//Initialize the Market order parameters:
|
|
var preOrderCheckResponse = PreOrderChecks(request);
|
|
if (preOrderCheckResponse.IsError)
|
|
{
|
|
return OrderTicket.InvalidSubmitRequest(Transactions, request, preOrderCheckResponse);
|
|
}
|
|
|
|
//Add the order and create a new order Id.
|
|
var ticket = Transactions.AddOrder(request);
|
|
|
|
// Wait for the order event to process, only if the exchange is open
|
|
if (!asynchronous)
|
|
{
|
|
Transactions.WaitForOrder(ticket.OrderId);
|
|
}
|
|
|
|
return ticket;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market on open order implementation: Send a market order when the exchange opens
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be ordered</param>
|
|
/// <param name="quantity">The number of shares to required</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>The order ID</returns>
|
|
public OrderTicket MarketOnOpenOrder(Symbol symbol, double quantity, string tag = "")
|
|
{
|
|
return MarketOnOpenOrder(symbol, quantity.SafeDecimalCast(), tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market on open order implementation: Send a market order when the exchange opens
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be ordered</param>
|
|
/// <param name="quantity">The number of shares to required</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>The order ID</returns>
|
|
public OrderTicket MarketOnOpenOrder(Symbol symbol, int quantity, string tag = "")
|
|
{
|
|
return MarketOnOpenOrder(symbol, (decimal)quantity, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market on open order implementation: Send a market order when the exchange opens
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be ordered</param>
|
|
/// <param name="quantity">The number of shares to required</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>The order ID</returns>
|
|
public OrderTicket MarketOnOpenOrder(Symbol symbol, decimal quantity, string tag = "")
|
|
{
|
|
var security = Securities[symbol];
|
|
var request = CreateSubmitOrderRequest(OrderType.MarketOnOpen, security, quantity, tag, DefaultOrderProperties?.Clone());
|
|
var response = PreOrderChecks(request);
|
|
if (response.IsError)
|
|
{
|
|
return OrderTicket.InvalidSubmitRequest(Transactions, request, response);
|
|
}
|
|
|
|
return Transactions.AddOrder(request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market on close order implementation: Send a market order when the exchange closes
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be ordered</param>
|
|
/// <param name="quantity">The number of shares to required</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>The order ID</returns>
|
|
public OrderTicket MarketOnCloseOrder(Symbol symbol, int quantity, string tag = "")
|
|
{
|
|
return MarketOnCloseOrder(symbol, (decimal)quantity, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market on close order implementation: Send a market order when the exchange closes
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be ordered</param>
|
|
/// <param name="quantity">The number of shares to required</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>The order ID</returns>
|
|
public OrderTicket MarketOnCloseOrder(Symbol symbol, double quantity, string tag = "")
|
|
{
|
|
return MarketOnCloseOrder(symbol, quantity.SafeDecimalCast(), tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Market on close order implementation: Send a market order when the exchange closes
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol to be ordered</param>
|
|
/// <param name="quantity">The number of shares to required</param>
|
|
/// <param name="tag">Place a custom order property or tag (e.g. indicator data).</param>
|
|
/// <returns>The order ID</returns>
|
|
public OrderTicket MarketOnCloseOrder(Symbol symbol, decimal quantity, string tag = "")
|
|
{
|
|
var security = Securities[symbol];
|
|
var request = CreateSubmitOrderRequest(OrderType.MarketOnClose, security, quantity, tag, DefaultOrderProperties?.Clone());
|
|
var response = PreOrderChecks(request);
|
|
if (response.IsError)
|
|
{
|
|
return OrderTicket.InvalidSubmitRequest(Transactions, request, response);
|
|
}
|
|
|
|
return Transactions.AddOrder(request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a limit order to the transaction handler:
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset</param>
|
|
/// <param name="quantity">Quantity of shares for limit order</param>
|
|
/// <param name="limitPrice">Limit price to fill this order</param>
|
|
/// <param name="tag">String tag for the order (optional)</param>
|
|
/// <returns>Order id</returns>
|
|
public OrderTicket LimitOrder(Symbol symbol, int quantity, decimal limitPrice, string tag = "")
|
|
{
|
|
return LimitOrder(symbol, (decimal)quantity, limitPrice, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a limit order to the transaction handler:
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset</param>
|
|
/// <param name="quantity">Quantity of shares for limit order</param>
|
|
/// <param name="limitPrice">Limit price to fill this order</param>
|
|
/// <param name="tag">String tag for the order (optional)</param>
|
|
/// <returns>Order id</returns>
|
|
public OrderTicket LimitOrder(Symbol symbol, double quantity, decimal limitPrice, string tag = "")
|
|
{
|
|
return LimitOrder(symbol, quantity.SafeDecimalCast(), limitPrice, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a limit order to the transaction handler:
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset</param>
|
|
/// <param name="quantity">Quantity of shares for limit order</param>
|
|
/// <param name="limitPrice">Limit price to fill this order</param>
|
|
/// <param name="tag">String tag for the order (optional)</param>
|
|
/// <returns>Order id</returns>
|
|
public OrderTicket LimitOrder(Symbol symbol, decimal quantity, decimal limitPrice, string tag = "")
|
|
{
|
|
var security = Securities[symbol];
|
|
var request = CreateSubmitOrderRequest(OrderType.Limit, security, quantity, tag, limitPrice: limitPrice, properties: DefaultOrderProperties?.Clone());
|
|
var response = PreOrderChecks(request);
|
|
if (response.IsError)
|
|
{
|
|
return OrderTicket.InvalidSubmitRequest(Transactions, request, response);
|
|
}
|
|
|
|
return Transactions.AddOrder(request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a stop market order and return the newly created order id; or negative if the order is invalid
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset we're trading</param>
|
|
/// <param name="quantity">Quantity to be traded</param>
|
|
/// <param name="stopPrice">Price to fill the stop order</param>
|
|
/// <param name="tag">Optional string data tag for the order</param>
|
|
/// <returns>Int orderId for the new order.</returns>
|
|
public OrderTicket StopMarketOrder(Symbol symbol, int quantity, decimal stopPrice, string tag = "")
|
|
{
|
|
return StopMarketOrder(symbol, (decimal)quantity, stopPrice, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a stop market order and return the newly created order id; or negative if the order is invalid
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset we're trading</param>
|
|
/// <param name="quantity">Quantity to be traded</param>
|
|
/// <param name="stopPrice">Price to fill the stop order</param>
|
|
/// <param name="tag">Optional string data tag for the order</param>
|
|
/// <returns>Int orderId for the new order.</returns>
|
|
public OrderTicket StopMarketOrder(Symbol symbol, double quantity, decimal stopPrice, string tag = "")
|
|
{
|
|
return StopMarketOrder(symbol, quantity.SafeDecimalCast(), stopPrice, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a stop market order and return the newly created order id; or negative if the order is invalid
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset we're trading</param>
|
|
/// <param name="quantity">Quantity to be traded</param>
|
|
/// <param name="stopPrice">Price to fill the stop order</param>
|
|
/// <param name="tag">Optional string data tag for the order</param>
|
|
/// <returns>Int orderId for the new order.</returns>
|
|
public OrderTicket StopMarketOrder(Symbol symbol, decimal quantity, decimal stopPrice, string tag = "")
|
|
{
|
|
var security = Securities[symbol];
|
|
var request = CreateSubmitOrderRequest(OrderType.StopMarket, security, quantity, tag, stopPrice: stopPrice, properties: DefaultOrderProperties?.Clone());
|
|
var response = PreOrderChecks(request);
|
|
if (response.IsError)
|
|
{
|
|
return OrderTicket.InvalidSubmitRequest(Transactions, request, response);
|
|
}
|
|
|
|
return Transactions.AddOrder(request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a stop limit order to the transaction handler:
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset</param>
|
|
/// <param name="quantity">Quantity of shares for limit order</param>
|
|
/// <param name="stopPrice">Stop price for this order</param>
|
|
/// <param name="limitPrice">Limit price to fill this order</param>
|
|
/// <param name="tag">String tag for the order (optional)</param>
|
|
/// <returns>Order id</returns>
|
|
public OrderTicket StopLimitOrder(Symbol symbol, int quantity, decimal stopPrice, decimal limitPrice, string tag = "")
|
|
{
|
|
return StopLimitOrder(symbol, (decimal)quantity, stopPrice, limitPrice, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a stop limit order to the transaction handler:
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset</param>
|
|
/// <param name="quantity">Quantity of shares for limit order</param>
|
|
/// <param name="stopPrice">Stop price for this order</param>
|
|
/// <param name="limitPrice">Limit price to fill this order</param>
|
|
/// <param name="tag">String tag for the order (optional)</param>
|
|
/// <returns>Order id</returns>
|
|
public OrderTicket StopLimitOrder(Symbol symbol, double quantity, decimal stopPrice, decimal limitPrice, string tag = "")
|
|
{
|
|
return StopLimitOrder(symbol, quantity.SafeDecimalCast(), stopPrice, limitPrice, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a stop limit order to the transaction handler:
|
|
/// </summary>
|
|
/// <param name="symbol">String symbol for the asset</param>
|
|
/// <param name="quantity">Quantity of shares for limit order</param>
|
|
/// <param name="stopPrice">Stop price for this order</param>
|
|
/// <param name="limitPrice">Limit price to fill this order</param>
|
|
/// <param name="tag">String tag for the order (optional)</param>
|
|
/// <returns>Order id</returns>
|
|
public OrderTicket StopLimitOrder(Symbol symbol, decimal quantity, decimal stopPrice, decimal limitPrice, string tag = "")
|
|
{
|
|
var security = Securities[symbol];
|
|
var request = CreateSubmitOrderRequest(OrderType.StopLimit, security, quantity, tag, stopPrice: stopPrice, limitPrice: limitPrice, properties: DefaultOrderProperties?.Clone());
|
|
var response = PreOrderChecks(request);
|
|
if (response.IsError)
|
|
{
|
|
return OrderTicket.InvalidSubmitRequest(Transactions, request, response);
|
|
}
|
|
|
|
//Add the order and create a new order Id.
|
|
return Transactions.AddOrder(request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send an exercise order to the transaction handler
|
|
/// </summary>
|
|
/// <param name="optionSymbol">String symbol for the option position</param>
|
|
/// <param name="quantity">Quantity of options contracts</param>
|
|
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it fills</param>
|
|
/// <param name="tag">String tag for the order (optional)</param>
|
|
public OrderTicket ExerciseOption(Symbol optionSymbol, int quantity, bool asynchronous = false, string tag = "")
|
|
{
|
|
var option = (Option) Securities[optionSymbol];
|
|
|
|
// SubmitOrderRequest.Quantity indicates the change in holdings quantity, therefore manual exercise quantities must be negative
|
|
// PreOrderChecksImpl confirms that we don't hold a short position, so we're lenient here and accept +/- quantity values
|
|
var request = CreateSubmitOrderRequest(OrderType.OptionExercise, option, -Math.Abs(quantity), tag, DefaultOrderProperties?.Clone());
|
|
|
|
// If warming up, do not submit
|
|
if (IsWarmingUp)
|
|
{
|
|
return OrderTicket.InvalidWarmingUp(Transactions, request);
|
|
}
|
|
|
|
//Initialize the exercise order parameters
|
|
var preOrderCheckResponse = PreOrderChecks(request);
|
|
if (preOrderCheckResponse.IsError)
|
|
{
|
|
return OrderTicket.InvalidSubmitRequest(Transactions, request, preOrderCheckResponse);
|
|
}
|
|
|
|
//Add the order and create a new order Id.
|
|
var ticket = Transactions.AddOrder(request);
|
|
|
|
// Wait for the order event to process, only if the exchange is open
|
|
if (!asynchronous)
|
|
{
|
|
Transactions.WaitForOrder(ticket.OrderId);
|
|
}
|
|
|
|
return ticket;
|
|
}
|
|
|
|
// Support for option strategies trading
|
|
|
|
/// <summary>
|
|
/// Buy Option Strategy (Alias of Order)
|
|
/// </summary>
|
|
/// <param name="strategy">Specification of the strategy to trade</param>
|
|
/// <param name="quantity">Quantity of the strategy to trade</param>
|
|
/// <returns>Sequence of order ids</returns>
|
|
public IEnumerable<OrderTicket> Buy(OptionStrategy strategy, int quantity)
|
|
{
|
|
return Order(strategy, Math.Abs(quantity));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sell Option Strategy (alias of Order)
|
|
/// </summary>
|
|
/// <param name="strategy">Specification of the strategy to trade</param>
|
|
/// <param name="quantity">Quantity of the strategy to trade</param>
|
|
/// <returns>Sequence of order ids</returns>
|
|
public IEnumerable<OrderTicket> Sell(OptionStrategy strategy, int quantity)
|
|
{
|
|
return Order(strategy, Math.Abs(quantity) * -1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Issue an order/trade for buying/selling an option strategy
|
|
/// </summary>
|
|
/// <param name="strategy">Specification of the strategy to trade</param>
|
|
/// <param name="quantity">Quantity of the strategy to trade</param>
|
|
/// <returns>Sequence of order ids</returns>
|
|
public IEnumerable<OrderTicket> Order(OptionStrategy strategy, int quantity)
|
|
{
|
|
return GenerateOrders(strategy, quantity);
|
|
}
|
|
|
|
private IEnumerable<OrderTicket> GenerateOrders(OptionStrategy strategy, int strategyQuantity)
|
|
{
|
|
var orders = new List<OrderTicket>();
|
|
|
|
// setting up the tag text for all orders of one strategy
|
|
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)
|
|
{
|
|
// 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);
|
|
|
|
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}")
|
|
);
|
|
}
|
|
|
|
var orderQuantity = optionLeg.Quantity * strategyQuantity;
|
|
switch (optionLeg.OrderType)
|
|
{
|
|
case OrderType.Market:
|
|
orders.Add(MarketOrder(contract.Symbol, orderQuantity, tag: tag));
|
|
break;
|
|
|
|
case OrderType.Limit:
|
|
orders.Add(LimitOrder(contract.Symbol, orderQuantity, optionLeg.OrderPrice, tag));
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidOperationException(Invariant($"Order type is not supported in option strategy: {optionLeg.OrderType}"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// walking through all underlying legs and issuing orders
|
|
if (strategy.UnderlyingLegs != null)
|
|
{
|
|
foreach (var underlyingLeg in strategy.UnderlyingLegs)
|
|
{
|
|
if (!Securities.ContainsKey(strategy.Underlying))
|
|
{
|
|
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:
|
|
orders.Add(MarketOrder(strategy.Underlying, orderQuantity, tag: tag));
|
|
break;
|
|
|
|
case OrderType.Limit:
|
|
orders.Add(LimitOrder(strategy.Underlying, orderQuantity, underlyingLeg.OrderPrice, tag));
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidOperationException(Invariant($"Order type is not supported in option strategy: {underlyingLeg.OrderType}"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return orders;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Perform pre-order checks to ensure we have sufficient capital,
|
|
/// the market is open, and we haven't exceeded maximum realistic orders per day.
|
|
/// </summary>
|
|
/// <returns>OrderResponse. If no error, order request is submitted.</returns>
|
|
private OrderResponse PreOrderChecks(SubmitOrderRequest request)
|
|
{
|
|
var response = PreOrderChecksImpl(request);
|
|
if (response.IsError)
|
|
{
|
|
Error(response.ErrorMessage);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform pre-order checks to ensure we have sufficient capital,
|
|
/// the market is open, and we haven't exceeded maximum realistic orders per day.
|
|
/// </summary>
|
|
/// <returns>OrderResponse. If no error, order request is submitted.</returns>
|
|
private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request)
|
|
{
|
|
if (IsWarmingUp)
|
|
{
|
|
return OrderResponse.WarmingUp(request);
|
|
}
|
|
|
|
//Most order methods use security objects; so this isn't really used.
|
|
// todo: Left here for now but should review
|
|
Security security;
|
|
if (!Securities.TryGetValue(request.Symbol, out security))
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity,
|
|
$"You haven't requested {request.Symbol} data. Add this with AddSecurity() in the Initialize() Method."
|
|
);
|
|
}
|
|
|
|
//Ordering 0 is useless.
|
|
if (request.Quantity == 0)
|
|
{
|
|
return OrderResponse.ZeroQuantity(request);
|
|
}
|
|
|
|
if (Math.Abs(request.Quantity) < security.SymbolProperties.LotSize)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.OrderQuantityLessThanLoteSize,
|
|
Invariant($"Unable to {request.OrderRequestType.ToLower()} order with id {request.OrderId} which ") +
|
|
Invariant($"quantity ({Math.Abs(request.Quantity)}) is less than lot ") +
|
|
Invariant($"size ({security.SymbolProperties.LotSize}).")
|
|
);
|
|
}
|
|
|
|
if (!security.IsTradable)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.NonTradableSecurity,
|
|
$"The security with symbol '{request.Symbol}' is marked as non-tradable."
|
|
);
|
|
}
|
|
|
|
var price = security.Price;
|
|
|
|
//Check the exchange is open before sending a market on close orders
|
|
if (request.OrderType == OrderType.MarketOnClose && !security.Exchange.ExchangeOpen)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen,
|
|
$"{request.OrderType} order and exchange not open."
|
|
);
|
|
}
|
|
|
|
//Check the exchange is open before sending a exercise orders
|
|
if (request.OrderType == OrderType.OptionExercise && !security.Exchange.ExchangeOpen)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen,
|
|
$"{request.OrderType} order and exchange not open."
|
|
);
|
|
}
|
|
|
|
if (price == 0)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.SecurityPriceZero, request.Symbol.GetZeroPriceMessage());
|
|
}
|
|
|
|
// check quote currency existence/conversion rate on all orders
|
|
Cash quoteCash;
|
|
var quoteCurrency = security.QuoteCurrency.Symbol;
|
|
if (!Portfolio.CashBook.TryGetValue(quoteCurrency, out quoteCash))
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.QuoteCurrencyRequired,
|
|
$"{request.Symbol.Value}: requires {quoteCurrency} in the cashbook to trade."
|
|
);
|
|
}
|
|
if (security.QuoteCurrency.ConversionRate == 0m)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.ConversionRateZero,
|
|
$"{request.Symbol.Value}: requires {quoteCurrency} to have a non-zero conversion rate. This can be caused by lack of data."
|
|
);
|
|
}
|
|
|
|
// need to also check base currency existence/conversion rate on forex orders
|
|
if (security.Type == SecurityType.Forex || security.Type == SecurityType.Crypto)
|
|
{
|
|
Cash baseCash;
|
|
var baseCurrency = ((IBaseCurrencySymbol)security).BaseCurrencySymbol;
|
|
if (!Portfolio.CashBook.TryGetValue(baseCurrency, out baseCash))
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired,
|
|
$"{request.Symbol.Value}: requires {baseCurrency} and {quoteCurrency} in the cashbook to trade."
|
|
);
|
|
}
|
|
if (baseCash.ConversionRate == 0m)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero,
|
|
$"{request.Symbol.Value}: requires {baseCurrency} and {quoteCurrency} to have non-zero conversion rates. This can be caused by lack of data."
|
|
);
|
|
}
|
|
}
|
|
|
|
//Make sure the security has some data:
|
|
if (!security.HasData)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData,
|
|
"There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point."
|
|
);
|
|
}
|
|
|
|
// We've already processed too many orders: max 10k
|
|
if (!LiveMode && Transactions.OrdersCount > _maxOrders)
|
|
{
|
|
Status = AlgorithmStatus.Stopped;
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.ExceededMaximumOrders,
|
|
Invariant($"You have exceeded maximum number of orders ({_maxOrders}), for unlimited orders upgrade your account.")
|
|
);
|
|
}
|
|
|
|
if (request.OrderType == OrderType.OptionExercise)
|
|
{
|
|
if (security.Type != SecurityType.Option && security.Type != SecurityType.FutureOption)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.NonExercisableSecurity,
|
|
$"The security with symbol '{request.Symbol}' is not exercisable."
|
|
);
|
|
}
|
|
|
|
if (security.Holdings.IsShort)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType,
|
|
$"The security with symbol '{request.Symbol}' has a short option position. Only long option positions are exercisable."
|
|
);
|
|
}
|
|
|
|
if (Math.Abs(request.Quantity) > security.Holdings.Quantity)
|
|
{
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.UnsupportedRequestType,
|
|
$"Cannot exercise more contracts of '{request.Symbol}' than is currently available in the portfolio. "
|
|
);
|
|
}
|
|
}
|
|
|
|
if (request.OrderType == OrderType.MarketOnClose)
|
|
{
|
|
var nextMarketClose = security.Exchange.Hours.GetNextMarketClose(security.LocalTime, false);
|
|
|
|
// must be submitted with at least 10 minutes in trading day, add buffer allow order submission
|
|
var latestSubmissionTime = nextMarketClose.Subtract(Orders.MarketOnCloseOrder.DefaultSubmissionTimeBuffer);
|
|
if (!security.Exchange.ExchangeOpen || Time > latestSubmissionTime)
|
|
{
|
|
// tell the user we require a 16 minute buffer, on minute data in live a user will receive the 3:44->3:45 bar at 3:45,
|
|
// this is already too late to submit one of these orders, so make the user do it at the 3:43->3:44 bar so it's submitted
|
|
// to the brokerage before 3:45.
|
|
return OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate,
|
|
"MarketOnClose orders must be placed with at least a 16 minute buffer before market close."
|
|
);
|
|
}
|
|
}
|
|
|
|
// passes all initial order checks
|
|
return OrderResponse.Success(request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Liquidate all holdings and cancel open orders. Called at the end of day for tick-strategies.
|
|
/// </summary>
|
|
/// <param name="symbolToLiquidate">Symbols we wish to liquidate</param>
|
|
/// <param name="tag">Custom tag to know who is calling this.</param>
|
|
/// <returns>Array of order ids for liquidated symbols</returns>
|
|
/// <seealso cref="MarketOrder(QuantConnect.Symbol,decimal,bool,string)"/>
|
|
public List<int> Liquidate(Symbol symbolToLiquidate = null, string tag = "Liquidated")
|
|
{
|
|
var orderIdList = new List<int>();
|
|
if (!Settings.LiquidateEnabled)
|
|
{
|
|
Debug("Liquidate() is currently disabled by settings. To re-enable please set 'Settings.LiquidateEnabled' to true");
|
|
return orderIdList;
|
|
}
|
|
|
|
IEnumerable<Symbol> toLiquidate;
|
|
if (symbolToLiquidate != null)
|
|
{
|
|
toLiquidate = Securities.ContainsKey(symbolToLiquidate)
|
|
? new[] { symbolToLiquidate } : Enumerable.Empty<Symbol>();
|
|
}
|
|
else
|
|
{
|
|
toLiquidate = Securities.Keys.OrderBy(x => x.Value);
|
|
}
|
|
|
|
|
|
foreach (var symbol in toLiquidate)
|
|
{
|
|
// get open orders
|
|
var orders = Transactions.GetOpenOrders(symbol);
|
|
|
|
// get quantity in portfolio
|
|
var quantity = Portfolio[symbol].Quantity;
|
|
|
|
// if there is only one open market order that would close the position, do nothing
|
|
if (orders.Count == 1 && quantity != 0 && orders[0].Quantity == -quantity && orders[0].Type == OrderType.Market)
|
|
continue;
|
|
|
|
// cancel all open orders
|
|
var marketOrdersQuantity = 0m;
|
|
foreach (var order in orders)
|
|
{
|
|
if (order.Type == OrderType.Market)
|
|
{
|
|
// pending market order
|
|
var ticket = Transactions.GetOrderTicket(order.Id);
|
|
if (ticket != null)
|
|
{
|
|
// get remaining quantity
|
|
marketOrdersQuantity += ticket.Quantity - ticket.QuantityFilled;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Transactions.CancelOrder(order.Id, tag);
|
|
}
|
|
}
|
|
|
|
// Liquidate at market price
|
|
if (quantity != 0)
|
|
{
|
|
// calculate quantity for closing market order
|
|
var ticket = Order(symbol, -quantity - marketOrdersQuantity, tag: tag);
|
|
if (ticket.Status == OrderStatus.Filled)
|
|
{
|
|
orderIdList.Add(ticket.OrderId);
|
|
}
|
|
}
|
|
}
|
|
|
|
return orderIdList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maximum number of orders for the algorithm
|
|
/// </summary>
|
|
/// <param name="max"></param>
|
|
public void SetMaximumOrders(int max)
|
|
{
|
|
if (!_locked)
|
|
{
|
|
_maxOrders = max;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets holdings for a collection of targets.
|
|
/// The implementation will order the provided targets executing first those that
|
|
/// reduce a position, freeing margin.
|
|
/// </summary>
|
|
/// <param name="targets">The portfolio desired quantities as percentages</param>
|
|
/// <param name="liquidateExistingHoldings">True will liquidate existing holdings</param>
|
|
/// <seealso cref="MarketOrder(QuantConnect.Symbol,decimal,bool,string)"/>
|
|
public void SetHoldings(List<PortfolioTarget> targets, bool liquidateExistingHoldings = false)
|
|
{
|
|
foreach (var portfolioTarget in targets
|
|
// we need to create targets with quantities for OrderTargetsByMarginImpact
|
|
.Select(target => new PortfolioTarget(target.Symbol, CalculateOrderQuantity(target.Symbol, target.Quantity)))
|
|
.OrderTargetsByMarginImpact(this, targetIsDelta:true))
|
|
{
|
|
SetHoldingsImpl(portfolioTarget.Symbol, portfolioTarget.Quantity, liquidateExistingHoldings);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alias for SetHoldings to avoid the M-decimal errors.
|
|
/// </summary>
|
|
/// <param name="symbol">string symbol we wish to hold</param>
|
|
/// <param name="percentage">double percentage of holdings desired</param>
|
|
/// <param name="liquidateExistingHoldings">liquidate existing holdings if necessary to hold this stock</param>
|
|
/// <seealso cref="MarketOrder(QuantConnect.Symbol,decimal,bool,string)"/>
|
|
public void SetHoldings(Symbol symbol, double percentage, bool liquidateExistingHoldings = false)
|
|
{
|
|
SetHoldings(symbol, percentage.SafeDecimalCast(), liquidateExistingHoldings);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alias for SetHoldings to avoid the M-decimal errors.
|
|
/// </summary>
|
|
/// <param name="symbol">string symbol we wish to hold</param>
|
|
/// <param name="percentage">float percentage of holdings desired</param>
|
|
/// <param name="liquidateExistingHoldings">bool liquidate existing holdings if necessary to hold this stock</param>
|
|
/// <param name="tag">Tag the order with a short string.</param>
|
|
/// <seealso cref="MarketOrder(QuantConnect.Symbol,decimal,bool,string)"/>
|
|
public void SetHoldings(Symbol symbol, float percentage, bool liquidateExistingHoldings = false, string tag = "")
|
|
{
|
|
SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alias for SetHoldings to avoid the M-decimal errors.
|
|
/// </summary>
|
|
/// <param name="symbol">string symbol we wish to hold</param>
|
|
/// <param name="percentage">float percentage of holdings desired</param>
|
|
/// <param name="liquidateExistingHoldings">bool liquidate existing holdings if necessary to hold this stock</param>
|
|
/// <param name="tag">Tag the order with a short string.</param>
|
|
/// <seealso cref="MarketOrder(QuantConnect.Symbol,decimal,bool,string)"/>
|
|
public void SetHoldings(Symbol symbol, int percentage, bool liquidateExistingHoldings = false, string tag = "")
|
|
{
|
|
SetHoldings(symbol, (decimal)percentage, liquidateExistingHoldings, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Automatically place a market order which will set the holdings to between 100% or -100% of *PORTFOLIO VALUE*.
|
|
/// E.g. SetHoldings("AAPL", 0.1); SetHoldings("IBM", -0.2); -> Sets portfolio as long 10% APPL and short 20% IBM
|
|
/// E.g. SetHoldings("AAPL", 2); -> Sets apple to 2x leveraged with all our cash.
|
|
/// If the market is closed, place a market on open order.
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol indexer</param>
|
|
/// <param name="percentage">decimal fraction of portfolio to set stock</param>
|
|
/// <param name="liquidateExistingHoldings">bool flag to clean all existing holdings before setting new faction.</param>
|
|
/// <param name="tag">Tag the order with a short string.</param>
|
|
/// <seealso cref="MarketOrder(QuantConnect.Symbol,decimal,bool,string)"/>
|
|
public void SetHoldings(Symbol symbol, decimal percentage, bool liquidateExistingHoldings = false, string tag = "")
|
|
{
|
|
SetHoldingsImpl(symbol, CalculateOrderQuantity(symbol, percentage), liquidateExistingHoldings, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set holdings implementation, which uses order quantities (delta) not percentage nor target final quantity
|
|
/// </summary>
|
|
private void SetHoldingsImpl(Symbol symbol, decimal orderQuantity, bool liquidateExistingHoldings = false, string tag = "")
|
|
{
|
|
//If they triggered a liquidate
|
|
if (liquidateExistingHoldings)
|
|
{
|
|
foreach (var kvp in Portfolio)
|
|
{
|
|
var holdingSymbol = kvp.Key;
|
|
var holdings = kvp.Value;
|
|
if (holdingSymbol != symbol && holdings.AbsoluteQuantity > 0)
|
|
{
|
|
//Go through all existing holdings [synchronously], market order the inverse quantity:
|
|
var liquidationQuantity = CalculateOrderQuantity(holdingSymbol, 0m);
|
|
Order(holdingSymbol, liquidationQuantity, false, tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Calculate total unfilled quantity for open market orders
|
|
var marketOrdersQuantity = Transactions.GetOpenOrderTickets(
|
|
ticket => ticket.Symbol == symbol
|
|
&& (ticket.OrderType == OrderType.Market
|
|
|| ticket.OrderType == OrderType.MarketOnOpen))
|
|
.Aggregate(0m, (d, ticket) => d + ticket.Quantity - ticket.QuantityFilled);
|
|
|
|
//Only place trade if we've got > 1 share to order.
|
|
var quantity = orderQuantity - marketOrdersQuantity;
|
|
if (Math.Abs(quantity) > 0)
|
|
{
|
|
Security security;
|
|
if (!Securities.TryGetValue(symbol, out security))
|
|
{
|
|
Error($"{symbol} not found in portfolio. Request this data when initializing the algorithm.");
|
|
return;
|
|
}
|
|
|
|
//Check whether the exchange is open to send a market order. If not, send a market on open order instead
|
|
if (security.Exchange.ExchangeOpen)
|
|
{
|
|
MarketOrder(symbol, quantity, false, tag);
|
|
}
|
|
else
|
|
{
|
|
MarketOnOpenOrder(symbol, quantity, tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the order quantity to achieve target-percent holdings.
|
|
/// </summary>
|
|
/// <param name="symbol">Security object we're asking for</param>
|
|
/// <param name="target">Target percentage holdings</param>
|
|
/// <returns>Order quantity to achieve this percentage</returns>
|
|
public decimal CalculateOrderQuantity(Symbol symbol, double target)
|
|
{
|
|
return CalculateOrderQuantity(symbol, target.SafeDecimalCast());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the order quantity to achieve target-percent holdings.
|
|
/// </summary>
|
|
/// <param name="symbol">Security object we're asking for</param>
|
|
/// <param name="target">Target percentage holdings, this is an unleveraged value, so
|
|
/// if you have 2x leverage and request 100% holdings, it will utilize half of the
|
|
/// available margin</param>
|
|
/// <returns>Order quantity to achieve this percentage</returns>
|
|
public decimal CalculateOrderQuantity(Symbol symbol, decimal target)
|
|
{
|
|
var percent = PortfolioTarget.Percent(this, symbol, target, true);
|
|
|
|
if (percent == null)
|
|
{
|
|
return 0;
|
|
}
|
|
return percent.Quantity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obsolete implementation of Order method accepting a OrderType. This was deprecated since it
|
|
/// was impossible to generate other orders via this method. Any calls to this method will always default to a Market Order.
|
|
/// </summary>
|
|
/// <param name="symbol">Symbol we want to purchase</param>
|
|
/// <param name="quantity">Quantity to buy, + is long, - short.</param>
|
|
/// <param name="type">Order Type</param>
|
|
/// <param name="asynchronous">Don't wait for the response, just submit order and move on.</param>
|
|
/// <param name="tag">Custom data for this order</param>
|
|
/// <returns>Integer Order ID.</returns>
|
|
[Obsolete("This Order method has been made obsolete, use Order(string, int, bool, string) method instead. Calls to the obsolete method will only generate market orders.")]
|
|
public OrderTicket Order(Symbol symbol, int quantity, OrderType type, bool asynchronous = false, string tag = "")
|
|
{
|
|
return Order(symbol, quantity, asynchronous, tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obsolete method for placing orders.
|
|
/// </summary>
|
|
/// <param name="symbol"></param>
|
|
/// <param name="quantity"></param>
|
|
/// <param name="type"></param>
|
|
[Obsolete("This Order method has been made obsolete, use the specialized Order helper methods instead. Calls to the obsolete method will only generate market orders.")]
|
|
public OrderTicket Order(Symbol symbol, decimal quantity, OrderType type)
|
|
{
|
|
return Order(symbol, quantity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obsolete method for placing orders.
|
|
/// </summary>
|
|
/// <param name="symbol"></param>
|
|
/// <param name="quantity"></param>
|
|
/// <param name="type"></param>
|
|
[Obsolete("This Order method has been made obsolete, use the specialized Order helper methods instead. Calls to the obsolete method will only generate market orders.")]
|
|
public OrderTicket Order(Symbol symbol, int quantity, OrderType type)
|
|
{
|
|
return Order(symbol, (decimal)quantity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the exchange for the specified symbol is open at the current time.
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol</param>
|
|
/// <returns>True if the exchange is considered open at the current time, false otherwise</returns>
|
|
public bool IsMarketOpen(Symbol symbol)
|
|
{
|
|
var exchangeHours = MarketHoursDatabase
|
|
.FromDataFolder()
|
|
.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
|
|
|
|
var time = UtcTime.ConvertFromUtc(exchangeHours.TimeZone);
|
|
|
|
return exchangeHours.IsOpen(time, false);
|
|
}
|
|
|
|
private SubmitOrderRequest CreateSubmitOrderRequest(OrderType orderType, Security security, decimal quantity, string tag, IOrderProperties properties, decimal stopPrice = 0m, decimal limitPrice = 0m)
|
|
{
|
|
return new SubmitOrderRequest(orderType, security.Type, security.Symbol, quantity, stopPrice, limitPrice, UtcTime, tag, properties);
|
|
}
|
|
}
|
|
}
|