Esempio n. 1
0
        /// <summary>
        /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        /// </summary>
        /// <param name="data">Slice object keyed by symbol containing the stock data</param>
        public override void OnData(Slice data)
        {
            if (!Portfolio.Invested)
            {
                CurrencyPairUtil.DecomposeCurrencyPair(_symbol, out var baseCurrency, out var quoteCurrency);

                var initialQuoteCurrency = Portfolio.CashBook[quoteCurrency].Amount;
                var ticket      = Buy(_symbol, 0.1m);
                var filledEvent = ticket.OrderEvents.Single(orderEvent => orderEvent.Status == OrderStatus.Filled);

                if (Portfolio.CashBook[baseCurrency].Amount != ticket.QuantityFilled ||
                    filledEvent.FillQuantity != ticket.QuantityFilled ||
                    (0.1m - filledEvent.OrderFee.Value.Amount) != ticket.QuantityFilled)
                {
                    throw new Exception($"Unexpected BaseCurrency porfoltio status. Event {filledEvent}. CashBook: {Portfolio.CashBook}. ");
                }

                if (Portfolio.CashBook[quoteCurrency].Amount != (initialQuoteCurrency - 0.1m * filledEvent.FillPrice))
                {
                    throw new Exception($"Unexpected QuoteCurrency porfoltio status. Event {filledEvent}. CashBook: {Portfolio.CashBook}. ");
                }

                if (Securities[_symbol].Holdings.Quantity != (0.1m - filledEvent.OrderFee.Value.Amount))
                {
                    throw new Exception($"Unexpected Holdings: {Securities[_symbol].Holdings}. Event {filledEvent}");
                }
            }
            else
            {
                Liquidate();
            }
        }
Esempio n. 2
0
        public void CurrencyPairDualReturnsNullOnWrongKnownSymbol()
        {
            var currencyPair = Symbol.Create("ETHBTC", SecurityType.Crypto, Market.Bitfinex);

            Assert.AreEqual(null, currencyPair.CurrencyPairDual("ZRX"));
            Assert.AreEqual(null, CurrencyPairUtil.CurrencyPairDual("ETH", "BTC", "ZRX"));
        }
Esempio n. 3
0
        public void DecomposeThrowsOnNonCurrencyPair(Symbol symbol)
        {
            string baseCurrency, quoteCurrency;

            Assert.Throws <ArgumentException>(
                () => CurrencyPairUtil.DecomposeCurrencyPair(symbol, out baseCurrency, out quoteCurrency));
        }
Esempio n. 4
0
        /// <summary>
        /// Gets the total account cash balance for specified account type
        /// </summary>
        /// <returns></returns>
        public override List <CashAmount> GetCashBalance()
        {
            var endpoint = GetEndpoint("auth/r/wallets");
            var request  = new RestRequest(endpoint, Method.POST);

            var parameters = new JsonObject();

            request.AddJsonBody(parameters.ToString());
            SignRequest(request, endpoint, parameters);

            var response = ExecuteRestRequest(request);

            if (response.StatusCode != HttpStatusCode.OK)
            {
                throw new Exception($"BitfinexBrokerage.GetCashBalance: request failed: " +
                                    $"[{(int)response.StatusCode}] {response.StatusDescription}, " +
                                    $"Content: {response.Content}, ErrorMessage: {response.ErrorMessage}");
            }

            var availableWallets = JsonConvert.DeserializeObject <Wallet[]>(response.Content)
                                   .Where(WalletFilter(_algorithm.BrokerageModel.AccountType));

            var list = new List <CashAmount>();

            foreach (var item in availableWallets)
            {
                if (item.Balance > 0)
                {
                    list.Add(new CashAmount(item.Balance, GetLeanCurrency(item.Currency)));
                }
            }

            var balances = list.ToDictionary(x => x.Currency);

            if (_algorithm.BrokerageModel.AccountType == AccountType.Margin)
            {
                // include cash balances from currency swaps for open Crypto positions
                foreach (var holding in GetAccountHoldings().Where(x => x.Symbol.SecurityType == SecurityType.Crypto))
                {
                    var defaultQuoteCurrency = _algorithm.Portfolio.CashBook.AccountCurrency;
                    CurrencyPairUtil.DecomposeCurrencyPair(holding.Symbol, out var baseCurrency, out var quoteCurrency, defaultQuoteCurrency);

                    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());
        }
Esempio n. 5
0
        public void CurrencyPairDualCrypto()
        {
            var currencyPair = Symbol.Create("ETHBTC", SecurityType.Crypto, Market.Bitfinex);

            Assert.AreEqual("BTC", currencyPair.CurrencyPairDual("ETH"));
            Assert.AreEqual("ETH", currencyPair.CurrencyPairDual("BTC"));
            Assert.AreEqual("BTC", CurrencyPairUtil.CurrencyPairDual("ETH", "BTC", "ETH"));
            Assert.AreEqual("ETH", CurrencyPairUtil.CurrencyPairDual("ETH", "BTC", "BTC"));
        }
Esempio n. 6
0
        public void CurrencyPairDualCfd()
        {
            var currencyPair = Symbol.Create("XAGUSD", SecurityType.Cfd, Market.Oanda);

            Assert.AreEqual("XAG", currencyPair.CurrencyPairDual("USD"));
            Assert.AreEqual("USD", currencyPair.CurrencyPairDual("XAG"));
            Assert.AreEqual("XAG", CurrencyPairUtil.CurrencyPairDual("XAG", "USD", "USD"));
            Assert.AreEqual("USD", CurrencyPairUtil.CurrencyPairDual("XAG", "USD", "XAG"));
        }
Esempio n. 7
0
        public void CurrencyPairDualForex()
        {
            var currencyPair = Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM);

            Assert.AreEqual("USD", currencyPair.CurrencyPairDual("EUR"));
            Assert.AreEqual("EUR", currencyPair.CurrencyPairDual("USD"));
            Assert.AreEqual("USD", CurrencyPairUtil.CurrencyPairDual("EUR", "USD", "EUR"));
            Assert.AreEqual("EUR", CurrencyPairUtil.CurrencyPairDual("EUR", "USD", "USD"));
        }
Esempio n. 8
0
        public void DecomposeDecomposesAllCurrencyPairTypes(
            Symbol symbol,
            string expectedBaseCurrency,
            string expectedQuoteCurrency)
        {
            string actualBaseCurrency;
            string actualQuoteCurrency;

            CurrencyPairUtil.DecomposeCurrencyPair(symbol, out actualBaseCurrency, out actualQuoteCurrency);

            Assert.AreEqual(expectedBaseCurrency, actualBaseCurrency);
            Assert.AreEqual(expectedQuoteCurrency, actualQuoteCurrency);
        }
Esempio n. 9
0
        /// <summary>
        /// Get the fee for this order in quote currency
        /// </summary>
        /// <param name="parameters">A <see cref="OrderFeeParameters"/> object
        /// containing the security and order</param>
        /// <returns>The cost of the order in quote currency</returns>
        public override OrderFee GetOrderFee(OrderFeeParameters parameters)
        {
            var order    = parameters.Order;
            var security = parameters.Security;
            // apply fee factor, currently we do not model 30-day volume, so we use the first tier
            var fee   = TakerFee;
            var props = order.Properties as BitfinexOrderProperties;

            if (order.Type == OrderType.Limit &&
                props?.Hidden != true &&
                (props?.PostOnly == true || !order.IsMarketable))
            {
                // limit order posted to the order book
                fee = MakerFee;
            }

            if (order.Direction == OrderDirection.Buy)
            {
                // fees taken in the received currency
                CurrencyPairUtil.DecomposeCurrencyPair(order.Symbol, out var baseCurrency, out _);
                return(new OrderFee(new CashAmount(order.AbsoluteQuantity * fee, baseCurrency)));
            }

            // get order value in quote currency
            var unitPrice = order.Direction == OrderDirection.Buy ? security.AskPrice : security.BidPrice;

            if (order.Type == OrderType.Limit)
            {
                // limit order posted to the order book
                unitPrice = ((LimitOrder)order).LimitPrice;
            }

            unitPrice *= security.SymbolProperties.ContractMultiplier;

            return(new OrderFee(new CashAmount(
                                    unitPrice * order.AbsoluteQuantity * fee,
                                    security.QuoteCurrency.Symbol)));
        }
Esempio n. 10
0
        /// <summary>
        /// Runs this instance.
        /// </summary>
        /// <returns></returns>
        public bool Run()
        {
            var stopwatch = Stopwatch.StartNew();

            var symbolMapper = new CoinApiSymbolMapper();
            var success      = true;

            // There were cases of files with with an extra suffix, following pattern:
            // <TickType>-<ID>-<Exchange>_SPOT_<BaseCurrency>_<QuoteCurrency>_<ExtraSuffix>.csv.gz
            // Those cases should be ignored for SPOT prices.
            var tradesFolder = new DirectoryInfo(
                Path.Combine(
                    _rawDataFolder.FullName,
                    "trades",
                    _processingDate.ToStringInvariant(DateFormat.EightCharacter)));

            var quotesFolder = new DirectoryInfo(
                Path.Combine(
                    _rawDataFolder.FullName,
                    "quotes",
                    _processingDate.ToStringInvariant(DateFormat.EightCharacter)));

            var rawMarket = _market != null &&
                            CoinApiSymbolMapper.MapMarketsToExchangeIds.TryGetValue(_market, out var rawMarketValue)
                    ? rawMarketValue
                    : null;

            // Distinct by tick type and first two parts of the raw file name, separated by '-'.
            // This prevents us from double processing the same ticker twice, in case we're given
            // two raw data files for the same symbol. Related: https://github.com/QuantConnect/Lean/pull/3262
            var apiDataReader            = new CoinApiDataReader(symbolMapper);
            var filesToProcessCandidates = tradesFolder.EnumerateFiles("*.gz")
                                           .Concat(quotesFolder.EnumerateFiles("*.gz"))
                                           .Where(f => f.Name.Contains("SPOT") && (rawMarket == null || f.Name.Contains(rawMarket)))
                                           .Where(f => f.Name.Split('_').Length == 4)
                                           .ToList();

            var filesToProcessKeys = new HashSet <string>();
            var filesToProcess     = new List <FileInfo>();

            foreach (var candidate in filesToProcessCandidates)
            {
                try
                {
                    var entryData = apiDataReader.GetCoinApiEntryData(candidate, _processingDate);
                    CurrencyPairUtil.DecomposeCurrencyPair(entryData.Symbol, out var baseCurrency,
                                                           out var quoteCurrency);

                    if (!candidate.FullName.Contains(baseCurrency) && !candidate.FullName.Contains(quoteCurrency))
                    {
                        throw new Exception($"Skipping {candidate.FullName} we have the wrong symbol {entryData.Symbol}!");
                    }

                    var key = candidate.Directory.Parent.Name + entryData.Symbol.ID;
                    if (filesToProcessKeys.Add(key))
                    {
                        // Separate list from HashSet to preserve ordering of viable candidates
                        filesToProcess.Add(candidate);
                    }
                }
                catch (Exception err)
                {
                    // Most likely the exchange isn't supported. Log exception message to avoid excessive stack trace spamming in console output
                    Log.Error(err.Message);
                }
            }

            Parallel.ForEach(filesToProcess, (file, loopState) =>
            {
                Log.Trace($"CoinApiDataConverter(): Starting data conversion from source file: {file.Name}...");
                try
                {
                    ProcessEntry(apiDataReader, file);
                }
                catch (Exception e)
                {
                    Log.Error(e, $"CoinApiDataConverter(): Error processing entry: {file.Name}");
                    success = false;
                    loopState.Break();
                }
            }
                             );

            Log.Trace($"CoinApiDataConverter(): Finished in {stopwatch.Elapsed}");
            return(success);
        }
Esempio n. 11
0
        private void EmitFillOrder(TradeExecutionUpdate update)
        {
            try
            {
                var brokerId = update.OrderId.ToStringInvariant();

                var order = CachedOrderIDs
                            .FirstOrDefault(o => o.Value.BrokerId.Contains(brokerId))
                            .Value;

                if (order == null)
                {
                    order = _algorithm.Transactions.GetOrderByBrokerageId(brokerId);
                    if (order == null)
                    {
                        Log.Error($"BitfinexBrokerage.EmitFillOrder(): order not found: BrokerId: {brokerId}");
                        return;
                    }
                }

                var symbol       = _symbolMapper.GetLeanSymbol(update.Symbol, SecurityType.Crypto, Market.Bitfinex);
                var fillPrice    = update.ExecPrice;
                var fillQuantity = update.ExecAmount;
                var direction    = fillQuantity < 0 ? OrderDirection.Sell : OrderDirection.Buy;
                var updTime      = Time.UnixMillisecondTimeStampToDateTime(update.MtsCreate);
                var orderFee     = new OrderFee(new CashAmount(Math.Abs(update.Fee), GetLeanCurrency(update.FeeCurrency)));

                var status = OrderStatus.Filled;
                if (fillQuantity != order.Quantity)
                {
                    decimal totalFillQuantity;
                    _fills.TryGetValue(order.Id, out totalFillQuantity);
                    totalFillQuantity += fillQuantity;
                    _fills[order.Id]   = totalFillQuantity;

                    status = totalFillQuantity == order.Quantity
                        ? OrderStatus.Filled
                        : OrderStatus.PartiallyFilled;
                }

                if (_algorithm.BrokerageModel.AccountType == AccountType.Cash && order.Direction == OrderDirection.Buy)
                {
                    CurrencyPairUtil.DecomposeCurrencyPair(symbol, out var baseCurrency, out _);
                    if (orderFee.Value.Currency != baseCurrency)
                    {
                        OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, "UnexpectedFeeCurrency", $"Unexpected fee currency {orderFee.Value.Currency} for symbol {symbol}. OrderId {order.Id}. BrokerageOrderId {brokerId}. " +
                                                            "This error can happen because your account is Margin type and Lean is configured to be Cash type or while using Cash type the Bitfinex account fee settings are set to 'Asset Trading Fee' and should be set to 'Currency Exchange Fee'."));
                    }
                }

                var orderEvent = new OrderEvent
                                 (
                    order.Id, symbol, updTime, status,
                    direction, fillPrice, fillQuantity,
                    orderFee, $"Bitfinex Order Event {direction}"
                                 );

                // if the order is closed, we no longer need it in the active order list
                if (status == OrderStatus.Filled)
                {
                    Order outOrder;
                    CachedOrderIDs.TryRemove(order.Id, out outOrder);

                    decimal ignored;
                    _fills.TryRemove(order.Id, out ignored);

                    var clientOrderId = _orderMap.FirstOrDefault(x => x.Value.BrokerId.Contains(brokerId)).Key;
                    if (clientOrderId > 0)
                    {
                        _orderMap.TryRemove(clientOrderId, out outOrder);
                    }
                }

                OnOrderEvent(orderEvent);
            }
            catch (Exception e)
            {
                Log.Error(e);
                throw;
            }
        }
Esempio n. 12
0
 public void IsDecomposableWorksCorrectly(Symbol symbol, bool expectedResult)
 {
     Assert.AreEqual(expectedResult, CurrencyPairUtil.IsDecomposable(symbol));
 }
        /// <summary>
        /// Finds a conversion between two currencies by looking through all available 1 and 2-leg options
        /// </summary>
        /// <param name="sourceCurrency">The currency to convert from</param>
        /// <param name="destinationCurrency">The currency to convert to</param>
        /// <param name="existingSecurities">The securities which are already added to the algorithm</param>
        /// <param name="potentialSymbols">The symbols to consider, may overlap with existingSecurities</param>
        /// <param name="makeNewSecurity">The function to call when a symbol becomes part of the conversion, must return the security that will provide price data about the symbol</param>
        /// <returns>A new <see cref="SecurityCurrencyConversion"/> instance representing the conversion from sourceCurrency to destinationCurrency</returns>
        /// <exception cref="ArgumentException">Thrown when no conversion from sourceCurrency to destinationCurrency can be found</exception>
        public static SecurityCurrencyConversion LinearSearch(
            string sourceCurrency,
            string destinationCurrency,
            IList <Security> existingSecurities,
            IEnumerable <Symbol> potentialSymbols,
            Func <Symbol, Security> makeNewSecurity)
        {
            var allSymbols = existingSecurities.Select(sec => sec.Symbol).Concat(potentialSymbols)
                             .Where(CurrencyPairUtil.IsDecomposable)
                             .ToList();

            var securitiesBySymbol = existingSecurities.Aggregate(new Dictionary <Symbol, Security>(),
                                                                  (mapping, security) =>
            {
                if (!mapping.ContainsKey(security.Symbol))
                {
                    mapping[security.Symbol] = security;
                }

                return(mapping);
            });

            // Search for 1 leg conversions
            foreach (var potentialConversionRateSymbol in allSymbols)
            {
                var leg1Match = potentialConversionRateSymbol.ComparePair(sourceCurrency, destinationCurrency);
                if (leg1Match == CurrencyPairUtil.Match.NoMatch)
                {
                    continue;
                }
                var inverted = leg1Match == CurrencyPairUtil.Match.InverseMatch;

                return(new SecurityCurrencyConversion(sourceCurrency, destinationCurrency, new List <Step>(1)
                {
                    CreateStep(potentialConversionRateSymbol, inverted, securitiesBySymbol, makeNewSecurity)
                }));
            }

            // Search for 2 leg conversions
            foreach (var potentialConversionRateSymbol1 in allSymbols)
            {
                var middleCurrency = potentialConversionRateSymbol1.CurrencyPairDual(sourceCurrency);
                if (middleCurrency == null)
                {
                    continue;
                }

                foreach (var potentialConversionRateSymbol2 in allSymbols)
                {
                    var leg2Match = potentialConversionRateSymbol2.ComparePair(middleCurrency, destinationCurrency);
                    if (leg2Match == CurrencyPairUtil.Match.NoMatch)
                    {
                        continue;
                    }
                    var secondStepInverted = leg2Match == CurrencyPairUtil.Match.InverseMatch;

                    var steps = new List <Step>(2);

                    // Step 1
                    string baseCurrency;
                    string quoteCurrency;

                    CurrencyPairUtil.DecomposeCurrencyPair(
                        potentialConversionRateSymbol1,
                        out baseCurrency,
                        out quoteCurrency);

                    steps.Add(CreateStep(potentialConversionRateSymbol1,
                                         sourceCurrency == quoteCurrency,
                                         securitiesBySymbol,
                                         makeNewSecurity));

                    // Step 2
                    steps.Add(CreateStep(potentialConversionRateSymbol2,
                                         secondStepInverted,
                                         securitiesBySymbol,
                                         makeNewSecurity));

                    return(new SecurityCurrencyConversion(sourceCurrency, destinationCurrency, steps));
                }
            }

            throw new ArgumentException(
                      $"No conversion path found between source currency {sourceCurrency} and destination currency {destinationCurrency}");
        }