Skip to content

Commit

Permalink
Merge pull request #1938 from StefanoRaggi/feature-1093-ib-time-in-fo…
Browse files Browse the repository at this point in the history
…rce-day

Add TimeInForce.Day support in backtesting and IB brokerage
  • Loading branch information
mchandschuh authored May 3, 2018
2 parents c1f194b + 62e1c2e commit d878857
Show file tree
Hide file tree
Showing 29 changed files with 824 additions and 71 deletions.
1 change: 1 addition & 0 deletions Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
<Compile Include="HourSplitRegressionAlgorithm.cs" />
<Compile Include="ScheduledUniverseSelectionModelRegressionAlgorithm.cs" />
<Compile Include="StandardDeviationExecutionModelRegressionAlgorithm.cs" />
<Compile Include="TimeInForceAlgorithm.cs" />
<Compile Include="UniverseSelectionDefinitionsAlgorithm.cs" />
<Compile Include="UserDefinedUniverseAlgorithm.cs" />
<Compile Include="VolumeWeightedAveragePriceExecutionModelRegressionAlgorithm.cs" />
Expand Down
86 changes: 86 additions & 0 deletions Algorithm.CSharp/TimeInForceAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using QuantConnect.Data;
using QuantConnect.Orders;

namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Demonstration algorithm of time in force order settings.
/// </summary>
/// <meta name="tag" content="using data" />
/// <meta name="tag" content="using quantconnect" />
/// <meta name="tag" content="trading and orders" />
public class TimeInForceAlgorithm : QCAlgorithm
{
private Symbol _symbol;
private OrderTicket _gtcOrderTicket;
private OrderTicket _dayOrderTicket;

/// <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);
SetCash(100000);

// The default time in force setting for all orders is GoodTilCancelled (GTC),
// uncomment this line to set a different time in force.
// We currently only support GTC and DAY.
// DefaultOrderProperties.TimeInForce = TimeInForce.Day;

_symbol = AddEquity("SPY", Resolution.Minute).Symbol;
}

/// <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 (_gtcOrderTicket == null)
{
// This order has a default time in force of GoodTilCanceled,
// it will never expire and will not be canceled automatically.

DefaultOrderProperties.TimeInForce = TimeInForce.GoodTilCanceled;
_gtcOrderTicket = LimitOrder(_symbol, 10, 160m);
}

if (_dayOrderTicket == null)
{
// This order will expire at market close,
// if not filled by then it will be canceled automatically.

DefaultOrderProperties.TimeInForce = TimeInForce.Day;
_dayOrderTicket = LimitOrder(_symbol, 10, 160m);
}
}

/// <summary>
/// Order event handler. This handler will be called for all order events, including submissions, fills, cancellations.
/// </summary>
/// <param name="orderEvent">Order event instance containing details of the event</param>
/// <remarks>This method can be called asynchronously, ensure you use proper locks on thread-unsafe objects</remarks>
public override void OnOrderEvent(OrderEvent orderEvent)
{
Debug($"{Time} {orderEvent}");
}

}
}
1 change: 1 addition & 0 deletions Algorithm.Python/QuantConnect.Algorithm.Python.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
<None Include="CustomSecurityInitializerAlgorithm.py" />
</ItemGroup>
<ItemGroup>
<None Include="TimeInForceAlgorithm.py" />
<Content Include="PairsTradingAlphaModelFrameworkAlgorithm.py" />
<Content Include="StandardDeviationExecutionModelRegressionAlgorithm.py" />
<Content Include="VolumeWeightedAveragePriceExecutionModelRegressionAlgorithm.py" />
Expand Down
71 changes: 71 additions & 0 deletions Algorithm.Python/TimeInForceAlgorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Orders import *

### <summary>
### Demonstration algorithm of time in force order settings.
### </summary>
### <meta name="tag" content="using data" />
### <meta name="tag" content="using quantconnect" />
### <meta name="tag" content="trading and orders" />
class TimeInForceAlgorithm(QCAlgorithm):

# Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
def Initialize(self):

self.SetStartDate(2013,10,7)
self.SetEndDate(2013,10,11)
self.SetCash(100000)

# The default time in force setting for all orders is GoodTilCancelled (GTC),
# uncomment this line to set a different time in force.
# We currently only support GTC and DAY.
# self.DefaultOrderProperties.TimeInForce = TimeInForce.Day;

self.symbol = self.AddEquity("SPY", Resolution.Second).Symbol

self.gtcOrderTicket = None
self.dayOrderTicket = None

# OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
# Arguments:
# data: Slice object keyed by symbol containing the stock data
def OnData(self, data):

if self.gtcOrderTicket is None:
# This order has a default time in force of GoodTilCanceled,
# it will never expire and will not be canceled automatically.

self.DefaultOrderProperties.TimeInForce = TimeInForce.GoodTilCanceled
self.gtcOrderTicket = self.LimitOrder(self.symbol, 10, 160)

if self.dayOrderTicket is None:
# This order will expire at market close,
# if not filled by then it will be canceled automatically.

self.DefaultOrderProperties.TimeInForce = TimeInForce.Day
self.dayOrderTicket = self.LimitOrder(self.symbol, 10, 160)

# Order event handler. This handler will be called for all order events, including submissions, fills, cancellations.
# This method can be called asynchronously, ensure you use proper locks on thread-unsafe objects
def OnOrderEvent(self, orderEvent):
self.Debug(f"{self.Time} {orderEvent}")
1 change: 1 addition & 0 deletions Algorithm/QCAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public QCAlgorithm()
_localTimeKeeper = _timeKeeper.GetLocalTimeKeeper(TimeZones.NewYork);

Settings = new AlgorithmSettings();
DefaultOrderProperties = new OrderProperties();

//Initialise Data Manager
SubscriptionManager = new SubscriptionManager(Settings, _timeKeeper);
Expand Down
30 changes: 30 additions & 0 deletions Brokerages/Backtesting/BacktestingBrokerage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using QuantConnect.Interfaces;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Orders.TimeInForces;
using QuantConnect.Securities;
using QuantConnect.Securities.Option;

Expand All @@ -39,6 +40,14 @@ public class BacktestingBrokerage : Brokerage
private readonly object _needsScanLock = new object();
private readonly HashSet<Symbol> _pendingOptionAssignments = new HashSet<Symbol>();

private readonly Dictionary<TimeInForce, ITimeInForceHandler> _timeInForceHandlers = new Dictionary<TimeInForce, ITimeInForceHandler>
{
{ TimeInForce.GoodTilCanceled, new GoodTilCanceledTimeInForceHandler() },
{ TimeInForce.Day, new DayTimeInForceHandler() },
// Custom time in force will be renamed to GTD soon and will have its own new handler
{ TimeInForce.Custom, new GoodTilCanceledTimeInForceHandler() }
};

/// <summary>
/// This is the algorithm under test
/// </summary>
Expand Down Expand Up @@ -79,6 +88,7 @@ public BacktestingBrokerage(IAlgorithm algorithm, IBacktestingMarketSimulation m
MarketSimulation = marketSimulation;
_pending = new ConcurrentDictionary<int, Order>();
}

/// <summary>
/// Gets the connection status
/// </summary>
Expand Down Expand Up @@ -276,6 +286,20 @@ public void Scan()
continue;
}

var timeInForceHandler = _timeInForceHandlers[order.TimeInForce];

// check if the time in force handler allows fills
if (timeInForceHandler.IsOrderExpired(security, order))
{
OnOrderEvent(new OrderEvent(order, Algorithm.UtcTime, 0m)
{
Status = OrderStatus.Canceled,
Message = "The order has expired."
});
_pending.TryRemove(order.Id, out order);
continue;
}

// check if we would actually be able to fill this
if (!Algorithm.BrokerageModel.CanExecuteOrder(security, order))
{
Expand Down Expand Up @@ -361,6 +385,12 @@ public void Scan()

foreach (var fill in fills)
{
// check if the fill should be emitted
if (!timeInForceHandler.IsFillValid(security, order, fill))
{
break;
}

// change in status or a new fill
if (order.Status != fill.Status || fill.FillQuantity != 0)
{
Expand Down
6 changes: 3 additions & 3 deletions Brokerages/Fxcm/FxcmBrokerage.Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private Order ConvertOrder(ExecutionReport fxcmOrder)
order.Quantity = Convert.ToInt32(fxcmOrder.getOrderQty() * (fxcmOrder.getSide() == SideFactory.BUY ? +1 : -1));
order.Status = ConvertOrderStatus(fxcmOrder.getFXCMOrdStatus());
order.BrokerId.Add(fxcmOrder.getOrderID());
order.TimeInForce = ConvertTimeInForce(fxcmOrder.getTimeInForce());
order.Properties.TimeInForce = ConvertTimeInForce(fxcmOrder.getTimeInForce());
order.Time = FromJavaDate(fxcmOrder.getTransactTime().toDate());

return order;
Expand All @@ -81,10 +81,10 @@ private Order ConvertOrder(ExecutionReport fxcmOrder)
private static TimeInForce ConvertTimeInForce(ITimeInForce timeInForce)
{
if (timeInForce == TimeInForceFactory.GOOD_TILL_CANCEL)
return TimeInForce.GoodTilCancelled;
return TimeInForce.GoodTilCanceled;

if (timeInForce == TimeInForceFactory.DAY)
return (TimeInForce)1; //.Day;
return TimeInForce.Day;

throw new ArgumentOutOfRangeException();
}
Expand Down
73 changes: 63 additions & 10 deletions Brokerages/InteractiveBrokers/InteractiveBrokersBrokerage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1621,20 +1621,11 @@ private IBApi.Order ConvertOrder(Order order, Contract contract, int ibOrderId)
TotalQuantity = (int)Math.Abs(order.Quantity),
OrderType = ConvertOrderType(order.Type),
AllOrNone = false,
Tif = IB.TimeInForce.GoodTillCancel,
Tif = ConvertTimeInForce(order),
Transmit = true,
Rule80A = _agentDescription
};

if (order.Type == OrderType.MarketOnOpen)
{
ibOrder.Tif = IB.TimeInForce.MarketOnOpen;
}
else if (order.Type == OrderType.MarketOnClose)
{
ibOrder.Tif = IB.TimeInForce.Day;
}

var limitOrder = order as LimitOrder;
var stopMarketOrder = order as StopMarketOrder;
var stopLimitOrder = order as StopLimitOrder;
Expand Down Expand Up @@ -1759,6 +1750,8 @@ private Order ConvertOrder(IBApi.Order ibOrder, Contract contract)

order.BrokerId.Add(ibOrder.OrderId.ToString());

order.Properties.TimeInForce = ConvertTimeInForce(ibOrder.Tif);

return order;
}

Expand Down Expand Up @@ -1898,6 +1891,66 @@ private static OrderType ConvertOrderType(IBApi.Order order)
}
}

/// <summary>
/// Maps TimeInForce from IB to LEAN
/// </summary>
private static TimeInForce ConvertTimeInForce(string timeInForce)
{
switch (timeInForce)
{
case IB.TimeInForce.Day:
return TimeInForce.Day;

//case IB.TimeInForce.GoodTillDate:
// return TimeInForce.GoodTilDate;

//case IB.TimeInForce.FillOrKill:
// return TimeInForce.FillOrKill;

//case IB.TimeInForce.ImmediateOrCancel:
// return TimeInForce.ImmediateOrCancel;

case IB.TimeInForce.MarketOnOpen:
case IB.TimeInForce.GoodTillCancel:
default:
return TimeInForce.GoodTilCanceled;
}
}

/// <summary>
/// Maps TimeInForce from LEAN to IB
/// </summary>
private static string ConvertTimeInForce(Order order)
{
if (order.Type == OrderType.MarketOnOpen)
{
return IB.TimeInForce.MarketOnOpen;
}
if (order.Type == OrderType.MarketOnClose)
{
return IB.TimeInForce.Day;
}

switch (order.TimeInForce)
{
case TimeInForce.Day:
return IB.TimeInForce.Day;

//case TimeInForce.GoodTilDate:
// return IB.TimeInForce.GoodTillDate;

//case TimeInForce.FillOrKill:
// return IB.TimeInForce.FillOrKill;

//case TimeInForce.ImmediateOrCancel:
// return IB.TimeInForce.ImmediateOrCancel;

case TimeInForce.GoodTilCanceled:
default:
return IB.TimeInForce.GoodTillCancel;
}
}

/// <summary>
/// Maps IB's OrderStats enum
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Brokerages/Oanda/OandaRestApiV1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ private Order ConvertOrder(RestV1.DataType.Order order)
qcOrder.Id = orderByBrokerageId.Id;
}

qcOrder.TimeInForce = TimeInForce.Custom;
qcOrder.Properties.TimeInForce = TimeInForce.Custom;
qcOrder.DurationValue = XmlConvert.ToDateTime(order.expiry, XmlDateTimeSerializationMode.Utc);
qcOrder.Time = XmlConvert.ToDateTime(order.time, XmlDateTimeSerializationMode.Utc);

Expand Down
2 changes: 1 addition & 1 deletion Brokerages/Oanda/OandaRestApiV20.cs
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ private Order ConvertOrder(JToken order)
var gtdTime = order["gtdTime"];
if (gtdTime != null)
{
qcOrder.TimeInForce = TimeInForce.Custom;
qcOrder.Properties.TimeInForce = TimeInForce.Custom;
qcOrder.DurationValue = GetTickDateTimeFromString(gtdTime.ToString());
}

Expand Down
Loading

0 comments on commit d878857

Please sign in to comment.