Exemple #1
0
        /// <summary>
        /// Gets the current cash balance for each currency held in the brokerage account
        /// </summary>
        /// <returns>The current cash balance for each currency available for trading</returns>
        public override List <CashAmount> GetCashBalance()
        {
            var balances = _api.GetCashBalance().ToDictionary(x => x.Currency);

            // include cash balances from currency swaps for open Forex positions
            foreach (var holding in GetAccountHoldings().Where(x => x.Symbol.SecurityType == SecurityType.Forex))
            {
                string baseCurrency;
                string quoteCurrency;
                Forex.DecomposeCurrencyPair(holding.Symbol.Value, out baseCurrency, out quoteCurrency);

                var        baseQuantity = holding.Quantity;
                CashAmount baseCurrencyAmount;
                balances[baseCurrency] = balances.TryGetValue(baseCurrency, out baseCurrencyAmount)
                    ? new CashAmount(baseQuantity + baseCurrencyAmount.Amount, baseCurrency)
                    : new CashAmount(baseQuantity, baseCurrency);

                var        quoteQuantity = -holding.Quantity * holding.AveragePrice;
                CashAmount quoteCurrencyAmount;
                balances[quoteCurrency] = balances.TryGetValue(quoteCurrency, out quoteCurrencyAmount)
                    ? new CashAmount(quoteQuantity + quoteCurrencyAmount.Amount, quoteCurrency)
                    : new CashAmount(quoteQuantity, quoteCurrency);
            }

            return(balances.Values.ToList());
        }
Exemple #2
0
        /// <summary>
        /// Create a simple JSON holdings from a Security holding class.
        /// </summary>
        /// <param name="security">The security instance</param>
        public Holding(Security security)
            : this()
        {
            var holding = security.Holdings;

            Symbol   = holding.Symbol;
            Type     = holding.Type;
            Quantity = holding.Quantity;

            var rounding = 2;

            if (holding.Type == SecurityType.Forex)
            {
                rounding = 5;
                string basec, quotec;
                Forex.DecomposeCurrencyPair(holding.Symbol.Value, out basec, out quotec);
                CurrencySymbol = Currencies.CurrencySymbols[quotec];
                ConversionRate = ((ForexHolding)holding).ConversionRate;
            }
            else if (holding.Type == SecurityType.Cfd)
            {
                rounding = 5;
                var cfd    = (Cfd)security;
                var quotec = cfd.QuoteCurrencySymbol;
                CurrencySymbol = Currencies.CurrencySymbols[quotec];
                ConversionRate = ((CfdHolding)holding).ConversionRate;
            }

            AveragePrice = Math.Round(holding.AveragePrice, rounding);
            MarketPrice  = Math.Round(holding.Price, rounding);
        }
Exemple #3
0
        /// <summary>
        /// Decomposes the specified currency pair into a base and quote currency provided as out parameters
        /// </summary>
        /// <param name="currencyPair">The input currency pair to be decomposed</param>
        /// <param name="baseCurrency">The output base currency</param>
        /// <param name="quoteCurrency">The output quote currency</param>
        /// <param name="defaultQuoteCurrency">Optionally can provide a default quote currency</param>
        public static void DecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency, string defaultQuoteCurrency = Currencies.USD)
        {
            IsValidSecurityType(currencyPair?.SecurityType, throwException: true);
            var securityType = currencyPair.SecurityType;

            if (securityType == SecurityType.Forex)
            {
                Forex.DecomposeCurrencyPair(currencyPair.Value, out baseCurrency, out quoteCurrency);
                return;
            }

            var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties(
                currencyPair.ID.Market,
                currencyPair,
                currencyPair.SecurityType,
                defaultQuoteCurrency);

            if (securityType == SecurityType.Cfd)
            {
                Cfd.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
            }
            else
            {
                Crypto.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
            }
        }
Exemple #4
0
 public void HasCurrencySymbolForEachPair()
 {
     foreach (var currencyPair in Currencies.CurrencyPairs)
     {
         string quotec, basec;
         Forex.DecomposeCurrencyPair(currencyPair, out basec, out quotec);
         Assert.IsTrue(Currencies.CurrencySymbols.ContainsKey(basec), "Missing currency symbol for: " + basec);
         Assert.IsTrue(Currencies.CurrencySymbols.ContainsKey(quotec), "Missing currency symbol for: " + quotec);
     }
 }
Exemple #5
0
        private IEnumerable <Tick> GetForexQuoteTicks(HistoryRequest request)
        {
            // https://api.polygon.io/v1/historic/forex/EUR/USD/2020-08-24?apiKey=

            var start = request.StartTimeUtc;
            var end   = request.EndTimeUtc;

            while (start <= end)
            {
                using (var client = new WebClient())
                {
                    string baseCurrency;
                    string quoteCurrency;
                    Forex.DecomposeCurrencyPair(request.Symbol.Value, out baseCurrency, out quoteCurrency);

                    var offset = Convert.ToInt64(Time.DateTimeToUnixTimeStampMilliseconds(start));
                    var url    = $"{HistoryBaseUrl}/v1/historic/forex/{baseCurrency}/{quoteCurrency}/{start.Date:yyyy-MM-dd}?apiKey={_apiKey}&offset={offset}";

                    var response = client.DownloadString(url);

                    var obj      = JObject.Parse(response);
                    var objTicks = obj["ticks"];
                    if (objTicks.Type == JTokenType.Null)
                    {
                        // current date finished, move to next day
                        start = start.Date.AddDays(1);
                        continue;
                    }

                    foreach (var objTick in objTicks)
                    {
                        var row = objTick.ToObject <ForexQuoteTickResponse>();

                        var utcTime = Time.UnixMillisecondTimeStampToDateTime(row.Timestamp);

                        if (utcTime < start)
                        {
                            continue;
                        }

                        start = utcTime.AddMilliseconds(1);

                        if (utcTime > end)
                        {
                            yield break;
                        }

                        var time = GetTickTime(request.Symbol, utcTime);

                        yield return(new Tick(time, request.Symbol, row.Bid, row.Ask));
                    }
                }
            }
        }
Exemple #6
0
        /// <summary>
        /// Returns true if the specified order is within IB's order size limits
        /// </summary>
        private bool IsForexWithinOrderSizeLimits(Order order, out BrokerageMessageEvent message)
        {
            /* https://www.interactivebrokers.com/en/?f=%2Fen%2Ftrading%2FforexOrderSize.php
             * Currency    Currency Description	    Minimum Order Size	Maximum Order Size
             * USD	        US Dollar	                25,000              7,000,000
             * AUD	        Australian Dollar	        25,000              6,000,000
             * CAD	        Canadian Dollar	            25,000              6,000,000
             * CHF	        Swiss Franc	                25,000	            6,000,000
             * CNH	        China Renminbi (offshore)	160,000	            40,000,000
             * CZK	        Czech Koruna	            USD 25,000(1)	    USD 7,000,000(1)
             * DKK	        Danish Krone	            150,000	            35,000,000
             * EUR	        Euro	                    20,000	            5,000,000
             * GBP	        British Pound Sterling	    17,000	            4,000,000
             * HKD	        Hong Kong Dollar	        200,000	            50,000,000
             * HUF	        Hungarian Forint	        USD 25,000(1)	    USD 7,000,000(1)
             * ILS	        Israeli Shekel	            USD 25,000(1)	    USD 7,000,000(1)
             * KRW	        Korean Won	                50,000,000	        750,000,000
             * JPY	        Japanese Yen	            2,500,000	        550,000,000
             * MXN	        Mexican Peso	            300,000	            70,000,000
             * NOK	        Norwegian Krone	            150,000	            35,000,000
             * NZD	        New Zealand Dollar	        35,000	            8,000,000
             * RUB	        Russian Ruble	            750,000	            30,000,000
             * SEK	        Swedish Krona	            175,000	            40,000,000
             * SGD	        Singapore Dollar	        35,000	            8,000,000
             */

            message = null;

            // switch on the currency being bought
            string baseCurrency, quoteCurrency;

            Forex.DecomposeCurrencyPair(order.Symbol, out baseCurrency, out quoteCurrency);


            decimal max;

            ForexCurrencyLimits.TryGetValue(baseCurrency, out max);

            var orderIsWithinForexSizeLimits = order.Quantity < max;

            if (!orderIsWithinForexSizeLimits)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSizeLimit",
                                                    string.Format("The maximum allowable order size is {0}{1}.", max, baseCurrency)
                                                    );
            }
            return(orderIsWithinForexSizeLimits);
        }
Exemple #7
0
        public void HasCurrencySymbolForEachForexPair(SecurityType securityType, string market)
        {
            var symbols = SymbolPropertiesDatabase
                          .FromDataFolder()
                          .GetSymbolPropertiesList(market, securityType)
                          .Select(x => x.Key.Symbol);

            foreach (var symbol in symbols)
            {
                string baseCurrency, quoteCurrency;
                Forex.DecomposeCurrencyPair(symbol, out baseCurrency, out quoteCurrency);

                Assert.IsTrue(!string.IsNullOrWhiteSpace(Currencies.GetCurrencySymbol(baseCurrency)), "Missing currency symbol for: " + baseCurrency);
                Assert.IsTrue(!string.IsNullOrWhiteSpace(Currencies.GetCurrencySymbol(quoteCurrency)), "Missing currency symbol for: " + quoteCurrency);
            }
        }
Exemple #8
0
        private bool IsForexWithinOrderSizeLimits(string currencyPair, int quantity, out BrokerageMessageEvent message)
        {
            message = null;
            string baseCurrency, quoteCurrency;

            Forex.DecomposeCurrencyPair(currencyPair, out baseCurrency, out quoteCurrency);
            decimal max;

            ForexCurrencyLimits.TryGetValue(baseCurrency, out max);
            var orderIsWithinForexSizeLimits = quantity < max;

            if (!orderIsWithinForexSizeLimits)
            {
                message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSizeLimit",
                                                    string.Format("The maximum allowable order size is {0}{1}.", max, baseCurrency)
                                                    );
            }
            return(orderIsWithinForexSizeLimits);
        }
Exemple #9
0
        /// <summary>
        /// Create a simple JSON holdings from a Security holding class.
        /// </summary>
        /// <param name="holding">Holdings object we'll use to initialize the transport</param>
        public Holding(SecurityHolding holding)
            : this()
        {
            Symbol   = holding.Symbol.Value;
            Type     = holding.Type;
            Quantity = holding.Quantity;

            var rounding = 2;

            if (holding.Type == SecurityType.Forex)
            {
                rounding = 5;
                string basec, quotec;
                Forex.DecomposeCurrencyPair(holding.Symbol.Value, out basec, out quotec);
                CurrencySymbol = Forex.CurrencySymbols[quotec];
                ConversionRate = ((ForexHolding)holding).ConversionRate;
            }

            AveragePrice = Math.Round(holding.AveragePrice, rounding);
            MarketPrice  = Math.Round(holding.Price, rounding);
        }
Exemple #10
0
        /// <summary>
        /// Decomposes the specified currency pair into a base and quote currency provided as out parameters
        /// </summary>
        /// <param name="currencyPair">The input currency pair to be decomposed</param>
        /// <param name="baseCurrency">The output base currency</param>
        /// <param name="quoteCurrency">The output quote currency</param>
        public static void DecomposeCurrencyPair(Symbol currencyPair, out string baseCurrency, out string quoteCurrency)
        {
            if (currencyPair == null)
            {
                throw new ArgumentException("Currency pair must not be null");
            }

            var securityType = currencyPair.SecurityType;

            if (securityType != SecurityType.Forex &&
                securityType != SecurityType.Cfd &&
                securityType != SecurityType.Crypto)
            {
                throw new ArgumentException($"Unsupported security type: {securityType}");
            }

            if (securityType == SecurityType.Forex)
            {
                Forex.DecomposeCurrencyPair(currencyPair.Value, out baseCurrency, out quoteCurrency);
                return;
            }

            var symbolProperties = SymbolPropertiesDatabase.Value.GetSymbolProperties(
                currencyPair.ID.Market,
                currencyPair,
                currencyPair.SecurityType,
                Currencies.USD);

            if (securityType == SecurityType.Cfd)
            {
                Cfd.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
            }
            else
            {
                Crypto.DecomposeCurrencyPair(currencyPair, symbolProperties, out baseCurrency, out quoteCurrency);
            }
        }
Exemple #11
0
        private IEnumerable <Tick> GetForexQuoteTicks(HistoryRequest request)
        {
            // https://api.polygon.io/v1/historic/forex/EUR/USD/2020-08-24?apiKey=

            var start       = request.StartTimeUtc;
            var end         = request.EndTimeUtc;
            var currentDate = start.Date;

            while (currentDate <= end.Date)
            {
                Log.Trace($"GetForexQuoteTicks(): Downloading ticks for the date {currentDate:yyyy-MM-dd}; symbol: {request.Symbol.ID.Symbol}");

                // If this is a very first iteration set offset exactly as request's start time.
                // Otherwise use date start as an offset. (!) Make sure to cast to Int64.
                var offset = currentDate == start.Date ? (long)Time.DateTimeToUnixTimeStampMilliseconds(start)
                    : (long)Time.DateTimeToUnixTimeStampMilliseconds(currentDate);

                var  counter           = 0;
                long lastTickTimestamp = 0;

                while (true)
                {
                    counter++;

                    string baseCurrency;
                    string quoteCurrency;
                    Forex.DecomposeCurrencyPair(request.Symbol.Value, out baseCurrency, out quoteCurrency);

                    var url = $"{HistoryBaseUrl}/v1/historic/forex/{baseCurrency}/{quoteCurrency}/{currentDate:yyyy-MM-dd}?" +
                              $"limit={ResponseSizeLimitCurrencies}&apiKey={_apiKey}&offset={offset}";

                    var response = DownloadAndParseData(typeof(ForexQuoteTickResponse[]), url, "ticks") as ForexQuoteTickResponse[];

                    // The first results of the next page will coincide with last of the previous page, lets clear from repeating values
                    var quoteTicksList = response?.Where(x => x.Timestamp != lastTickTimestamp).ToList();
                    if (quoteTicksList.IsNullOrEmpty())
                    {
                        break;
                    }

                    Log.Trace($"GetForexQuoteTicks(): Page # {counter}; " +
                              $"first: {Time.UnixMillisecondTimeStampToDateTime(quoteTicksList.First().Timestamp)}; " +
                              $"last: {Time.UnixMillisecondTimeStampToDateTime(quoteTicksList.Last().Timestamp)}");

                    foreach (var row in quoteTicksList)
                    {
                        var utcTime = Time.UnixMillisecondTimeStampToDateTime(row.Timestamp);

                        if (utcTime < start)
                        {
                            continue;
                        }

                        if (utcTime > end)
                        {
                            yield break;
                        }

                        var time = GetTickTime(request.Symbol, utcTime);
                        yield return(new Tick(time, request.Symbol, row.Bid, row.Ask));

                        lastTickTimestamp = row.Timestamp;
                    }

                    offset           = lastTickTimestamp;
                    _dataPointCount += quoteTicksList.Count;
                }

                // Jump to the next iteration
                currentDate = currentDate.AddDays(1);
            }
        }
Exemple #12
0
        /// <summary>
        /// Perform preorder checks to ensure we have sufficient capital,
        /// the market is open, and we haven't exceeded maximum realistic orders per day.
        /// </summary>
        /// <returns>OrderResponse. If no error, order request is submitted.</returns>
        private OrderResponse PreOrderChecksImpl(SubmitOrderRequest request)
        {
            //Ordering 0 is useless.
            if (request.Quantity == 0 || request.Symbol == null || request.Symbol == Symbol.Empty)
            {
                return(OrderResponse.ZeroQuantity(request));
            }

            //If we're not tracking this symbol: throw error:
            if (!Securities.ContainsKey(request.Symbol) && !_sentNoDataError)
            {
                _sentNoDataError = true;
                return(OrderResponse.Error(request, OrderResponseErrorCode.MissingSecurity, "You haven't requested " + request.Symbol.SID + " data. Add this with AddSecurity() in the Initialize() Method."));
            }

            //Set a temporary price for validating order for market orders:
            var security = Securities[request.Symbol];
            var price    = security.Price;

            //Check the exchange is open before sending a market on close orders
            //Allow market orders, they'll just execute when the exchange reopens
            if (request.OrderType == OrderType.MarketOnClose && !security.Exchange.ExchangeOpen)
            {
                return(OrderResponse.Error(request, OrderResponseErrorCode.ExchangeNotOpen, request.OrderType + " order and exchange not open."));
            }

            if (price == 0)
            {
                return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityPriceZero, request.Symbol.SID + ": asset price is $0. If using custom data make sure you've set the 'Value' property."));
            }

            if (security.Type == SecurityType.Forex)
            {
                // for forex pairs we need to verify that the conversions to USD have values as well
                string baseCurrency, quoteCurrency;
                Forex.DecomposeCurrencyPair(security.Symbol.Value, out baseCurrency, out quoteCurrency);

                // verify they're in the portfolio
                Cash baseCash, quoteCash;
                if (!Portfolio.CashBook.TryGetValue(baseCurrency, out baseCash) || !Portfolio.CashBook.TryGetValue(quoteCurrency, out quoteCash))
                {
                    return(OrderResponse.Error(request, OrderResponseErrorCode.ForexBaseAndQuoteCurrenciesRequired, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " in the cashbook to trade."));
                }
                // verify we have conversion rates for each leg of the pair back into the account currency
                if (baseCash.ConversionRate == 0m || quoteCash.ConversionRate == 0m)
                {
                    return(OrderResponse.Error(request, OrderResponseErrorCode.ForexConversionRateZero, request.Symbol.Value + ": requires " + baseCurrency + " and " + quoteCurrency + " to have non-zero conversion rates. This can be caused by lack of data."));
                }
            }

            //Make sure the security has some data:
            if (!security.HasData)
            {
                return(OrderResponse.Error(request, OrderResponseErrorCode.SecurityHasNoData, "There is no data for this symbol yet, please check the security.HasData flag to ensure there is at least one data point."));
            }

            //We've already processed too many orders: max 100 per day or the memory usage explodes
            if (Transactions.OrdersCount > _maxOrders)
            {
                _quit = true;
                return(OrderResponse.Error(request, OrderResponseErrorCode.ExceededMaximumOrders, string.Format("You have exceeded maximum number of orders ({0}), for unlimited orders upgrade your account.", _maxOrders)));
            }

            if (request.OrderType == OrderType.MarketOnClose)
            {
                // must be submitted with at least 10 minutes in trading day, add buffer allow order submission
                var latestSubmissionTime = (Time.Date + security.Exchange.MarketClose).AddMinutes(-10.75);
                if (Time > latestSubmissionTime)
                {
                    // tell the user we require an 11 minute buffer, on minute data in live a user will receive the 3:49->3:50 bar at 3:50,
                    // this is already too late to submit one of these orders, so make the user do it at the 3:48->3:49 bar so it's submitted
                    // to the brokerage before 3:50.
                    return(OrderResponse.Error(request, OrderResponseErrorCode.MarketOnCloseOrderTooLate, "MarketOnClose orders must be placed with at least a 11 minute buffer before market close."));
                }
            }

            // passes all initial order checks
            return(OrderResponse.Success(request));
        }
        private void HandleOrderEvent(OrderEvent fill)
        {
            // update the order status
            var order = GetOrderByIdInternal(fill.OrderId);

            if (order == null)
            {
                Log.Error("BrokerageTransactionHandler.HandleOrderEvent(): Unable to locate Order with id " + fill.OrderId);
                return;
            }

            // set the status of our order object based on the fill event
            order.Status = fill.Status;

            // save that the order event took place, we're initializing the list with a capacity of 2 to reduce number of mallocs
            //these hog memory
            //List<OrderEvent> orderEvents = _orderEvents.GetOrAdd(orderEvent.OrderId, i => new List<OrderEvent>(2));
            //orderEvents.Add(orderEvent);

            //Apply the filled order to our portfolio:
            if (fill.Status == OrderStatus.Filled || fill.Status == OrderStatus.PartiallyFilled)
            {
                Log.Debug("BrokerageTransactionHandler.HandleOrderEvent(): " + fill);
                Interlocked.Exchange(ref _lastFillTimeTicks, DateTime.Now.Ticks);
                try
                {
                    _algorithm.Portfolio.ProcessFill(fill);

                    var conversionRate = 1m;
                    if (order.SecurityType == SecurityType.Forex)
                    {
                        string baseCurrency, quoteCurrency;
                        Forex.DecomposeCurrencyPair(fill.Symbol, out baseCurrency, out quoteCurrency);
                        conversionRate = _algorithm.Portfolio.CashBook[quoteCurrency].ConversionRate;
                    }

                    _algorithm.TradeBuilder.ProcessFill(fill, conversionRate);
                }
                catch (Exception err)
                {
                    Log.Error(err);
                    _algorithm.Error(string.Format("Order Error: id: {0}, Error in Portfolio.ProcessFill: {1}", order.Id, err.Message));
                }
            }

            // update the ticket and order after we've processed the fill, but before the event, this way everything is ready for user code
            OrderTicket ticket;

            if (_orderTickets.TryGetValue(fill.OrderId, out ticket))
            {
                ticket.AddOrderEvent(fill);
                order.Price = ticket.AverageFillPrice;
            }
            else
            {
                Log.Error("BrokerageTransactionHandler.HandleOrderEvent(): Unable to resolve ticket: " + fill.OrderId);
            }

            //We have an event! :) Order filled, send it in to be handled by algorithm portfolio.
            if (fill.Status != OrderStatus.None) //order.Status != OrderStatus.Submitted
            {
                //Create new order event:
                _resultHandler.OrderEvent(fill);
                try
                {
                    //Trigger our order event handler
                    _algorithm.OnOrderEvent(fill);
                }
                catch (Exception err)
                {
                    _algorithm.Error("Order Event Handler Error: " + err.Message);
                    // kill the algorithm
                    _algorithm.RunTimeError = err;
                }
            }
        }