Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions Algorithm.CSharp/PeggedToMidpointRegressionAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Orders;

namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Basic algorithm demonstrating how to place Pegged-to-Midpoint orders.
/// </summary>
/// <meta name="tag" content="trading and orders" />
/// <meta name="tag" content="placing orders" />
/// <meta name="tag" content="pegged to midpoint order" />
public class PeggedToMidpointRegressionAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Symbol _symbol;
private OrderTicket _buyOrderTicket;
private OrderTicket _sellOrderTicket;
protected virtual bool AsynchronousOrders => false;

public override void Initialize()
{
SetStartDate(2013, 10, 07);
SetEndDate(2013, 10, 11);
SetCash(100000);
_symbol = AddEquity("SPY").Symbol;
}

public override void OnData(Slice slice)
{
if (!slice.ContainsKey(_symbol))
{
return;
}

if (_buyOrderTicket == null)
{
_buyOrderTicket = PeggedToMidpointOrder(_symbol, 1, limitPrice: 0m, limitPriceOffset: 0m,
asynchronous: AsynchronousOrders);
}
else if (_sellOrderTicket == null && Portfolio.Invested)
{
_sellOrderTicket = PeggedToMidpointOrder(_symbol, -1, limitPrice: 0m, limitPriceOffset: 0m,
asynchronous: AsynchronousOrders);
}
}

public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status != OrderStatus.Filled)
{
return;
}

var order = Transactions.GetOrderById(orderEvent.OrderId);
if (order is not Orders.PeggedToMidpointOrder)
{
throw new RegressionTestException($"Expected PeggedToMidpointOrder but got {order.GetType().Name}");
}
}

public override void OnEndOfAlgorithm()
{
if (_sellOrderTicket?.Status != OrderStatus.Filled)
{
throw new RegressionTestException("Expected sell PeggedToMidpoint order to be filled by end of algorithm.");
}
}

/// <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 => true;

/// <summary>
/// This is used by the regression test system to indicate which languages this algorithm is written in.
/// </summary>
public List<Language> Languages { get; } = new() { Language.CSharp, Language.Python };

/// <summary>
/// Data Points count of all timeslices of algorithm
/// </summary>
public long DataPoints => 3943;

/// <summary>
/// Data Points count of the algorithm history
/// </summary>
public int AlgorithmHistoryDataPoints => 0;

/// <summary>
/// Final status of the algorithm
/// </summary>
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;

/// <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 Orders", "2"},
{"Average Win", "0%"},
{"Average Loss", "0.00%"},
{"Compounding Annual Return", "-0.198%"},
{"Drawdown", "0.000%"},
{"Expectancy", "-1"},
{"Start Equity", "100000"},
{"End Equity", "99997.46"},
{"Net Profit", "-0.003%"},
{"Sharpe Ratio", "-108.977"},
{"Sortino Ratio", "0"},
{"Probabilistic Sharpe Ratio", "1.216%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "-0.009"},
{"Beta", "0"},
{"Annual Standard Deviation", "0"},
{"Annual Variance", "0"},
{"Information Ratio", "-8.915"},
{"Tracking Error", "0.222"},
{"Treynor Ratio", "-32.005"},
{"Total Fees", "$2.00"},
{"Estimated Strategy Capacity", "$17000000.00"},
{"Lowest Capacity Asset", "SPY R735QTJ8XC9X"},
{"Portfolio Turnover", "0.06%"},
{"Drawdown Recovery", "0"},
{"OrderListHash", "d310603dec0f507a6b9b8a9eb21c4717"}
};
}
}
52 changes: 52 additions & 0 deletions Algorithm.Python/PeggedToMidpointRegressionAlgorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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 AlgorithmImports import *

### <summary>
### Basic algorithm demonstrating how to place Pegged-to-Midpoint orders.
### </summary>
### <meta name="tag" content="trading and orders" />
### <meta name="tag" content="placing orders" />
### <meta name="tag" content="pegged to midpoint order" />
class PeggedToMidpointRegressionAlgorithm(QCAlgorithm):

_buy_order_ticket = None
_sell_order_ticket = None

def initialize(self):
self.set_start_date(2013, 10, 7)
self.set_end_date(2013, 10, 11)
self.set_cash(100000)
self._symbol = self.add_equity("SPY").symbol

def on_data(self, data):
if not data.contains_key(self._symbol):
return

if self._buy_order_ticket is None:
self._buy_order_ticket = self.pegged_to_midpoint_order(self._symbol, 1, limit_price=0, limit_price_offset=0)
elif self._sell_order_ticket is None and self.portfolio.invested:
self._sell_order_ticket = self.pegged_to_midpoint_order(self._symbol, -1, limit_price=0, limit_price_offset=0)

def on_order_event(self, order_event):
if order_event.status != OrderStatus.FILLED:
return

order = self.transactions.get_order_by_id(order_event.order_id)
if not isinstance(order, PeggedToMidpointOrder):
raise AssertionError(f"Expected PeggedToMidpointOrder but got {type(order).__name__}")

def on_end_of_algorithm(self):
if self._sell_order_ticket is None or self._sell_order_ticket.status != OrderStatus.FILLED:
raise AssertionError("Expected sell PeggedToMidpoint order to be filled by end of algorithm.")
58 changes: 58 additions & 0 deletions Algorithm/QCAlgorithm.Trading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,64 @@ public OrderTicket LimitIfTouchedOrder(Symbol symbol, decimal quantity, decimal
return SubmitOrderRequest(request);
}

/// <summary>
/// Send a pegged-to-midpoint order to the transaction handler:
/// </summary>
/// <param name="symbol">Symbol for the asset</param>
/// <param name="quantity">Quantity of shares for the order</param>
/// <param name="limitPrice">Optional limit price cap (buy) or floor (sell)</param>
/// <param name="limitPriceOffset">Offset from the midpoint</param>
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it is fully submitted</param>
/// <param name="tag">String tag for the order (optional)</param>
/// <param name="orderProperties">The order properties to use. Defaults to <see cref="DefaultOrderProperties"/></param>
/// <returns>The order ticket instance.</returns>
public OrderTicket PeggedToMidpointOrder(Symbol symbol, int quantity, decimal limitPrice, decimal limitPriceOffset,
bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null)
{
return PeggedToMidpointOrder(symbol, (decimal)quantity, limitPrice, limitPriceOffset, asynchronous, tag, orderProperties);
}

/// <summary>
/// Send a pegged-to-midpoint order to the transaction handler:
/// </summary>
/// <param name="symbol">Symbol for the asset</param>
/// <param name="quantity">Quantity of shares for the order</param>
/// <param name="limitPrice">Optional limit price cap (buy) or floor (sell)</param>
/// <param name="limitPriceOffset">Offset from the midpoint</param>
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it is fully submitted</param>
/// <param name="tag">String tag for the order (optional)</param>
/// <param name="orderProperties">The order properties to use. Defaults to <see cref="DefaultOrderProperties"/></param>
/// <returns>The order ticket instance.</returns>
public OrderTicket PeggedToMidpointOrder(Symbol symbol, double quantity, decimal limitPrice, decimal limitPriceOffset,
bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null)
{
return PeggedToMidpointOrder(symbol, quantity.SafeDecimalCast(), limitPrice, limitPriceOffset, asynchronous, tag, orderProperties);
}

/// <summary>
/// Send a pegged-to-midpoint order to the transaction handler:
/// </summary>
/// <param name="symbol">Symbol for the asset</param>
/// <param name="quantity">Quantity of shares for the order</param>
/// <param name="limitPrice">Optional limit price cap (buy) or floor (sell)</param>
/// <param name="limitPriceOffset">Offset from the midpoint</param>
/// <param name="asynchronous">Send the order asynchronously (false). Otherwise we'll block until it is fully submitted</param>
/// <param name="tag">String tag for the order (optional)</param>
/// <param name="orderProperties">The order properties to use. Defaults to <see cref="DefaultOrderProperties"/></param>
/// <returns>The order ticket instance.</returns>
[DocumentationAttribute(TradingAndOrders)]
public OrderTicket PeggedToMidpointOrder(Symbol symbol, decimal quantity, decimal limitPrice, decimal limitPriceOffset,
bool asynchronous = false, string tag = "", IOrderProperties orderProperties = null)
{
var security = Securities[symbol];
var request = CreateSubmitOrderRequest(OrderType.PeggedToMidpoint, security, quantity, tag,
limitPrice: limitPrice, triggerPrice: limitPriceOffset,
properties: orderProperties ?? DefaultOrderProperties?.Clone(),
asynchronous: asynchronous);

return SubmitOrderRequest(request);
}

/// <summary>
/// Send an exercise order to the transaction handler
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions Common/Brokerages/InteractiveBrokersBrokerageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class InteractiveBrokersBrokerageModel : DefaultBrokerageModel
OrderType.StopLimit,
OrderType.TrailingStop,
OrderType.LimitIfTouched,
OrderType.PeggedToMidpoint,
OrderType.ComboMarket,
OrderType.ComboLimit,
OrderType.ComboLegLimit,
Expand Down
58 changes: 58 additions & 0 deletions Common/Orders/Fills/FillModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public virtual Fill Fill(FillModelParameters parameters)
? PythonWrapper.ComboLegLimitFill(parameters.Order, parameters)
: ComboLegLimitFill(parameters.Order, parameters);
break;
case OrderType.PeggedToMidpoint:
orderEvents.Add(PythonWrapper != null
? PythonWrapper.PeggedToMidpointFill(parameters.Security, parameters.Order as PeggedToMidpointOrder)
: PeggedToMidpointFill(parameters.Security, parameters.Order as PeggedToMidpointOrder));
break;
default:
throw new ArgumentOutOfRangeException();
}
Expand Down Expand Up @@ -644,6 +649,59 @@ public virtual OrderEvent LimitIfTouchedFill(Security asset, LimitIfTouchedOrder
return fill;
}

/// <summary>
/// Pegged to midpoint fill model. Fills at the NBBO midpoint adjusted by <see cref="PeggedToMidpointOrder.LimitPriceOffset"/>.
/// If <see cref="PeggedToMidpointOrder.LimitPrice"/> is set it acts as a cap for buy orders and a floor for sell orders;
/// the order will not fill if the effective midpoint price would exceed the cap (buy) or fall below the floor (sell).
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill information detailing the average price and quantity filled.</returns>
public virtual OrderEvent PeggedToMidpointFill(Security asset, PeggedToMidpointOrder order)
{
var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone);
var fill = new OrderEvent(order, utcTime, OrderFee.Zero);

if (order.Status == OrderStatus.Canceled) return fill;

if (!IsExchangeOpen(asset)) return fill;

var askPrice = GetAskPrice(asset, out var askEndTime);
var bidPrice = GetBidPrice(asset, out var bidEndTime);
var pricesEndTime = askEndTime > bidEndTime ? askEndTime : bidEndTime;

// Do not fill on stale data
if (pricesEndTime <= order.Time) return fill;

var midpoint = (bidPrice + askPrice) / 2m;

switch (order.Direction)
{
case OrderDirection.Buy:
{
var fillPrice = midpoint + order.LimitPriceOffset;
// LimitPrice acts as a cap: skip fill if effective price exceeds it
if (order.LimitPrice > 0 && fillPrice > order.LimitPrice) break;
fill.Status = OrderStatus.Filled;
fill.FillPrice = fillPrice;
fill.FillQuantity = order.Quantity;
break;
}
case OrderDirection.Sell:
{
var fillPrice = midpoint - order.LimitPriceOffset;
// LimitPrice acts as a floor: skip fill if effective price falls below it
if (order.LimitPrice > 0 && fillPrice < order.LimitPrice) break;
fill.Status = OrderStatus.Filled;
fill.FillPrice = fillPrice;
fill.FillQuantity = order.Quantity;
break;
}
}

return fill;
}

/// <summary>
/// Default limit order fill model in the base security class.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions Common/Orders/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ private static Order CreateOrder(int orderId, OrderType type, Symbol symbol, dec
order = new ComboMarketOrder(symbol, quantity, time, groupOrderManager, tag, properties);
break;

case OrderType.PeggedToMidpoint:
order = new PeggedToMidpointOrder(symbol, quantity, limitPrice, triggerPrice, time, tag, properties);
break;

default:
throw new ArgumentOutOfRangeException();
}
Expand Down
8 changes: 8 additions & 0 deletions Common/Orders/OrderJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,14 @@ private static Order CreateOrder(OrderType orderType, JObject jObject)
};
break;

case OrderType.PeggedToMidpoint:
order = new PeggedToMidpointOrder
{
LimitPrice = jObject["limitPrice"]?.Value<decimal>() ?? jObject["LimitPrice"]?.Value<decimal>() ?? default(decimal),
LimitPriceOffset = jObject["limitPriceOffset"]?.Value<decimal>() ?? jObject["LimitPriceOffset"]?.Value<decimal>() ?? default(decimal)
};
break;

default:
throw new ArgumentOutOfRangeException();
}
Expand Down
7 changes: 6 additions & 1 deletion Common/Orders/OrderTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ public enum OrderType
/// <summary>
/// Trailing Stop Order Type - (11)
/// </summary>
TrailingStop
TrailingStop,

/// <summary>
/// Pegged to Midpoint Order Type - (12)
/// </summary>
PeggedToMidpoint
}

/// <summary>
Expand Down
Loading