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