예제 #1
0
        /********************************************************
         * 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.");
        }
예제 #2
0
        /// <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);
        }