396 lines
16 KiB
C#
396 lines
16 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 NAMESPACES
|
|
**********************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using QuantConnect.Algorithm;
|
|
using QuantConnect.AlgorithmFactory;
|
|
using QuantConnect.Brokerages;
|
|
using QuantConnect.Brokerages.Tradier;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Lean.Engine.Results;
|
|
using QuantConnect.Logging;
|
|
using QuantConnect.Orders;
|
|
using QuantConnect.Packets;
|
|
using QuantConnect.Securities;
|
|
|
|
namespace QuantConnect.Lean.Engine.Setup
|
|
{
|
|
/// <summary>
|
|
/// Setup the algorithm for live trading with Tradier Brokerage! Get the brokerage cash, portfolio and setup algorithm internal state.
|
|
/// </summary>
|
|
public class TradierSetupHandler : ISetupHandler
|
|
{
|
|
/********************************************************
|
|
* PRIVATE VARIABLES
|
|
*********************************************************/
|
|
private TradierBrokerage _tradier = new TradierBrokerage();
|
|
|
|
/********************************************************
|
|
* PUBLIC PROPERTIES
|
|
*********************************************************/
|
|
/// <summary>
|
|
/// Internal errors list from running the setup proceedures.
|
|
/// </summary>
|
|
public List<string> Errors { get; set; }
|
|
|
|
/// <summary>
|
|
/// Maximum runtime of the algorithm in seconds.
|
|
/// </summary>
|
|
/// <remarks>Maximum runtime is a formula based on the number and resolution of symbols requested, and the days backtesting</remarks>
|
|
public TimeSpan MaximumRuntime { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Starting capital according to the users initialize routine.
|
|
/// </summary>
|
|
/// <remarks>Set from the user code.</remarks>
|
|
/// <seealso cref="QCAlgorithm.SetCash(decimal)"/>
|
|
public decimal StartingCapital { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Start date for analysis loops to search for data.
|
|
/// </summary>
|
|
/// <seealso cref="QCAlgorithm.SetStartDate(DateTime)"/>
|
|
public DateTime StartingDate { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Maximum number of orders for this live paper trading algorithm. (int.MaxValue)
|
|
/// </summary>
|
|
/// <remarks>For live trading its almost impossible to limit the order number</remarks>
|
|
public int MaxOrders { get; private set; }
|
|
|
|
/********************************************************
|
|
* PUBLIC CONSTRUCTOR
|
|
*********************************************************/
|
|
/// <summary>
|
|
/// Setup the algorithm data, cash, job start end date etc:
|
|
/// </summary>
|
|
public TradierSetupHandler()
|
|
{
|
|
StartingCapital = 0;
|
|
MaxOrders = int.MaxValue;
|
|
StartingDate = new DateTime(1998, 01, 01);
|
|
MaximumRuntime = TimeSpan.FromDays(365 * 10);
|
|
Errors = new List<string>();
|
|
}
|
|
|
|
/********************************************************
|
|
* PUBLIC METHODS
|
|
*********************************************************/
|
|
/// <summary>
|
|
/// Creates a new algorithm instance. Checks configuration for a specific type name, and if present will
|
|
/// force it to find that one
|
|
/// </summary>
|
|
/// <param name="assemblyPath">Physical path of the algorithm dll.</param>
|
|
/// <returns>Algorithm instance</returns>
|
|
public IAlgorithm CreateAlgorithmInstance(string assemblyPath)
|
|
{
|
|
string error;
|
|
IAlgorithm algorithm;
|
|
|
|
// limit load times to 10 seconds and force the assembly to have exactly one derived type
|
|
var loader = new Loader(TimeSpan.FromSeconds(10), names => names.SingleOrDefault());
|
|
bool complete = loader.TryCreateAlgorithmInstanceWithIsolator(assemblyPath, out algorithm, out error);
|
|
if (!complete) throw new Exception(error + " Try re-building algorithm.");
|
|
|
|
return algorithm;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Primary entry point to setup a new algorithm
|
|
/// </summary>
|
|
/// <param name="algorithm">Algorithm instance</param>
|
|
/// <param name="brokerage">New brokerage output instance</param>
|
|
/// <param name="baseJob">Algorithm job task</param>
|
|
/// <returns>True on successfully setting up the algorithm state, or false on error.</returns>
|
|
public bool Setup(IAlgorithm algorithm, out IBrokerage brokerage, AlgorithmNodePacket baseJob)
|
|
{
|
|
//-> Initialize:
|
|
var initializeComplete = false;
|
|
var job = baseJob as LiveNodePacket;
|
|
var portfolioResolution = PortfolioResolution(algorithm.Securities);
|
|
|
|
//-> Connect to Tradier:
|
|
_tradier = new TradierBrokerage();
|
|
//_tradier = (Tradier)brokerage;
|
|
_tradier.SetTokens(job.UserId, job.AccessToken, job.RefreshToken, job.IssuedAt, job.LifeTime);
|
|
brokerage = _tradier;
|
|
|
|
// -> Refresh the session immediately, buy us 24 hours:
|
|
if (!_tradier.RefreshSession())
|
|
{
|
|
Errors.Add("Failed to refresh access token. Please login again.");
|
|
return false;
|
|
}
|
|
|
|
//-> Setup any user specific code:
|
|
try
|
|
{
|
|
algorithm.Initialize();
|
|
}
|
|
catch (Exception err)
|
|
{
|
|
Errors.Add("Failed to initialize user algorithm, Initialize() returned error - " + err.Message);
|
|
return false;
|
|
}
|
|
|
|
Log.Trace("TradierSetupHandler.Setup(): Algorithm initialized");
|
|
|
|
//-> Strip any FOREX Symbols:
|
|
var symbols = algorithm.Securities.Keys.ToList();
|
|
foreach(var symbol in symbols)
|
|
{
|
|
if (algorithm.Securities[symbol].Type == SecurityType.Forex)
|
|
{
|
|
algorithm.Securities.Remove(symbol);
|
|
}
|
|
}
|
|
|
|
//-> Fetch the orders on the account:
|
|
var orders = _tradier.FetchOrders(job.AccountId);
|
|
foreach (var order in orders)
|
|
{
|
|
//Ignore option orders for now.
|
|
if (order.Class != TradierOrderClass.Equity) continue;
|
|
|
|
var qcPrice = order.Price;
|
|
var qcQuantity = order.Quantity;
|
|
var qcType = OrderType.Limit;
|
|
var qcStatus = OrderStatus.None;
|
|
|
|
// Get the order type:
|
|
switch (order.Type)
|
|
{
|
|
case TradierOrderType.Market:
|
|
qcType = OrderType.Market;
|
|
break;
|
|
case TradierOrderType.Limit:
|
|
qcType = OrderType.Limit;
|
|
break;
|
|
case TradierOrderType.StopMarket:
|
|
qcType = OrderType.StopMarket;
|
|
break;
|
|
}
|
|
|
|
// Convert order direction to a quantity
|
|
switch (order.Direction)
|
|
{
|
|
case TradierOrderDirection.Buy:
|
|
case TradierOrderDirection.BuyToCover:
|
|
break;
|
|
|
|
case TradierOrderDirection.Sell:
|
|
case TradierOrderDirection.SellShort:
|
|
qcQuantity *= -1; //Invert quantity.
|
|
break;
|
|
}
|
|
|
|
//Set the QC Order Status Flag:
|
|
switch (order.Status)
|
|
{
|
|
case TradierOrderStatus.Canceled:
|
|
qcStatus = OrderStatus.Canceled;
|
|
break;
|
|
case TradierOrderStatus.Filled:
|
|
qcStatus = OrderStatus.Filled;
|
|
break;
|
|
case TradierOrderStatus.Open:
|
|
case TradierOrderStatus.Submitted:
|
|
case TradierOrderStatus.Pending:
|
|
qcStatus = OrderStatus.Submitted;
|
|
break;
|
|
case TradierOrderStatus.PartiallyFilled:
|
|
qcStatus = OrderStatus.PartiallyFilled;
|
|
break;
|
|
case TradierOrderStatus.Rejected:
|
|
qcStatus = OrderStatus.Invalid;
|
|
break;
|
|
}
|
|
|
|
//Create the new qcOrder
|
|
var qcOrder = new Order(order.Symbol, Convert.ToInt32((decimal) qcQuantity), qcType, order.CreatedDate, qcPrice);
|
|
//Set Status for Order:
|
|
qcOrder.Status = qcStatus;
|
|
|
|
//Create any fill information:
|
|
var fill = new OrderEvent(qcOrder, "Pre-existing Tradier Order");
|
|
fill.FillPrice = order.AverageFillPrice;
|
|
fill.FillQuantity = Convert.ToInt32((decimal) order.QuantityExecuted);
|
|
var fillList = new List<OrderEvent>() { fill };
|
|
|
|
//Get a unique qc-id: set to fill
|
|
var qcid = algorithm.Transactions.GetIncrementOrderId();
|
|
order.Id = qcid; fill.OrderId = qcid; qcOrder.Id = qcid;
|
|
|
|
//Add the order to our internal records:
|
|
algorithm.Transactions.Orders.AddOrUpdate<int, Order>(Convert.ToInt32((long) order.Id), qcOrder);
|
|
|
|
//Add the fill quantity to the list:
|
|
algorithm.Transactions.OrderEvents.AddOrUpdate<int, List<OrderEvent>>(Convert.ToInt32((long) order.Id), fillList);
|
|
|
|
//If we don't have this symbol, add it manually:
|
|
if (!algorithm.Portfolio.ContainsKey(order.Symbol))
|
|
{
|
|
algorithm.AddSecurity(SecurityType.Equity, order.Symbol, portfolioResolution, true, 1, false);
|
|
}
|
|
}
|
|
|
|
//-> Retrieve/Set Tradier Portfolio Positions:
|
|
var positions = _tradier.Positions(job.AccountId);
|
|
foreach (var position in positions)
|
|
{
|
|
//We can't support options.
|
|
if (position.Symbol.Length >= 10)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//If we don't have this symbol, add it manually:
|
|
if (!algorithm.Portfolio.ContainsKey(position.Symbol))
|
|
{
|
|
algorithm.AddSecurity(SecurityType.Equity, position.Symbol, portfolioResolution, true, 1, false);
|
|
}
|
|
//Once we have the symbol, set the holdings:
|
|
var avgPrice = Math.Round(position.CostBasis / Convert.ToDecimal((long) position.Quantity), 4);
|
|
algorithm.Portfolio[position.Symbol].SetHoldings(avgPrice, (int)position.Quantity);
|
|
Log.Trace("TradierSetupHandler.Setup(): Portfolio security added to algorithm: " + position.Symbol + " with " + position.Quantity + " shares at " + avgPrice.ToString("C"));
|
|
}
|
|
|
|
|
|
//-> Retrieve/Set Tradier Cash Positions:
|
|
var balanceFound = false;
|
|
|
|
//HACK:
|
|
//balanceFound = true;
|
|
//algorithm.Portfolio.SetCash(100000);
|
|
//_startingCapital = 100000;
|
|
|
|
var balance = _tradier.Balance(job.AccountId);
|
|
if (balance != null)
|
|
{
|
|
if (balance.AccountNumber == job.AccountId)
|
|
{
|
|
//Set the cash in this account:
|
|
var cash = balance.TotalCash - balance.OptionRequirement;
|
|
algorithm.Portfolio.SetCash(cash);
|
|
StartingCapital = cash;
|
|
balanceFound = true;
|
|
Log.Trace("TradierSetupHandler.Setup(): Free Cash: " + cash.ToString("C"));
|
|
Log.Trace("TradierSetupHandler.Setup(): Total Cash: " + balance.TotalCash.ToString("C"));
|
|
}
|
|
|
|
//Set the leverage on all the securities:
|
|
switch (balance.Type)
|
|
{
|
|
//Maximum 1x Leverage
|
|
case TradierAccountType.Cash:
|
|
foreach (var security in algorithm.Securities.Values)
|
|
{
|
|
if (security.Type == SecurityType.Equity)
|
|
{
|
|
security.SetLeverage(1m);
|
|
}
|
|
}
|
|
break;
|
|
|
|
//Maximum 2x Leverage
|
|
case TradierAccountType.Margin:
|
|
foreach (var security in algorithm.Securities.Values)
|
|
{
|
|
if (security.Type == SecurityType.Equity && security.Leverage > 2)
|
|
{
|
|
security.SetLeverage(2m);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TradierAccountType.DayTrader:
|
|
//Do nothing, let the user set their own leverage:
|
|
foreach (var security in algorithm.Securities.Values)
|
|
{
|
|
if (security.Type == SecurityType.Equity && security.Leverage > 4)
|
|
{
|
|
security.SetLeverage(4m);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Maximum number of orders or the algorithm
|
|
MaxOrders = int.MaxValue;
|
|
|
|
if (!balanceFound)
|
|
{
|
|
Errors.Add("Could not get the account cash balance");
|
|
}
|
|
|
|
if (Errors.Count == 0)
|
|
{
|
|
initializeComplete = true;
|
|
}
|
|
return initializeComplete;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the lowest resolution of the portfolio manager.
|
|
/// </summary>
|
|
/// <param name="securities">List of securities we're scanning.</param>
|
|
/// <returns>Resolution frequency of desired updates</returns>
|
|
private static Resolution PortfolioResolution(SecurityManager securities)
|
|
{
|
|
var resolution = Resolution.Minute;
|
|
//Go through the portfolio, find the lowest common resolution:
|
|
foreach (var asset in securities.Values)
|
|
{
|
|
//Enum comparison of resolution int values:
|
|
if ((int)asset.Resolution < (int)resolution)
|
|
{
|
|
resolution = asset.Resolution;
|
|
}
|
|
}
|
|
return resolution;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Error handlers in event of a brokerage error.
|
|
/// </summary>
|
|
/// <param name="results">Result handler for sending results on error.</param>
|
|
/// <param name="brokerage">Brokerage instance firing the errors</param>
|
|
/// <returns>Boolean true on successfully setting up local algorithm</returns>
|
|
public bool SetupErrorHandler(IResultHandler results, IBrokerage brokerage)
|
|
{
|
|
//Setup handler for access token error.
|
|
brokerage.AddErrorHander("Access Token expired", () =>
|
|
{
|
|
results.RuntimeError("Brokerage access token has expired. In general this should not happen, please contact support@quantconnect.com");
|
|
});
|
|
brokerage.AddErrorHander("Invalid Access Token", () =>
|
|
{
|
|
results.RuntimeError("Access token is invalid. In general this should not happen, please contact support@quantconnect.com");
|
|
});
|
|
return true;
|
|
}
|
|
|
|
} // End Result Handler Thread:
|
|
|
|
} // End Namespace
|