diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs index 7c5406d..c5688b1 100644 --- a/src/Managing.Api/Program.cs +++ b/src/Managing.Api/Program.cs @@ -249,24 +249,6 @@ app.UseEndpoints(endpoints => Predicate = r => r.Tags.Contains("live"), ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse }); - - endpoints.MapHealthChecks("/health/candles", new HealthCheckOptions - { - Predicate = r => r.Tags.Contains("candles"), - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - - endpoints.MapHealthChecks("/health/gmx", new HealthCheckOptions - { - Predicate = r => r.Name == "gmx-connectivity", - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - - endpoints.MapHealthChecks("/health/web3proxy", new HealthCheckOptions - { - Predicate = r => r.Name == "web3proxy", - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); }); app.Run(); \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/IEvmManager.cs b/src/Managing.Application.Abstractions/Repositories/IEvmManager.cs index 4d726b2..e2bc235 100644 --- a/src/Managing.Application.Abstractions/Repositories/IEvmManager.cs +++ b/src/Managing.Application.Abstractions/Repositories/IEvmManager.cs @@ -20,7 +20,7 @@ public interface IEvmManager Task> GetAllBalancesOnAllChain(string publicAddress); Task> GetCandles(Ticker ticker, DateTime startDate, - Timeframe interval); + Timeframe interval, bool isFirstCall = false); decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker); Task> GetAvailableTicker(); diff --git a/src/Managing.Application.Abstractions/Services/IExchangeService.cs b/src/Managing.Application.Abstractions/Services/IExchangeService.cs index 8ebc47b..2d962a0 100644 --- a/src/Managing.Application.Abstractions/Services/IExchangeService.cs +++ b/src/Managing.Application.Abstractions/Services/IExchangeService.cs @@ -27,7 +27,7 @@ public interface IExchangeService Task> GetBalances(Account account, bool isForPaperTrading = false); decimal GetPrice(Account account, Ticker ticker, DateTime date); Task GetTrade(Account account, string order, Ticker ticker); - Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval); + Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval, bool isFirstCall); Task OpenStopLoss(Account account, Ticker ticker, TradeDirection originalDirection, decimal stopLossPrice, decimal quantity, bool isForPaperTrading = false, DateTime? currentDate = null); diff --git a/src/Managing.Application.Workers/Managing.Application.Workers.csproj b/src/Managing.Application.Workers/Managing.Application.Workers.csproj index dfb4696..ad04fde 100644 --- a/src/Managing.Application.Workers/Managing.Application.Workers.csproj +++ b/src/Managing.Application.Workers/Managing.Application.Workers.csproj @@ -1,19 +1,25 @@ - - net8.0 - enable - enable - + + net8.0 + enable + enable + - - - - - + + + + + - - - + + + + + + + ..\Managing.Api\bin\Debug\net8.0\Managing.Application.dll + + diff --git a/src/Managing.Application.Workers/PricesService.cs b/src/Managing.Application.Workers/PricesService.cs index 03cdff6..1f384f9 100644 --- a/src/Managing.Application.Workers/PricesService.cs +++ b/src/Managing.Application.Workers/PricesService.cs @@ -1,6 +1,7 @@ using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Workers.Abstractions; +using Managing.Domain.Candles; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -38,7 +39,16 @@ public class PricesService : IPricesService await _candleRepository.GetCandles(exchange, ticker, timeframe, DateTime.UtcNow.AddDays(-2)); var lastCandle = lastCandles.LastOrDefault(); var startDate = lastCandle != null ? lastCandle.Date : new DateTime(2017, 1, 1); - var newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe); + + List newCandles; + if (!lastCandles.Any()) + { + newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe, true); + } + else + { + newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe, false); + } var candles = !lastCandles.Any() ? newCandles : newCandles.Where(c => c.Date > lastCandle?.Date); var candlesInserted = 0; diff --git a/src/Managing.Application.Workers/StatisticService.cs b/src/Managing.Application.Workers/StatisticService.cs index 9ed935f..530586d 100644 --- a/src/Managing.Application.Workers/StatisticService.cs +++ b/src/Managing.Application.Workers/StatisticService.cs @@ -1,4 +1,5 @@ -using Managing.Application.Abstractions.Repositories; +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Workers.Abstractions; using Managing.Domain.Accounts; @@ -23,6 +24,7 @@ public class StatisticService : IStatisticService private readonly IBacktester _backtester; private readonly ITradaoService _tradaoService; private readonly IMessengerService _messengerService; + private readonly ICacheService _cacheService; private readonly ILogger _logger; public StatisticService( @@ -34,7 +36,8 @@ public class StatisticService : IStatisticService ITradingService tradingService, IBacktester backtester, ITradaoService tradaoService, - IMessengerService messengerService) + IMessengerService messengerService, + ICacheService cacheService) { _exchangeService = exchangeService; _accountService = accountService; @@ -45,6 +48,7 @@ public class StatisticService : IStatisticService _backtester = backtester; _tradaoService = tradaoService; _messengerService = messengerService; + _cacheService = cacheService; } public async Task UpdateTopVolumeTicker(TradingExchanges exchange, int top) @@ -159,7 +163,16 @@ public class StatisticService : IStatisticService public async Task> GetTickers() { - return await _evmManager.GetAvailableTicker(); + var cachedTickers = _cacheService.GetValue>("tickers"); + + if (cachedTickers != null) + { + return cachedTickers; + } + + var tickers = await _evmManager.GetAvailableTicker(); + _cacheService.SaveValue("tickers", tickers, TimeSpan.FromDays(1)); + return tickers; } public async Task UpdateSpotlight() diff --git a/src/Managing.Infrastructure.Exchanges/ExchangeService.cs b/src/Managing.Infrastructure.Exchanges/ExchangeService.cs index cbf8800..6219999 100644 --- a/src/Managing.Infrastructure.Exchanges/ExchangeService.cs +++ b/src/Managing.Infrastructure.Exchanges/ExchangeService.cs @@ -5,6 +5,7 @@ using Managing.Domain.Candles; using Managing.Domain.Statistics; using Managing.Domain.Trades; using Managing.Infrastructure.Exchanges.Abstractions; +using Managing.Infrastructure.Exchanges.Exchanges; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -199,10 +200,15 @@ namespace Managing.Infrastructure.Exchanges return await processor.GetTrades(account, ticker); } - public async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, - Timeframe timeframe) + public async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe timeframe, bool isFirstCall) { var processor = GetProcessor(account); + // Only EvmProcessor supports isFirstCall + if (processor is EvmProcessor evmProcessor) + { + return await evmProcessor.GetCandles(account, ticker, startDate, timeframe, isFirstCall); + } + // Fallback to default behavior for other processors return await processor.GetCandles(account, ticker, startDate, timeframe); } diff --git a/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs b/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs index 972b163..96f13e8 100644 --- a/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs +++ b/src/Managing.Infrastructure.Exchanges/Exchanges/EvmProcessor.cs @@ -73,6 +73,11 @@ public class EvmProcessor : BaseProcessor return await _evmManager.GetCandles(ticker, startDate, interval); } + public async Task> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval, bool isFirstCall) + { + return await _evmManager.GetCandles(ticker, startDate, interval, isFirstCall); + } + public override decimal GetFee(Account account, bool isForPaperTrading = false) { return _evmManager.GetFee(Constants.Chains.Arbitrum).Result; diff --git a/src/Managing.Infrastructure.Web3/EvmManager.cs b/src/Managing.Infrastructure.Web3/EvmManager.cs index 8dc6c9d..2199a8b 100644 --- a/src/Managing.Infrastructure.Web3/EvmManager.cs +++ b/src/Managing.Infrastructure.Web3/EvmManager.cs @@ -336,20 +336,43 @@ public class EvmManager : IEvmManager return chainBalances; } - public async Task> GetCandles(Ticker ticker, DateTime startDate, - Timeframe timeframe) + public async Task> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe, + bool isFirstCall = false) { string gmxTimeframe = GmxHelpers.GeTimeframe(timeframe); - var gmxPrices = await _httpClient.GetFromJsonAsync( - $"https://arbitrum-api.gmxinfra.io/prices/candles?tokenSymbol={ticker}&period={gmxTimeframe}&limit=10000"); + int limit = isFirstCall ? 10000 : CalculateCandleLimit(startDate, timeframe); + + GmxV2Prices? gmxPrices = null; + int maxRetries = 3; + int delayMs = 1000; + + for (int attempt = 1; attempt <= maxRetries; attempt++) + { + try + { + gmxPrices = await _httpClient.GetFromJsonAsync( + $"https://arbitrum-api.gmxinfra.io/prices/candles?tokenSymbol={ticker}&period={gmxTimeframe}&limit={limit}"); + break; + } + catch (HttpRequestException ex) when (ex.InnerException is IOException) + { + Console.Error.WriteLine($"Attempt {attempt}: Network error while fetching candles: {ex.Message}"); + if (attempt == maxRetries) + throw; + await Task.Delay(delayMs * attempt); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Unexpected error: {ex.Message}"); + throw; + } + } if (gmxPrices == null) return null; var filteredCandles = gmxPrices.Candles.Where(p => p[0] >= startDate.ToUnixTimestamp()).ToList(); - var candles = new List(); - var timeBetweenCandles = gmxPrices.Candles.Count > 2 ? gmxPrices.Candles[0][0] - gmxPrices.Candles[1][0] : 900; // Default 15 minutes @@ -362,6 +385,25 @@ public class EvmManager : IEvmManager return candles.OrderBy(c => c.Date).ToList(); } + private int CalculateCandleLimit(DateTime startDate, Timeframe timeframe) + { + var now = DateTime.UtcNow; + var minutesPerCandle = timeframe switch + { + Timeframe.OneMinute => 1, + Timeframe.FiveMinutes => 5, + Timeframe.FifteenMinutes => 15, + Timeframe.ThirtyMinutes => 30, + Timeframe.OneHour => 60, + Timeframe.FourHour => 240, + Timeframe.OneDay => 1440, + _ => 15 + }; + var totalMinutes = (now - startDate).TotalMinutes; + var candlesNeeded = (int)Math.Ceiling(totalMinutes / minutesPerCandle); + return Math.Min(candlesNeeded + 5, 10000); + } + public decimal GetVolume(SubgraphProvider subgraphProvider, Ticker ticker) { var subgraph = GetSubgraph(subgraphProvider); @@ -850,4 +892,10 @@ public class EvmManager : IEvmManager await _web3ProxyService.CallPrivyServiceAsync("sign-message", requestBody); return response.Signature; } + + // Overload to match IEvmManager interface + public async Task> GetCandles(Ticker ticker, DateTime startDate, Timeframe timeframe) + { + return await GetCandles(ticker, startDate, timeframe, false); + } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs index 0c42391..9003de5 100644 --- a/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs +++ b/src/Managing.Infrastructure.Web3/Services/Gmx/GmxV2Mappers.cs @@ -139,7 +139,7 @@ internal static class GmxV2Mappers } catch (Exception e) { - Console.WriteLine($"Could not parse ticker for symbol {t.Symbol}: {e.Message}"); + //Console.WriteLine($"Could not parse ticker for symbol {t.Symbol}: {e.Message}"); } } diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index 0f9e347..a13e5f2 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -196,7 +196,6 @@ export const openGmxPositionImpl = async ( const marketInfo = getMarketInfoFromTicker(ticker, marketsInfoData); const collateralToken = getTokenDataFromTicker("USDC", tokensData); // Using USDC as collateral - console.log('collateralToken', collateralToken) // Calculate the collateral amount in USDC (quantity * price) const collateralAmount = BigInt(Math.floor((quantity || 0) * (price || 0) * 1e6)); // USDC has 6 decimals @@ -325,8 +324,6 @@ export const cancelGmxOrdersImpl = async ( tokensData }); - console.log('ordersData', ordersData) - // Extract order keys for the specified ticker const orderKeys = Object.values(ordersData.ordersInfoData) .filter(order => { @@ -341,8 +338,6 @@ export const cancelGmxOrdersImpl = async ( return true; // No orders to cancel } - console.log('orderKeys', orderKeys) - // Cancel orders using the batch method await sdk.orders.cancelOrders(orderKeys); @@ -455,8 +450,6 @@ export const closeGmxPositionImpl = async ( showPnlInLeverage: true }); - console.log('positionsInfo', positionsInfo) - console.log('direction', direction) // Find the specific position to close const positionKey = Object.keys(positionsInfo).find(key => { const position = positionsInfo[key]; @@ -503,8 +496,6 @@ export const closeGmxPositionImpl = async ( triggerPrice: position.markPrice, } - //console.log('params', params) - const params2 = { marketInfo, marketsInfoData,