/******************************************************** * CLASS METHODS *********************************************************/ /// <summary> /// Primary thread entry point to launch the transaction thread. /// </summary> public void Run() { while (!_exitTriggered) { try { //1. Add order commands from queue to primary order list. if (OrderQueue.Count == 0) { //We've processed all the orders in queue.Allow interruption of thread if nothing to do (99.99% of time). _ready = true; //Set finished processing flag: _algorithm.ProcessingOrder = false; //NOP. Thread.Sleep(1); } else { //We're now processing an order: _ready = false; //Scan jobs in the new orders queue: Order order; if (OrderQueue.TryDequeue(out order)) { switch (order.Status) { case OrderStatus.New: //If we don't have this key, add it to the dictionary if (!Orders.ContainsKey(order.Id)) { //Tell algorithm to wait: _algorithm.ProcessingOrder = true; //TRADIER Requires Processing Cross-Zero Orders in TWO Parts: //-> If Long Going Short, Break order up into two, process one as "Sell", other as "Short". //If neccessary divide the order into two components: var portfolio = _algorithm.Portfolio; if (portfolio.ContainsKey(order.Symbol)) { var crossZero = DetectZeroCrossing(order, portfolio[order.Symbol]); var currentHoldings = portfolio[order.Symbol].Quantity; //If crossing zero, first order is to close out position if (crossZero) { var firstOrderQuantity = 0; var secondOrderQuantity = 0; //Break into two orders, make second order contingent on first processing: //1. First order, close out to zero. if (currentHoldings > 0) { // First order close out to zero: a sell order: firstOrderQuantity = -1 * currentHoldings; secondOrderQuantity = -1 * Convert.ToInt32(order.AbsoluteQuantity - currentHoldings); } else if (currentHoldings < 0) { firstOrderQuantity = Math.Abs(currentHoldings); secondOrderQuantity = order.Quantity - firstOrderQuantity; } //Set the first order quantity: order.Quantity = firstOrderQuantity; while (!Orders.TryAdd(order.Id, order)) { } ; //Create the second order: add to queue, make contingent on primary order. var secondOrder = new Order(order.Symbol, Convert.ToInt32(secondOrderQuantity), order.Type, order.Time, order.Price, order.Tag); secondOrder.Id = _algorithm.Transactions.GetIncrementOrderId(); secondOrder.Status = OrderStatus.New; secondOrder.ContingentId = order.Id; while (!Orders.TryAdd(secondOrder.Id, order)) { } ; } else { //If not zero crossing, simply add the order to the collection while (!Orders.TryAdd(order.Id, order)) { } ; } } } break; case OrderStatus.Canceled: if (Orders.ContainsKey(order.Id) && Orders[order.Id].Status == OrderStatus.Submitted) { //Just set the master dictionary to a cancelled order, only IF we've only been submitted and no further processing. Orders[order.Id] = order; } break; case OrderStatus.Update: if (Orders.ContainsKey(order.Id) && Orders[order.Id].Status == OrderStatus.Submitted) { //Just set the master dictionary to a updated order, only IF we've only been submitted and no further processing. Orders[order.Id] = order; } break; } } } //2. NOW ALL ORDERS IN ORDER DICTIONARY::> // Scan through Orders: Process fills. Trigger Events. // Refresh the order model: look at the orders for ones - process every time. var keys = (from order in Orders where order.Value.Status != OrderStatus.Filled && order.Value.Status != OrderStatus.Canceled && order.Value.Status != OrderStatus.Invalid && order.Value.Direction != OrderDirection.Hold select order.Key).ToList <int>(); //Now we have the list of keys; re-apply the order models to each order. foreach (var id in keys) { //We're working... _ready = false; var order = Orders[id]; //Make sure we have this in our portfolio: if (!_algorithm.Portfolio.ContainsKey(order.Symbol)) { continue; } //Don't process until contingent order completed: if (order.ContingentId != 0 && Orders.ContainsKey(order.ContingentId)) { if (Orders[order.ContingentId].Status != OrderStatus.Filled) { continue; } } //Make sure we have sufficient buying power: var sufficientBuyingPower = _algorithm.Transactions.GetSufficientCapitalForOrder(_algorithm.Portfolio, order); //Before we check this queued order make sure we have buying power: if (sufficientBuyingPower) { var response = _tradier.PlaceOrder( accountId: _accountId, classification: TradierOrderClass.Equity, direction: Direction(_algorithm.Portfolio[order.Symbol].Quantity, order), symbol: order.Symbol, quantity: Convert.ToDecimal(order.AbsoluteQuantity), price: order.Price, stop: order.Price, optionSymbol: "", type: OrderType(order.Type), duration: TradierOrderDuration.GTC); if (response != null && response.Order != null && response.Errors.Errors.Count == 0) { //Save brokerage Id: order.BrokerId.Add(response.Order.Id); order.Tag = response.Order.Status; //Set status as submitted, no more: order.Status = OrderStatus.Submitted; } } else { //Flag order as invalid and push off queue: order.Status = OrderStatus.Invalid; _algorithm.Error("Order Error: id: " + id + ": Insufficient buying power to complete order."); } } // Check the key list: if more than 0-> there are orders pending: if (keys.Count > 0 && DateTime.Now > _refreshOrders) { //Fetch orders and schedule for next refresh in 200ms. var orderDetails = _tradier.FetchOrders(_accountId); _refreshOrders = DateTime.Now.AddMilliseconds(200); //Go through each submitted order, detect fills, process fills when delta from known fill. foreach (var orderState in orderDetails) { //Process order: detect fills. if (orderState.Class != TradierOrderClass.Equity) { continue; } var deltaFilled = orderState.QuantityExecuted; var status = OrderStatus.Filled; //Look at fill prices as they happen, detect if Quantity changes from known quantity var previousState = (from previous in _previousOrders where previous.Id == orderState.Id select previous).SingleOrDefault(); // Previous exists, find the delta between order - previous. if (previousState != null) { deltaFilled = orderState.QuantityExecuted - previousState.QuantityExecuted; } //Set the state of the fill event: if (orderState.RemainingQuantity > 0) { status = OrderStatus.PartiallyFilled; } // Generate the (Partial Fill) OrderEvent - var fillEvent = new OrderEvent(Convert.ToInt32((long)orderState.Id), orderState.Symbol, status, orderState.AverageFillPrice, Convert.ToInt32((decimal)deltaFilled), "Tradier Fill Event"); // Create (partial)fill Objects - _algorithm.Portfolio.ProcessFill(fillEvent); try { // Fire Order Events _algorithm.OnOrderEvent(fillEvent); } catch (Exception err) { _results.RuntimeError("Caught Error OnOrderEvent(): " + err.Message, err.StackTrace); } } //Save the previous order information: _previousOrders = orderDetails; } } catch (Exception err) { Log.Trace("TradierTransactionHandler.Run(): " + err.Message + " > > " + err.StackTrace); } } //Set flag thread ended. _isActive = false; Log.Trace("TradierTransactionHandler.Run(): Transaction Handler Thread Completed."); }
/// <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); }