private void InitiateBuy(string pair, IRule rule, IEnumerable <ISignal> ruleSignals) { trailingSignals.TryRemove(pair, out List <SignalTrailingInfo> trailingInfo); IPairConfig pairConfig = tradingService.GetPairConfig(pair); SignalRuleModifiers ruleModifiers = rule.GetModifiers <SignalRuleModifiers>(); string ruleSignalsList = String.Join(", ", ruleSignals.Select(s => s.Name)); if (LoggingEnabled) { loggingService.Info($"Initiate buy request for {pair}. Rule: {rule.Name}, Signals: {ruleSignalsList}"); } var buyOptions = new BuyOptions(pair) { MaxCost = pairConfig.BuyMaxCost * pairConfig.BuyMultiplier * (ruleModifiers?.CostMultiplier ?? 1), Metadata = new OrderMetadata { SignalRule = rule.Name, Signals = ruleSignals.Select(s => s.Name).ToList(), BoughtRating = ruleSignals.Any(s => s.Rating.HasValue) ? ruleSignals.Where(s => s.Rating.HasValue).Average(s => s.Rating) : null, BoughtGlobalRating = signalsService.GetGlobalRating() } }; tradingService.Buy(buyOptions); }
public void Buy(BuyOptions options) { lock (SyncRoot) { IRule rule = signalsService.Rules.Entries.FirstOrDefault(r => r.Name == options.Metadata.SignalRule); ITradingPair swappedPair = Account.GetTradingPairs().OrderBy(p => p.CurrentMargin).FirstOrDefault(tradingPair => { IPairConfig pairConfig = GetPairConfig(tradingPair.Pair); return(pairConfig.SellEnabled && pairConfig.SwapEnabled && pairConfig.SwapSignalRules != null && pairConfig.SwapSignalRules.Contains(options.Metadata.SignalRule) && pairConfig.SwapTimeout < (DateTimeOffset.Now - tradingPair.OrderDates.Max()).TotalSeconds); }); if (swappedPair != null) { Swap(new SwapOptions(swappedPair.Pair, options.Pair, options.Metadata)); } else if (rule?.Action != Constants.SignalRuleActions.Swap) { if (CanBuy(options, out string message)) { tradingTimedTask.InitiateBuy(options); } else { loggingService.Debug(message); } } } }
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 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 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); }
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 void Swap(SwapOptions options) { lock (syncRoot) { PauseTasks(); try { if (CanSwap(options, out string message)) { ITradingPair oldTradingPair = Account.GetTradingPair(options.OldPair); var sellOptions = new SellOptions(options.OldPair) { Swap = true, ManualOrder = options.ManualOrder, Metadata = new OrderMetadata { SwapPair = options.NewPair } }; if (CanSell(sellOptions, out message)) { decimal currentMargin = oldTradingPair.CurrentMargin; decimal additionalCosts = oldTradingPair.Cost - oldTradingPair.CurrentCost + (oldTradingPair.Metadata.AdditionalCosts ?? 0); int additionalDCALevels = oldTradingPair.DCALevel; IOrderDetails sellOrderDetails = orderingService.PlaceSellOrder(sellOptions); if (!Account.HasTradingPair(options.OldPair)) { var buyOptions = new BuyOptions(options.NewPair) { Swap = true, ManualOrder = options.ManualOrder, MaxCost = sellOrderDetails.Cost, Metadata = options.Metadata }; buyOptions.Metadata.LastBuyMargin = currentMargin; buyOptions.Metadata.SwapPair = options.OldPair; buyOptions.Metadata.AdditionalDCALevels = additionalDCALevels; buyOptions.Metadata.AdditionalCosts = additionalCosts; IOrderDetails buyOrderDetails = orderingService.PlaceBuyOrder(buyOptions); var newTradingPair = Account.GetTradingPair(options.NewPair) as TradingPair; if (newTradingPair != null) { newTradingPair.Metadata.AdditionalCosts += CalculateOrderFees(sellOrderDetails); loggingService.Info($"Swap {oldTradingPair.FormattedName} for {newTradingPair.FormattedName}. " + $"Old margin: {oldTradingPair.CurrentMargin:0.00}, new margin: {newTradingPair.CurrentMargin:0.00}"); } else { loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to buy {options.NewPair}"); notificationService.Notify($"Unable to swap {options.OldPair} for {options.NewPair}: Failed to buy {options.NewPair}"); } } else { loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to sell {options.OldPair}"); } } else { loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}: {message}"); } } else { loggingService.Info(message); } } finally { ContinueTasks(); } } }
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}"); } }
public void Swap(SwapOptions options) { lock (SyncRoot) { if (CanSwap(options, out string message)) { ITradingPair oldTradingPair = Account.GetTradingPair(options.OldPair); var sellOptions = new SellOptions(options.OldPair) { Swap = true, SwapPair = options.NewPair, ManualOrder = options.ManualOrder }; if (CanSell(sellOptions, out message)) { decimal currentMargin = oldTradingPair.CurrentMargin; decimal additionalCosts = oldTradingPair.AverageCostPaid - oldTradingPair.CurrentCost + (oldTradingPair.Metadata.AdditionalCosts ?? 0); int additionalDCALevels = oldTradingPair.DCALevel; IOrderDetails sellOrderDetails = tradingTimedTask.PlaceSellOrder(sellOptions); if (!Account.HasTradingPair(options.OldPair)) { var buyOptions = new BuyOptions(options.NewPair) { Swap = true, ManualOrder = options.ManualOrder, MaxCost = sellOrderDetails.AverageCost, Metadata = options.Metadata }; buyOptions.Metadata.LastBuyMargin = currentMargin; buyOptions.Metadata.SwapPair = options.OldPair; buyOptions.Metadata.AdditionalDCALevels = additionalDCALevels; buyOptions.Metadata.AdditionalCosts = additionalCosts; IOrderDetails buyOrderDetails = tradingTimedTask.PlaceBuyOrder(buyOptions); var newTradingPair = Account.GetTradingPair(options.NewPair) as TradingPair; if (newTradingPair != null) { if (sellOrderDetails.Fees != 0 && sellOrderDetails.FeesCurrency != null) { if (sellOrderDetails.FeesCurrency == Config.Market) { newTradingPair.Metadata.AdditionalCosts += sellOrderDetails.Fees; } else { string feesPair = sellOrderDetails.FeesCurrency + Config.Market; newTradingPair.Metadata.AdditionalCosts += GetCurrentPrice(feesPair) * sellOrderDetails.Fees; } } loggingService.Info($"Swap {oldTradingPair.FormattedName} for {newTradingPair.FormattedName}. Old margin: {oldTradingPair.CurrentMargin:0.00}, new margin: {newTradingPair.CurrentMargin:0.00}"); } else { loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to buy {options.NewPair}"); notificationService.Notify($"Unable to swap {options.OldPair} for {options.NewPair}: Failed to buy {options.NewPair}"); } } else { loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}. Reason: failed to sell {options.OldPair}"); } } else { loggingService.Info($"Unable to swap {options.OldPair} for {options.NewPair}: {message}"); } } else { loggingService.Info(message); } } }