using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; using Managing.Domain.Candles; using Managing.Domain.Statistics; using Managing.Domain.Trades; using Managing.Infrastructure.Exchanges.Abstractions; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; namespace Managing.Infrastructure.Exchanges { public class ExchangeService : IExchangeService { private readonly ILogger _logger; private readonly ICandleRepository _candleRepository; private readonly IEnumerable _exchangeProcessor; public ExchangeService(ILogger logger, ICandleRepository candleRepository, IEnumerable processor) { _logger = logger; _candleRepository = candleRepository; _exchangeProcessor = processor; } #region Trades public async Task OpenTrade( Account account, Ticker ticker, TradeDirection direction, decimal price, decimal quantity, decimal? leverage = null, TradeType tradeType = TradeType.Limit, bool reduceOnly = false, bool isForPaperTrading = false, DateTime? currentDate = null, bool ioc = true, decimal? stopLossPrice = null, decimal? takeProfitPrice = null) { _logger.LogInformation( $"OpenMarketTrade - {ticker} - Type: {tradeType} - {direction} - Price: {price} - Quantity: {quantity} - Leverage: {leverage} - SL: {stopLossPrice} - TP: {takeProfitPrice}"); if (isForPaperTrading) { return BuildEmptyTrade(ticker, price, quantity, direction, leverage, tradeType, currentDate.Value, reduceOnly ? TradeStatus.PendingOpen : TradeStatus.Filled); } var processor = GetProcessor(account); return await processor.OpenTrade(account, ticker, direction, price, quantity, leverage, tradeType, reduceOnly, isForPaperTrading, currentDate, ioc, stopLossPrice, takeProfitPrice); } private IExchangeProcessor GetProcessor(Account account) { var exchange = EvmTradingExchangesList.Contains(account.Exchange) ? TradingExchanges.Evm : account.Exchange; return _exchangeProcessor.First(e => e.Exchange() == exchange); } private static List EvmTradingExchangesList { get { var eligibileProcessors = new List() { TradingExchanges.Evm, TradingExchanges.GmxV2 }; return eligibileProcessors; } } private bool IsEvmExchange(Account account) { return EvmTradingExchangesList.Contains(account.Exchange); } public Trade BuildEmptyTrade(Ticker ticker, decimal price, decimal quantity, TradeDirection direction, decimal? leverage, TradeType tradeType, DateTime dateTime, TradeStatus tradeStatus) { return new Trade(dateTime, direction, tradeStatus, tradeType, ticker, quantity, price, leverage, Guid.NewGuid().ToString(), "EmptyTrade"); } public async Task OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice, decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null) { return await OpenTrade( account, ticker, originalDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long, stopLossPrice, quantity, tradeType: TradeType.StopLoss, isForPaperTrading: isForPaperTrading, currentDate: currentDate, reduceOnly: true); } public async Task OpenTakeProfit(Account account, Ticker ticker, TradeDirection originalDirection, decimal takeProfitPrice, decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null) { return await OpenTrade( account, ticker, originalDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long, takeProfitPrice, quantity, tradeType: TradeType.TakeProfit, isForPaperTrading: isForPaperTrading, currentDate: currentDate, reduceOnly: true); } public async Task ClosePosition(Account account, Position position, decimal lastPrice, bool isForPaperTrading = false) { var direction = position.OriginDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long; if (isForPaperTrading) { var fake = BuildEmptyTrade(position.Open.Ticker, lastPrice, position.Open.Quantity, direction, position.Open.Leverage, TradeType.Market, position.Open.Date, TradeStatus.Filled); return fake; } var processor = GetProcessor(account); var closedTrade = await processor.OpenTrade( account, position.Ticker, direction, lastPrice, position.Open.Quantity, position.Open.Leverage, tradeType: TradeType.Market, reduceOnly: true); if (!IsEvmExchange(account)) { // Only use for non-EVM exchanges since the call to the blockchain is async closedTrade.Price = processor.GetTrade(account, closedTrade.ExchangeOrderId, closedTrade.Ticker).Result .Price; } return closedTrade; } #endregion public async Task CancelOrder(Account account, Ticker ticker) { var processor = GetProcessor(account); return await processor.CancelOrder(account, ticker); } public async Task GetTrade(Account account, string orderId, Ticker ticker) { var processor = GetProcessor(account); return await processor.GetTrade(account, orderId, ticker); } public async Task GetTrade(string reference, string orderId, Ticker ticker) { var processor = _exchangeProcessor.First(e => e.Exchange() == TradingExchanges.Evm); return await processor.GetTrade(reference, orderId, ticker); } public Task> GetFundingRates() { var processor = _exchangeProcessor.First(e => e.Exchange() == TradingExchanges.Evm); return processor.GetFundingRates(); } public async Task> GetTrades(Account account, Ticker ticker) { var processor = GetProcessor(account); return await processor.GetTrades(account, ticker); } public async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe timeframe) { var processor = GetProcessor(account); return await processor.GetCandles(account, ticker, startDate, timeframe); } public async Task> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe) { var candlesFromRepo = await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate); return candlesFromRepo.ToList(); } public async Task> GetCandlesInflux(TradingExchanges exchange, Ticker ticker, DateTime startDate, Timeframe timeframe, DateTime endDate) { var candlesFromRepo = await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate, endDate); return candlesFromRepo.ToList(); } public async Task GetBalance(Account account, bool isForPaperTrading = false) { if (isForPaperTrading) { return 1000; } var processor = GetProcessor(account); return await processor.GetBalance(account); } public decimal GetFee(Account account, bool isForPaperTrading = false) { var processor = GetProcessor(account); return processor.GetFee(account); } public decimal GetPrice(Account account, Ticker ticker, DateTime date) { var processor = GetProcessor(account); return processor.GetPrice(account, ticker, date); } public Candle GetCandle(Account account, Ticker ticker, DateTime date) { var processor = GetProcessor(account); return processor.GetCandle(account, ticker, date); } public async Task GetQuantityInPosition(Account account, Ticker ticker) { var processor = GetProcessor(account); return await processor.GetQuantityInPosition(account, ticker); } public decimal GetVolume(Account account, Ticker ticker) { var processor = GetProcessor(account); return processor.GetVolume(account, ticker); } public async Task> GetTickers(Timeframe timeframe) { var tickers = await _candleRepository.GetTickersAsync(TradingExchanges.Evm, timeframe, DateTime.UtcNow.AddDays(-2)); return tickers.ToList(); } public decimal GetBestPrice(Account account, Ticker ticker, decimal lastPrice, decimal quantity, TradeDirection direction) { if (IsEvmExchange(account)) { return GetPrice(account, ticker, DateTime.UtcNow); } return GetOrderbook(account, ticker).GetBestPrice(direction, quantity); } public Orderbook GetOrderbook(Account account, Ticker ticker) { var processor = GetProcessor(account); return processor.GetOrderbook(account, ticker); } public async Task> GetBalances(Account account, bool isForPaperTrading = false) { var processor = GetProcessor(account); return await processor.GetBalances(account, isForPaperTrading); } public async Task> GetOpenOrders(Account account, Ticker ticker) { var processor = GetProcessor(account); return await processor.GetOrders(account, ticker); } } }