public bool CanBuy(BuyOptions options, out string message) { IPairConfig pairConfig = GetPairConfig(options.Pair); if (!options.ManualOrder && !options.Swap && IsTradingSuspended) { message = $"Cancel buy request for {options.Pair}. Reason: trading suspended"; return(false); } else if (!options.ManualOrder && !options.Swap && !pairConfig.BuyEnabled) { message = $"Cancel buy request for {options.Pair}. Reason: buying not enabled"; return(false); } else if (!options.ManualOrder && Config.ExcludedPairs.Contains(options.Pair)) { message = $"Cancel buy request for {options.Pair}. Reason: exluded pair"; return(false); } else if (!options.ManualOrder && !options.IgnoreExisting && Account.HasTradingPair(options.Pair)) { message = $"Cancel buy request for {options.Pair}. Reason: pair already exists"; return(false); } else if (!options.ManualOrder && !options.Swap && Config.MaxPairs != 0 && Account.GetTradingPairs().Count() >= Config.MaxPairs && !Account.HasTradingPair(options.Pair)) { message = $"Cancel buy request for {options.Pair}. Reason: maximum pairs reached"; return(false); } else if (!options.ManualOrder && !options.Swap && pairConfig.BuyMinBalance != 0 && (Account.GetBalance() - options.MaxCost) < pairConfig.BuyMinBalance) { message = $"Cancel buy request for {options.Pair}. Reason: minimum balance reached"; return(false); } else if (GetCurrentPrice(options.Pair) <= 0) { message = $"Cancel buy request for {options.Pair}. Reason: invalid price"; return(false); } else if (Account.GetBalance() < options.MaxCost) { message = $"Cancel buy request for {options.Pair}. Reason: not enough balance"; return(false); } else if (options.Amount == null && options.MaxCost == null || options.Amount != null && options.MaxCost != null) { message = $"Cancel buy request for {options.Pair}. Reason: either max cost or amount needs to be specified (not both)"; } else if (!options.ManualOrder && !options.Swap && pairConfig.BuySamePairTimeout > 0 && OrderHistory.Any(h => h.Side == OrderSide.Buy && h.Pair == options.Pair) && (DateTimeOffset.Now - OrderHistory.Where(h => h.Pair == options.Pair).Max(h => h.Date)).TotalSeconds < pairConfig.BuySamePairTimeout) { var elapsedSeconds = (DateTimeOffset.Now - OrderHistory.Where(h => h.Pair == options.Pair).Max(h => h.Date)).TotalSeconds; message = $"Cancel buy request for {options.Pair}. Reason: buy same pair timeout (elapsed: {elapsedSeconds:0.#}, timeout: {pairConfig.BuySamePairTimeout:0.#})"; return(false); } message = null; return(true); }
public void Buy(BuyOptions options) { lock (syncRoot) { PauseTasks(); try { IRule rule = signalsService.Rules.Entries.FirstOrDefault(r => r.Name == options.Metadata.SignalRule); RuleAction ruleAction = rule?.Action ?? RuleAction.Default; IPairConfig pairConfig = GetPairConfig(options.Pair); bool arbitragePair = pairConfig.ArbitrageEnabled && pairConfig.ArbitrageSignalRules.Contains(options.Metadata.SignalRule); if (arbitragePair) { Arbitrage arbitrage = Exchange.GetArbitrage(options.Pair, Config.Market, pairConfig.ArbitrageMarkets, pairConfig.ArbitrageType); if (arbitrage.IsAssigned) { Arbitrage(new ArbitrageOptions(options.Pair, arbitrage, options.Metadata)); } } else { ITradingPair swappedPair = Account.GetTradingPairs().OrderBy(p => p.CurrentMargin).FirstOrDefault(tradingPair => { IPairConfig tradingPairConfig = GetPairConfig(tradingPair.Pair); return(tradingPairConfig.SellEnabled && tradingPairConfig.SwapEnabled && tradingPairConfig.SwapSignalRules != null && tradingPairConfig.SwapSignalRules.Contains(options.Metadata.SignalRule) && tradingPairConfig.SwapTimeout < (DateTimeOffset.Now - tradingPair.OrderDates.DefaultIfEmpty().Max()).TotalSeconds); }); if (swappedPair != null) { Swap(new SwapOptions(swappedPair.Pair, options.Pair, options.Metadata)); } else if (ruleAction == RuleAction.Default) { if (CanBuy(options, out string message)) { tradingTimedTask.InitiateBuy(options); } else { loggingService.Debug(message); } } } } finally { ContinueTasks(); } } }
public bool CanSell(SellOptions options, out string message) { IPairConfig pairConfig = GetPairConfig(options.Pair); if (!options.ManualOrder && !options.Arbitrage && IsTradingSuspended) { message = $"Cancel sell request for {options.Pair}. Reason: trading suspended"; return(false); } else if (!options.ManualOrder && !options.Arbitrage && !pairConfig.SellEnabled) { message = $"Cancel sell request for {options.Pair}. Reason: selling not enabled"; return(false); } else if (!options.ManualOrder && !options.Arbitrage && Config.ExcludedPairs.Contains(options.Pair)) { message = $"Cancel sell request for {options.Pair}. Reason: excluded pair"; return(false); } else if (!Account.HasTradingPair(options.Pair, includeDust: true) && !Account.HasTradingPair(NormalizePair(options.Pair), includeDust: true)) { message = $"Cancel sell request for {options.Pair}. Reason: pair does not exist"; return(false); } else if (options.Price != null && options.Price <= 0) { message = $"Cancel sell request for {options.Pair}. Reason: invalid price"; return(false); } else if (options.Amount != null && options.Amount <= 0) { message = $"Cancel sell request for {options.Pair}. Reason: invalid amount"; return(false); } else if (options.Amount != null && options.Price != null && (options.Amount * options.Price) < Config.MinCost) { message = $"Cancel sell request for {options.Pair}. Reason: dust"; return(false); } else if (!options.ManualOrder && !options.Arbitrage && (DateTimeOffset.Now - Account.GetTradingPair(options.Pair, includeDust: true).OrderDates.DefaultIfEmpty().Max()). TotalMilliseconds < (MIN_INTERVAL_BETWEEN_BUY_AND_SELL / Application.Speed)) { message = $"Cancel sell request for {options.Pair}. Reason: pair just bought"; return(false); } message = null; return(true); }
public void InitiateBuy(BuyOptions options) { IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair); if (!options.ManualOrder && !options.Swap && pairConfig.BuyTrailing != 0) { if (!trailingBuys.ContainsKey(options.Pair)) { trailingSells.TryRemove(options.Pair, out SellTrailingInfo sellTrailingInfo); ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair); decimal currentPrice = tradingService.GetCurrentPrice(options.Pair); decimal currentMargin = 0; var trailingInfo = new BuyTrailingInfo { BuyOptions = options, Trailing = pairConfig.BuyTrailing, TrailingStopMargin = pairConfig.BuyTrailingStopMargin, TrailingStopAction = pairConfig.BuyTrailingStopAction, InitialPrice = currentPrice, LastTrailingMargin = currentMargin, BestTrailingMargin = currentMargin }; if (trailingBuys.TryAdd(options.Pair, trailingInfo)) { if (LoggingEnabled) { loggingService.Info($"Start trailing buy {tradingPair?.FormattedName ?? options.Pair}. Price: {currentPrice:0.00000000}, Margin: {currentMargin:0.00}"); } } } else { if (LoggingEnabled) { //loggingService.Info($"Cancel trailing buy {tradingPair?.FormattedName ?? pair}. Reason: already trailing"); } } } else { PlaceBuyOrder(options); } }
public void InitiateSell(SellOptions options) { IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair); if (!options.ManualOrder && !options.Swap && pairConfig.SellTrailing != 0) { if (!trailingSells.ContainsKey(options.Pair)) { trailingBuys.TryRemove(options.Pair, out BuyTrailingInfo buyTrailingInfo); ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair); tradingPair.SetCurrentValues(tradingService.GetCurrentPrice(options.Pair), tradingService.GetCurrentSpread(options.Pair)); var trailingInfo = new SellTrailingInfo { SellOptions = options, SellMargin = pairConfig.SellMargin, Trailing = pairConfig.SellTrailing, TrailingStopMargin = pairConfig.SellTrailingStopMargin, TrailingStopAction = pairConfig.SellTrailingStopAction, InitialPrice = tradingPair.CurrentPrice, LastTrailingMargin = tradingPair.CurrentMargin, BestTrailingMargin = tradingPair.CurrentMargin }; if (trailingSells.TryAdd(options.Pair, trailingInfo)) { if (LoggingEnabled) { loggingService.Info($"Start trailing sell {tradingPair.FormattedName}. Price: {tradingPair.CurrentPrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}"); } } } else { if (LoggingEnabled) { //loggingService.Info($"Cancel trailing sell {tradingPair.FormattedName}. Reason: already trailing"); } } } else { PlaceSellOrder(options); } }
public void InitiateSell(SellOptions options) { if (tradingService.Account.HasTradingPair(options.Pair)) { IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair); if (!options.ManualOrder && pairConfig.SellTrailing != 0) { if (!trailingSells.ContainsKey(options.Pair)) { StopTrailingBuy(options.Pair); ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair); tradingPair.SetCurrentValues(tradingService.GetPrice(options.Pair), tradingService.Exchange.GetPriceSpread(options.Pair)); var trailingInfo = new SellTrailingInfo { SellOptions = options, SellMargin = pairConfig.SellMargin, Trailing = pairConfig.SellTrailing, TrailingStopMargin = pairConfig.SellTrailingStopMargin, TrailingStopAction = pairConfig.SellTrailingStopAction, InitialPrice = tradingPair.CurrentPrice, LastTrailingMargin = tradingPair.CurrentMargin, BestTrailingMargin = tradingPair.CurrentMargin }; if (trailingSells.TryAdd(options.Pair, trailingInfo)) { if (LoggingEnabled) { loggingService.Info($"Start trailing sell {tradingPair.FormattedName}. " + $"Price: {tradingPair.CurrentPrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}"); } } } } else { orderingService.PlaceSellOrder(options); } } else { loggingService.Info($"Cancel initiate sell for {options.Pair}. Reason: pair does not exist"); } }
public void InitiateBuy(BuyOptions options) { IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair); if (!options.ManualOrder && pairConfig.BuyTrailing != 0) { if (!trailingBuys.ContainsKey(options.Pair)) { StopTrailingSell(options.Pair); decimal currentPrice = tradingService.GetPrice(options.Pair); decimal currentMargin = 0; var trailingInfo = new BuyTrailingInfo { BuyOptions = options, Trailing = pairConfig.BuyTrailing, TrailingStopMargin = pairConfig.BuyTrailingStopMargin, TrailingStopAction = pairConfig.BuyTrailingStopAction, InitialPrice = currentPrice, LastTrailingMargin = currentMargin, BestTrailingMargin = currentMargin }; if (trailingBuys.TryAdd(options.Pair, trailingInfo)) { if (LoggingEnabled) { ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair); loggingService.Info($"Start trailing buy {tradingPair?.FormattedName ?? options.Pair}. " + $"Price: {currentPrice:0.00000000}, Margin: {currentMargin:0.00}"); } } } } else { orderingService.PlaceBuyOrder(options); } }
public IOrderDetails PlaceBuyOrder(BuyOptions options) { OrderDetails orderDetails = new OrderDetails(); tradingService.StopTrailingBuy(options.Pair); tradingService.StopTrailingSell(options.Pair); try { ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair, includeDust: true); options.Price = tradingService.GetPrice(options.Pair, TradePriceType.Ask, normalize: false); options.Amount = options.Amount ?? (options.MaxCost.Value / (options.Pair.EndsWith(Constants.Markets.USDT) ? 1 : options.Price)); options.Price = tradingService.Exchange.ClampOrderPrice(options.Pair, options.Price.Value); options.Amount = tradingService.Exchange.ClampOrderAmount(options.Pair, options.Amount.Value); if (tradingService.CanBuy(options, out string message)) { IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair); BuyOrder buyOrder = new BuyOrder { Type = pairConfig.BuyType, Date = DateTimeOffset.Now, Pair = options.Pair, Price = options.Price.Value, Amount = options.Amount.Value }; lock (tradingService.Account.SyncRoot) { loggingService.Info($"Place buy order for {tradingPair?.FormattedName ?? options.Pair}. " + $"Price: {buyOrder.Price:0.00000000}, Amount: {buyOrder.Amount:0.########}, Signal Rule: " + (options.Metadata.SignalRule ?? "N/A")); if (!tradingService.Config.VirtualTrading) { orderDetails = tradingService.Exchange.PlaceOrder(buyOrder) as OrderDetails; } else { string pairMarket = tradingService.Exchange.GetPairMarket(options.Pair); orderDetails = new OrderDetails { OrderId = DateTime.Now.ToFileTimeUtc().ToString(), Side = OrderSide.Buy, Result = OrderResult.Filled, Date = buyOrder.Date, Pair = buyOrder.Pair, Amount = buyOrder.Amount, AmountFilled = buyOrder.Amount, Price = buyOrder.Price, AveragePrice = buyOrder.Price, Fees = buyOrder.Amount * buyOrder.Price * tradingService.Config.VirtualTradingFees, FeesCurrency = pairMarket }; } NormalizeOrder(orderDetails, TradePriceType.Ask); options.Metadata.TradingRules = pairConfig.Rules.ToList(); options.Metadata.LastBuyMargin = options.Metadata.LastBuyMargin ?? tradingPair?.CurrentMargin ?? null; orderDetails.Metadata = options.Metadata; tradingService.Account.AddBuyOrder(orderDetails); tradingService.Account.Save(); tradingService.LogOrder(orderDetails); decimal fees = tradingService.CalculateOrderFees(orderDetails); tradingPair = tradingService.Account.GetTradingPair(orderDetails.Pair, includeDust: true); loggingService.Info("{@Trade}", orderDetails); loggingService.Info($"Buy order result for {orderDetails.OriginalPair ?? tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). " + $"Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, " + $"Filled: {orderDetails.AmountFilled:0.########}, Cost: {orderDetails.Cost:0.00000000}, Fees: {fees:0.00000000}"); notificationService.Notify($"Bought {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, " + $"Price: {orderDetails.AveragePrice:0.00000000}, Cost: {(orderDetails.Cost + fees):0.00000000}"); } tradingService.ReapplyTradingRules(); } else { loggingService.Info(message); } } catch (Exception ex) { loggingService.Error($"Unable to place buy order for {options.Pair}", ex); notificationService.Notify($"Unable to buy {options.Pair}: {ex.Message}"); } return(orderDetails); }
public IOrderDetails PlaceSellOrder(SellOptions options) { OrderDetails orderDetails = new OrderDetails(); tradingService.StopTrailingSell(options.Pair); tradingService.StopTrailingBuy(options.Pair); try { string normalizedPair = tradingService.NormalizePair(options.Pair); ITradingPair tradingPair = tradingService.Account.GetTradingPair(normalizedPair, includeDust: true); options.Price = tradingService.GetPrice(options.Pair, TradePriceType.Bid); options.Amount = options.Amount ?? tradingPair?.Amount ?? 0; options.Price = options.Price != 1 ? tradingService.Exchange.ClampOrderPrice(options.Pair, options.Price.Value) : 1; // 1 = USDT price options.Amount = tradingService.Exchange.ClampOrderAmount(options.Pair, options.Amount.Value); if (tradingService.CanSell(options, out string message)) { IPairConfig pairConfig = tradingService.GetPairConfig(normalizedPair); SellOrder sellOrder = new SellOrder { Type = pairConfig.SellType, Date = DateTimeOffset.Now, Pair = options.Pair, Price = options.Price.Value, Amount = options.Amount.Value }; lock (tradingService.Account.SyncRoot) { tradingPair.SetCurrentValues(tradingService.GetPrice(normalizedPair), tradingService.Exchange.GetPriceSpread(normalizedPair)); string sellPairName = normalizedPair != options.Pair ? options.Pair : tradingPair.FormattedName; loggingService.Info($"Place sell order for {sellPairName}. " + $"Price: {sellOrder.Price:0.00000000}, Amount: {sellOrder.Amount:0.########}, Margin: {tradingPair.CurrentMargin:0.00}"); if (!tradingService.Config.VirtualTrading) { orderDetails = tradingService.Exchange.PlaceOrder(sellOrder) as OrderDetails; } else { string pairMarket = tradingService.Exchange.GetPairMarket(options.Pair); orderDetails = new OrderDetails { OrderId = DateTime.Now.ToFileTimeUtc().ToString(), Side = OrderSide.Sell, Result = OrderResult.Filled, Date = sellOrder.Date, Pair = sellOrder.Pair, Amount = sellOrder.Amount, AmountFilled = sellOrder.Amount, Price = sellOrder.Price, AveragePrice = sellOrder.Price, Fees = sellOrder.Amount * sellOrder.Price * tradingService.Config.VirtualTradingFees, FeesCurrency = pairMarket }; } NormalizeOrder(orderDetails, TradePriceType.Bid); tradingPair.SetMetadata(tradingPair.Metadata.MergeWith(options.Metadata)); orderDetails.Metadata = tradingPair.Metadata; var tradeResult = tradingService.Account.AddSellOrder(orderDetails) as TradeResult; tradeResult.IsSwap = options.Swap; tradeResult.IsArbitrage = options.Arbitrage; tradingService.Account.Save(); tradingService.LogOrder(orderDetails); decimal fees = tradingService.CalculateOrderFees(orderDetails); decimal margin = (tradeResult.Profit / (tradeResult.Cost + (tradeResult.Metadata.AdditionalCosts ?? 0)) * 100); string swapPair = options.Metadata.SwapPair != null ? $", Swap Pair: {options.Metadata.SwapPair}" : ""; string arbitrage = options.Metadata.Arbitrage != null ? $", Arbitrage: {options.Metadata.Arbitrage} ({options.Metadata.ArbitragePercentage:0.00})" : ""; loggingService.Info("{@Trade}", orderDetails); loggingService.Info("{@Trade}", tradeResult); loggingService.Info($"Sell order result for {orderDetails.OriginalPair ?? tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). " + $"Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Filled: {orderDetails.AmountFilled:0.########}, " + $"Cost: {orderDetails.Cost:0.00000000}, Fees: {fees:0.00000000}, Margin: {margin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}{arbitrage}"); notificationService.Notify($"Sold {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, " + $"Price: {orderDetails.AveragePrice:0.00000000}, Margin: {margin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}{arbitrage}"); } tradingService.ReapplyTradingRules(); } else { loggingService.Info(message); } } catch (Exception ex) { loggingService.Error($"Unable to place sell order for {options.Pair}", ex); notificationService.Notify($"Unable to sell {options.Pair}: {ex.Message}"); } return(orderDetails); }
private PairConfig CreatePairConfig(string pair, ITradingConfig modifiedTradingConfig, IPairConfig modifiedPairConfig, IEnumerable <IRule> appliedRules) { ITradingPair tradingPair = tradingService.Account.GetTradingPair(pair); DCALevel currentDCALevel = GetCurrentDCALevel(tradingPair, modifiedTradingConfig.DCALevels); DCALevel nextDCALevel = GetNextDCALevel(tradingPair, modifiedTradingConfig.DCALevels, modifiedTradingConfig.RepeatLastDCALevel); return(new PairConfig { Rules = appliedRules.Select(r => r.Name), MaxPairs = modifiedTradingConfig.MaxPairs, BuyEnabled = tradingPair == null ? modifiedTradingConfig.BuyEnabled : modifiedTradingConfig.BuyDCAEnabled, BuyType = modifiedTradingConfig.BuyType, BuyMaxCost = modifiedTradingConfig.BuyMaxCost, BuyMultiplier = tradingPair == null ? (modifiedTradingConfig.BuyMultiplier != 0 ? modifiedTradingConfig.BuyMultiplier : 1) : nextDCALevel?.BuyMultiplier ?? modifiedTradingConfig.BuyDCAMultiplier, BuyMinBalance = tradingPair == null ? modifiedTradingConfig.BuyMinBalance : modifiedTradingConfig.BuyDCAMinBalance, BuySamePairTimeout = (tradingPair == null ? modifiedTradingConfig.BuySamePairTimeout : nextDCALevel?.BuySamePairTimeout ?? modifiedTradingConfig.BuyDCASamePairTimeout) / Application.Speed, BuyTrailing = tradingPair == null ? modifiedTradingConfig.BuyTrailing : nextDCALevel?.BuyTrailing ?? modifiedTradingConfig.BuyDCATrailing, BuyTrailingStopMargin = tradingPair == null ? modifiedTradingConfig.BuyTrailingStopMargin : nextDCALevel?.BuyTrailingStopMargin ?? modifiedTradingConfig.BuyDCATrailingStopMargin, BuyTrailingStopAction = tradingPair == null ? modifiedTradingConfig.BuyTrailingStopAction : nextDCALevel?.BuyTrailingStopAction ?? modifiedTradingConfig.BuyDCATrailingStopAction, SellEnabled = modifiedTradingConfig.SellEnabled, SellType = modifiedTradingConfig.SellType, SellMargin = currentDCALevel == null ? modifiedTradingConfig.SellMargin : currentDCALevel?.SellMargin ?? modifiedTradingConfig.SellDCAMargin, SellTrailing = currentDCALevel == null ? modifiedTradingConfig.SellTrailing : currentDCALevel?.SellTrailing ?? modifiedTradingConfig.SellDCATrailing, SellTrailingStopMargin = currentDCALevel == null ? modifiedTradingConfig.SellTrailingStopMargin : currentDCALevel?.SellTrailingStopMargin ?? modifiedTradingConfig.SellDCATrailingStopMargin, SellTrailingStopAction = currentDCALevel == null ? modifiedTradingConfig.SellTrailingStopAction : currentDCALevel?.SellTrailingStopAction ?? modifiedTradingConfig.SellDCATrailingStopAction, SellStopLossEnabled = modifiedTradingConfig.SellStopLossEnabled, SellStopLossAfterDCA = modifiedTradingConfig.SellStopLossAfterDCA, SellStopLossMinAge = modifiedTradingConfig.SellStopLossMinAge / Application.Speed, SellStopLossMargin = modifiedTradingConfig.SellStopLossMargin, SwapEnabled = modifiedPairConfig.SwapEnabled, SwapSignalRules = modifiedPairConfig.SwapSignalRules, SwapTimeout = (int)Math.Round(modifiedPairConfig.SwapTimeout / Application.Speed), ArbitrageEnabled = modifiedPairConfig.ArbitrageEnabled, ArbitrageMarkets = modifiedPairConfig.ArbitrageMarkets, ArbitrageType = modifiedPairConfig.ArbitrageType, ArbitrageBuyMultiplier = modifiedPairConfig.ArbitrageBuyMultiplier, ArbitrageSellMultiplier = modifiedPairConfig.ArbitrageSellMultiplier, ArbitrageSignalRules = modifiedPairConfig.ArbitrageSignalRules, CurrentDCAMargin = currentDCALevel?.Margin, NextDCAMargin = nextDCALevel?.Margin }); }
private void ArbitrageReverse(ArbitrageOptions options) { string marketPair = Exchange.GetArbitrageMarketPair(options.Arbitrage.Market); ITradingPair existingMarketPair = Account.GetTradingPair(marketPair); IPairConfig pairConfig = GetPairConfig(options.Pair); bool useExistingMarketPair = (existingMarketPair != null && existingMarketPair.CurrentCost > pairConfig.BuyMaxCost && existingMarketPair.AveragePrice <= existingMarketPair.CurrentPrice); var buyMarketPairOptions = new BuyOptions(marketPair) { Arbitrage = true, MaxCost = pairConfig.BuyMaxCost, ManualOrder = options.ManualOrder, IgnoreBalance = useExistingMarketPair, Metadata = options.Metadata }; if (CanBuy(buyMarketPairOptions, out string message)) { IOrderDetails buyMarketPairOrderDetails = null; if (useExistingMarketPair) { buyMarketPairOrderDetails = Account.AddBlankOrder(buyMarketPairOptions.Pair, buyMarketPairOptions.MaxCost.Value / GetPrice(buyMarketPairOptions.Pair, TradePriceType.Ask), includeFees: false); loggingService.Info($"Use existing market pair for arbitrage: {marketPair}. " + $"Average price: {existingMarketPair.AveragePrice}, Current price: {existingMarketPair.CurrentPrice}"); } else { buyMarketPairOrderDetails = orderingService.PlaceBuyOrder(buyMarketPairOptions); } if (buyMarketPairOrderDetails.Result == OrderResult.Filled) { decimal buyArbitragePairMultiplier = pairConfig.ArbitrageBuyMultiplier ?? DEFAULT_ARBITRAGE_BUY_MULTIPLIER; decimal buyMarketPairFees = CalculateOrderFees(buyMarketPairOrderDetails); string arbitragePair = Exchange.ChangeMarket(options.Pair, options.Arbitrage.Market.ToString()); decimal buyArbitragePairAmount = options.Arbitrage.Market == ArbitrageMarket.USDT ? buyMarketPairOrderDetails.AmountFilled * GetPrice(buyMarketPairOrderDetails.Pair, TradePriceType.Ask, normalize: false) / GetPrice(arbitragePair, TradePriceType.Ask) : buyMarketPairOrderDetails.AmountFilled / GetPrice(arbitragePair, TradePriceType.Ask); var buyArbitragePairOptions = new BuyOptions(arbitragePair) { Arbitrage = true, ManualOrder = options.ManualOrder, Amount = buyArbitragePairAmount * buyArbitragePairMultiplier, Metadata = options.Metadata }; IOrderDetails buyArbitragePairOrderDetails = orderingService.PlaceBuyOrder(buyArbitragePairOptions); if (buyArbitragePairOrderDetails.Result == OrderResult.Filled) { decimal buyArbitragePairFees = CalculateOrderFees(buyArbitragePairOrderDetails); options.Metadata.FeesNonDeductible = buyMarketPairFees * buyArbitragePairMultiplier; var sellArbitragePairOptions = new SellOptions(buyArbitragePairOrderDetails.Pair) { Arbitrage = true, Amount = buyArbitragePairOrderDetails.AmountFilled, ManualOrder = options.ManualOrder, Metadata = options.Metadata }; TradingPair existingArbitragePair = Account.GetTradingPair(buyArbitragePairOrderDetails.Pair) as TradingPair; existingArbitragePair.OverrideCost(buyArbitragePairOrderDetails.Cost + buyArbitragePairFees * 2); IOrderDetails sellArbitragePairOrderDetails = orderingService.PlaceSellOrder(sellArbitragePairOptions); existingArbitragePair.OverrideCost(null); if (sellArbitragePairOrderDetails.Result == OrderResult.Filled) { loggingService.Info($"{pairConfig.ArbitrageType} arbitrage successful: {marketPair} -> {arbitragePair} -> {existingArbitragePair.Pair}"); } else { loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to sell arbitrage pair {arbitragePair}"); notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to sell arbitrage pair {arbitragePair}"); } } else { loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to buy arbitrage pair {arbitragePair}"); notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to buy arbitrage pair {arbitragePair}"); } } else { loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to buy market pair {marketPair}"); } } else { loggingService.Info($"Unable to arbitrage {options.Pair}: {message}"); } }
public IOrderDetails PlaceSellOrder(SellOptions options) { IOrderDetails orderDetails = null; trailingSells.TryRemove(options.Pair, out SellTrailingInfo sellTrailingInfo); trailingBuys.TryRemove(options.Pair, out BuyTrailingInfo buyTrailingInfo); if (tradingService.CanSell(options, out string message)) { IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair); ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair); tradingPair.SetCurrentValues(tradingService.GetCurrentPrice(options.Pair), tradingService.GetCurrentSpread(options.Pair)); SellOrder sellOrder = new SellOrder { Type = pairConfig.SellType, Date = DateTimeOffset.Now, Pair = options.Pair, Amount = options.Amount ?? tradingPair.TotalAmount, Price = tradingPair.CurrentPrice }; if (!tradingService.Config.VirtualTrading) { loggingService.Info($"Place sell order for {tradingPair.FormattedName}. Price: {sellOrder.Price:0.00000000}, Amount: {sellOrder.Amount:0.########}, Margin: {tradingPair.CurrentMargin:0.00}"); try { lock (tradingService.Account.SyncRoot) { orderDetails = tradingService.PlaceOrder(sellOrder); tradingPair.Metadata.SwapPair = options.SwapPair; orderDetails.SetMetadata(tradingPair.Metadata); ITradeResult tradeResult = tradingService.Account.AddSellOrder(orderDetails); tradeResult.SetSwap(options.Swap); tradingService.Account.Save(); tradingService.LogOrder(orderDetails); decimal soldMargin = (tradeResult.Profit / (tradeResult.AverageCost + (tradeResult.Metadata.AdditionalCosts ?? 0)) * 100); string swapPair = options.SwapPair != null ? $", Swap Pair: {options.SwapPair}" : ""; loggingService.Info("{@Trade}", orderDetails); loggingService.Info("{@Trade}", tradeResult); loggingService.Info($"Sell order result for {tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Filled: {orderDetails.AmountFilled:0.########}, Margin: {soldMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}"); notificationService.Notify($"Sold {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Margin: {soldMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}"); } } catch (Exception ex) { loggingService.Error($"Unable to place sell order for {options.Pair}", ex); notificationService.Notify($"Unable to sell {options.Pair}: {ex.Message}"); } } else { loggingService.Info($"Place virtual sell order for {tradingPair.FormattedName}. Price: {sellOrder.Price:0.00000000}, Amount: {sellOrder.Amount:0.########}"); lock (tradingService.Account.SyncRoot) { orderDetails = new OrderDetails { Metadata = tradingPair.Metadata, OrderId = DateTime.Now.ToFileTimeUtc().ToString(), Side = OrderSide.Sell, Result = OrderResult.Filled, Date = sellOrder.Date, Pair = sellOrder.Pair, Amount = sellOrder.Amount, AmountFilled = sellOrder.Amount, Price = sellOrder.Price, AveragePrice = sellOrder.Price }; tradingPair.Metadata.SwapPair = options.SwapPair; ITradeResult tradeResult = tradingService.Account.AddSellOrder(orderDetails); tradeResult.SetSwap(options.Swap); tradingService.Account.Save(); tradingService.LogOrder(orderDetails); decimal soldMargin = (tradeResult.Profit / (tradeResult.AverageCost + (tradeResult.Metadata.AdditionalCosts ?? 0)) * 100); string swapPair = options.SwapPair != null ? $", Swap Pair: {options.SwapPair}" : ""; loggingService.Info("{@Trade}", orderDetails); loggingService.Info("{@Trade}", tradeResult); loggingService.Info($"Virtual sell order result for {tradingPair.FormattedName}. Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Margin: {tradingPair.CurrentMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}"); notificationService.Notify($"Sold {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}, Profit: {tradeResult.Profit:0.00000000}{swapPair}"); } } tradingService.ReapplyTradingRules(); } else { loggingService.Info(message); } return(orderDetails); }
public IOrderDetails PlaceBuyOrder(BuyOptions options) { IOrderDetails orderDetails = null; trailingBuys.TryRemove(options.Pair, out BuyTrailingInfo buyTrailingInfo); trailingSells.TryRemove(options.Pair, out SellTrailingInfo sellTrailingInfo); if (tradingService.CanBuy(options, out string message)) { IPairConfig pairConfig = tradingService.GetPairConfig(options.Pair); ITradingPair tradingPair = tradingService.Account.GetTradingPair(options.Pair); decimal currentPrice = tradingService.GetCurrentPrice(options.Pair); options.Metadata.TradingRules = pairConfig.Rules.ToList(); if (options.Metadata.LastBuyMargin == null) { options.Metadata.LastBuyMargin = tradingPair?.CurrentMargin ?? 0; } string signalRule = options.Metadata.SignalRule ?? "N/A"; BuyOrder buyOrder = new BuyOrder { Type = pairConfig.BuyType, Date = DateTimeOffset.Now, Pair = options.Pair, Amount = options.Amount ?? (options.MaxCost.Value / currentPrice), Price = currentPrice }; if (!tradingService.Config.VirtualTrading) { loggingService.Info($"Place buy order for {tradingPair?.FormattedName ?? options.Pair}. Price: {buyOrder.Price:0.00000000}, Amount: {buyOrder.Amount:0.########}, Signal Rule: {signalRule}"); try { lock (tradingService.Account.SyncRoot) { orderDetails = tradingService.PlaceOrder(buyOrder); orderDetails.SetMetadata(options.Metadata); tradingService.Account.AddBuyOrder(orderDetails); tradingService.Account.Save(); tradingService.LogOrder(orderDetails); tradingPair = tradingService.Account.GetTradingPair(options.Pair); loggingService.Info("{@Trade}", orderDetails); loggingService.Info($"Buy order result for {tradingPair.FormattedName}: {orderDetails.Result} ({orderDetails.Message}). Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Filled: {orderDetails.AmountFilled:0.########}, Cost: {orderDetails.AverageCost:0.00000000}"); notificationService.Notify($"Bought {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Cost: {orderDetails.AverageCost:0.00000000}"); } } catch (Exception ex) { loggingService.Error($"Unable to place buy order for {options.Pair}", ex); notificationService.Notify($"Unable to buy {options.Pair}: {ex.Message}"); } } else { loggingService.Info($"Place virtual buy order for {tradingPair?.FormattedName ?? options.Pair}. Price: {buyOrder.Price:0.00000000}, Amount: {buyOrder.Amount:0.########}, Signal Rule: {signalRule}"); lock (tradingService.Account.SyncRoot) { decimal roundedAmount = Math.Round(buyOrder.Amount, 4); orderDetails = new OrderDetails { Metadata = options.Metadata, OrderId = DateTime.Now.ToFileTimeUtc().ToString(), Side = OrderSide.Buy, Result = OrderResult.Filled, Date = buyOrder.Date, Pair = buyOrder.Pair, Amount = roundedAmount, AmountFilled = roundedAmount, Price = buyOrder.Price, AveragePrice = buyOrder.Price }; tradingService.Account.AddBuyOrder(orderDetails); tradingService.Account.Save(); tradingService.LogOrder(orderDetails); tradingPair = tradingService.Account.GetTradingPair(options.Pair); loggingService.Info("{@Trade}", orderDetails); loggingService.Info($"Virtual buy order result for {tradingPair.FormattedName}. Price: {orderDetails.AveragePrice:0.00000000}, Amount: {orderDetails.Amount:0.########}, Cost: {orderDetails.AverageCost:0.00000000}"); notificationService.Notify($"Bought {tradingPair.FormattedName}. Amount: {orderDetails.AmountFilled:0.########}, Price: {orderDetails.AveragePrice:0.00000000}, Cost: {orderDetails.AverageCost:0.00000000}"); } } tradingService.ReapplyTradingRules(); } else { loggingService.Info(message); } return(orderDetails); }
private void ProcessTradingPairs() { int traidingPairsCount = 0; foreach (var tradingPair in tradingService.Account.GetTradingPairs()) { IPairConfig pairConfig = tradingService.GetPairConfig(tradingPair.Pair); tradingPair.SetCurrentValues(tradingService.GetCurrentPrice(tradingPair.Pair), tradingService.GetCurrentSpread(tradingPair.Pair)); tradingPair.Metadata.TradingRules = pairConfig.Rules.ToList(); tradingPair.Metadata.CurrentRating = tradingPair.Metadata.Signals != null?signalsService.GetRating(tradingPair.Pair, tradingPair.Metadata.Signals) : null; tradingPair.Metadata.CurrentGlobalRating = signalsService.GetGlobalRating(); if (trailingSells.TryGetValue(tradingPair.Pair, out SellTrailingInfo sellTrailingInfo)) { if (pairConfig.SellEnabled) { if (Math.Round(tradingPair.CurrentMargin, 1) != Math.Round(sellTrailingInfo.LastTrailingMargin, 1)) { if (LoggingEnabled) { loggingService.Info($"Continue trailing sell {tradingPair.FormattedName}. Price: {tradingPair.CurrentPrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}"); } } if (tradingPair.CurrentMargin <= sellTrailingInfo.TrailingStopMargin || tradingPair.CurrentMargin < (sellTrailingInfo.BestTrailingMargin - sellTrailingInfo.Trailing)) { trailingSells.TryRemove(tradingPair.Pair, out SellTrailingInfo p); if (tradingPair.CurrentMargin > 0 || sellTrailingInfo.SellMargin < 0) { if (sellTrailingInfo.TrailingStopAction == SellTrailingStopAction.Sell || tradingPair.CurrentMargin > sellTrailingInfo.TrailingStopMargin) { PlaceSellOrder(sellTrailingInfo.SellOptions); } else { if (LoggingEnabled) { loggingService.Info($"Stop trailing sell {tradingPair.FormattedName}. Reason: stop margin reached"); } } } else { if (LoggingEnabled) { loggingService.Info($"Stop trailing sell {tradingPair.FormattedName}. Reason: negative margin"); } } } else { sellTrailingInfo.LastTrailingMargin = tradingPair.CurrentMargin; if (tradingPair.CurrentMargin > sellTrailingInfo.BestTrailingMargin) { sellTrailingInfo.BestTrailingMargin = tradingPair.CurrentMargin; } } } else { trailingSells.TryRemove(tradingPair.Pair, out SellTrailingInfo p); } } else { if (pairConfig.SellEnabled && tradingPair.CurrentMargin >= pairConfig.SellMargin) { InitiateSell(new SellOptions(tradingPair.Pair)); } else if (pairConfig.SellEnabled && pairConfig.SellStopLossEnabled && tradingPair.CurrentMargin <= pairConfig.SellStopLossMargin && tradingPair.CurrentAge >= pairConfig.SellStopLossMinAge && (pairConfig.NextDCAMargin == null || !pairConfig.SellStopLossAfterDCA)) { if (LoggingEnabled) { loggingService.Info($"Stop loss triggered for {tradingPair.FormattedName}. Margin: {tradingPair.CurrentMargin:0.00}"); } PlaceSellOrder(new SellOptions(tradingPair.Pair)); } else if (pairConfig.NextDCAMargin != null && pairConfig.BuyEnabled && pairConfig.NextDCAMargin != null && !trailingBuys.ContainsKey(tradingPair.Pair) && !trailingSells.ContainsKey(tradingPair.Pair)) { if (tradingPair.CurrentMargin <= pairConfig.NextDCAMargin) { var buyOptions = new BuyOptions(tradingPair.Pair) { MaxCost = tradingPair.AverageCostPaid * pairConfig.BuyMultiplier, IgnoreExisting = true }; if (tradingService.CanBuy(buyOptions, message: out string message)) { if (LoggingEnabled) { loggingService.Info($"DCA triggered for {tradingPair.FormattedName}. Margin: {tradingPair.CurrentMargin:0.00}, Level: {pairConfig.NextDCAMargin:0.00}, Multiplier: {pairConfig.BuyMultiplier}"); } InitiateBuy(buyOptions); } } } } traidingPairsCount++; } foreach (var kvp in trailingBuys) { string pair = kvp.Key; BuyTrailingInfo buyTrailingInfo = kvp.Value; ITradingPair tradingPair = tradingService.Account.GetTradingPair(pair); IPairConfig pairConfig = tradingService.GetPairConfig(pair); decimal currentPrice = tradingService.GetCurrentPrice(pair); decimal currentMargin = Utils.CalculateMargin(buyTrailingInfo.InitialPrice, currentPrice); if (pairConfig.BuyEnabled) { if (Math.Round(currentMargin, 1) != Math.Round(buyTrailingInfo.LastTrailingMargin, 1)) { if (LoggingEnabled) { loggingService.Info($"Continue trailing buy {tradingPair?.FormattedName ?? pair}. Price: {currentPrice:0.00000000}, Margin: {currentMargin:0.00}"); } } if (currentMargin >= buyTrailingInfo.TrailingStopMargin || currentMargin > (buyTrailingInfo.BestTrailingMargin - buyTrailingInfo.Trailing)) { trailingBuys.TryRemove(pair, out BuyTrailingInfo p); if (buyTrailingInfo.TrailingStopAction == BuyTrailingStopAction.Buy || currentMargin < buyTrailingInfo.TrailingStopMargin) { PlaceBuyOrder(buyTrailingInfo.BuyOptions); } else { if (LoggingEnabled) { loggingService.Info($"Stop trailing buy {tradingPair?.FormattedName ?? pair}. Reason: stop margin reached"); } } } else { buyTrailingInfo.LastTrailingMargin = currentMargin; if (currentMargin < buyTrailingInfo.BestTrailingMargin) { buyTrailingInfo.BestTrailingMargin = currentMargin; } } } else { trailingBuys.TryRemove(pair, out BuyTrailingInfo p); } } healthCheckService.UpdateHealthCheck(Constants.HealthChecks.TradingPairsProcessed, $"Pairs: {traidingPairsCount}, Trailing buys: {trailingBuys.Count}, Trailing sells: {trailingSells.Count}"); }
private void ArbitrageDirect(ArbitrageOptions options) { string arbitragePair = options.Pair; ITradingPair existingArbitragePair = this.Account.GetTradingPair(arbitragePair); IPairConfig pairConfig = this.GetPairConfig(options.Pair); bool useExistingArbitragePair = (existingArbitragePair != null && existingArbitragePair.CurrentCost > pairConfig.BuyMaxCost && existingArbitragePair.AveragePrice <= existingArbitragePair.CurrentPrice); var buyArbitragePairOptions = new BuyOptions(arbitragePair) { Arbitrage = true, MaxCost = pairConfig.BuyMaxCost, ManualOrder = options.ManualOrder, IgnoreBalance = useExistingArbitragePair, Metadata = options.Metadata }; if (this.CanBuy(buyArbitragePairOptions, out string message)) { IOrderDetails buyArbitragePairOrderDetails = null; if (useExistingArbitragePair) { buyArbitragePairOrderDetails = this.Account.AddBlankOrder(buyArbitragePairOptions.Pair, buyArbitragePairOptions.MaxCost.Value / this.GetPrice(buyArbitragePairOptions.Pair, TradePriceType.Ask), includeFees: false); loggingService.Info($"Use existing arbitrage pair for arbitrage: {arbitragePair}. " + $"Average price: {existingArbitragePair.AveragePrice}, Current price: {existingArbitragePair.CurrentPrice}"); } else { buyArbitragePairOrderDetails = orderingService.PlaceBuyOrder(buyArbitragePairOptions); } if (buyArbitragePairOrderDetails.Result == OrderResult.Filled) { decimal buyArbitragePairFees = this.CalculateOrderFees(buyArbitragePairOrderDetails); string flippedArbitragePair = this.Exchange.ChangeMarket(arbitragePair, options.Arbitrage.Market.ToString()); var sellArbitragePairOptions = new SellOptions(flippedArbitragePair) { Arbitrage = true, Amount = buyArbitragePairOrderDetails.AmountFilled, ManualOrder = options.ManualOrder, Metadata = options.Metadata.MergeWith(new OrderMetadata { IsTransitional = true }) }; IOrderDetails sellArbitragePairOrderDetails = orderingService.PlaceSellOrder(sellArbitragePairOptions); if (sellArbitragePairOrderDetails.Result == OrderResult.Filled) { decimal sellArbitragePairMultiplier = pairConfig.ArbitrageSellMultiplier ?? DEFAULT_ARBITRAGE_SELL_MULTIPLIER; decimal sellArbitragePairFees = this.CalculateOrderFees(sellArbitragePairOrderDetails); options.Metadata.FeesNonDeductible = buyArbitragePairFees * sellArbitragePairMultiplier; decimal sellMarketPairAmount = sellArbitragePairOrderDetails.AmountFilled * this.GetPrice(flippedArbitragePair, TradePriceType.Bid, normalize: false) * sellArbitragePairMultiplier; string marketPair = this.Exchange.GetArbitrageMarketPair(options.Arbitrage.Market); var sellMarketPairOptions = new SellOptions(marketPair) { Arbitrage = true, Amount = sellMarketPairAmount, ManualOrder = options.ManualOrder, Metadata = options.Metadata.MergeWith(new OrderMetadata { IsTransitional = false, OriginalPair = arbitragePair }) }; existingArbitragePair = this.Account.GetTradingPair(marketPair); existingArbitragePair.OverrideCost((buyArbitragePairOrderDetails.Cost + sellArbitragePairFees * 2) * sellArbitragePairMultiplier); IOrderDetails sellMarketPairOrderDetails = orderingService.PlaceSellOrder(sellMarketPairOptions); existingArbitragePair.OverrideCost(null); if (sellMarketPairOrderDetails.Result == OrderResult.Filled) { loggingService.Info($"{pairConfig.ArbitrageType} arbitrage successful: {arbitragePair} -> {flippedArbitragePair} -> {marketPair}"); } else { loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to sell market pair {arbitragePair}"); notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to sell market pair {arbitragePair}"); } } else { loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to sell arbitrage pair {flippedArbitragePair}"); notificationService.Notify($"Unable to arbitrage {options.Pair}: Failed to sell arbitrage pair {flippedArbitragePair}"); } } else { loggingService.Info($"Unable to arbitrage {options.Pair}. Reason: failed to buy arbitrage pair {arbitragePair}"); } } else { loggingService.Info($"Unable to arbitrage {options.Pair}: {message}"); } }