Fix get current price
This commit is contained in:
@@ -26,6 +26,7 @@ public interface IExchangeService
|
|||||||
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
||||||
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||||
Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
|
Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
|
||||||
|
Task<decimal> GetCurrentPrice(Account account, Ticker ticker);
|
||||||
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
||||||
|
|
||||||
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval,
|
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval,
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ namespace Managing.Application.Tests
|
|||||||
{
|
{
|
||||||
var settings = new Web3ProxySettings { BaseUrl = baseUrl };
|
var settings = new Web3ProxySettings { BaseUrl = baseUrl };
|
||||||
var options = Options.Create(settings);
|
var options = Options.Create(settings);
|
||||||
return new Web3ProxyService(options);
|
var logger = new Mock<ILogger<Web3ProxyService>>().Object;
|
||||||
|
return new Web3ProxyService(options, logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -615,7 +615,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
async exchangeService =>
|
async exchangeService =>
|
||||||
{
|
{
|
||||||
currentPrice = await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
currentPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker);
|
||||||
});
|
});
|
||||||
var riskResult = default(SynthRiskResult);
|
var riskResult = default(SynthRiskResult);
|
||||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
||||||
@@ -664,7 +664,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
{
|
{
|
||||||
return Config.IsForBacktest
|
return Config.IsForBacktest
|
||||||
? LastCandle?.Close ?? 0
|
? LastCandle?.Close ?? 0
|
||||||
: await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
: await exchangeService.GetCurrentPrice(Account, Config.Ticker);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (openedPosition != null)
|
if (openedPosition != null)
|
||||||
@@ -825,7 +825,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
{
|
{
|
||||||
currentPrice = Config.IsForBacktest
|
currentPrice = Config.IsForBacktest
|
||||||
? LastCandle?.Close ?? 0
|
? LastCandle?.Close ?? 0
|
||||||
: await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
: await exchangeService.GetCurrentPrice(Account, Config.Ticker);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1157,7 +1157,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
async exchangeService =>
|
async exchangeService =>
|
||||||
{
|
{
|
||||||
closingPrice = await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
closingPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker);
|
||||||
});
|
});
|
||||||
|
|
||||||
bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long
|
bool isManualCloseProfitable = position.OriginDirection == TradeDirection.Long
|
||||||
@@ -1541,7 +1541,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
await ServiceScopeHelpers.WithScopedServices<ITradingService, IExchangeService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedServices<ITradingService, IExchangeService>(_scopeFactory,
|
||||||
async (tradingService, exchangeService) =>
|
async (tradingService, exchangeService) =>
|
||||||
{
|
{
|
||||||
var currentPrice = await exchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
var currentPrice = await exchangeService.GetCurrentPrice(Account, Config.Ticker);
|
||||||
|
|
||||||
var signalValidationResult = await tradingService.ValidateSynthSignalAsync(
|
var signalValidationResult = await tradingService.ValidateSynthSignalAsync(
|
||||||
signal,
|
signal,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public interface IExchangeProcessor
|
|||||||
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
Task<decimal> GetBalance(Account account, bool isForPaperTrading = false);
|
||||||
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
Task<List<Balance>> GetBalances(Account account, bool isForPaperTrading = false);
|
||||||
Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
|
Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
|
||||||
|
Task<decimal> GetCurrentPrice(Account account, Ticker ticker);
|
||||||
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
||||||
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
|
Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
|
||||||
decimal GetVolume(Account account, Ticker ticker);
|
decimal GetVolume(Account account, Ticker ticker);
|
||||||
|
|||||||
@@ -249,10 +249,35 @@ namespace Managing.Infrastructure.Exchanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date)
|
public async Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var processor = GetProcessor(account);
|
var processor = GetProcessor(account);
|
||||||
return await processor.GetPrice(account, ticker, date);
|
return await processor.GetPrice(account, ticker, date);
|
||||||
}
|
}
|
||||||
|
catch (InvalidOperationException ex) when (ex.Message.Contains("no candle data available"))
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Primary price source failed for {ticker} at {date:yyyy-MM-dd HH:mm:ss}. Attempting fallback to current price.");
|
||||||
|
|
||||||
|
// Fallback: Try to get current price instead of historical price
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var processor = GetProcessor(account);
|
||||||
|
return await processor.GetCurrentPrice(account, ticker);
|
||||||
|
}
|
||||||
|
catch (Exception fallbackEx)
|
||||||
|
{
|
||||||
|
_logger.LogError(fallbackEx, $"Fallback price retrieval also failed for {ticker} at {date:yyyy-MM-dd HH:mm:ss}");
|
||||||
|
throw new InvalidOperationException($"Unable to retrieve price for {ticker}. Both primary and fallback methods failed. Please check if the ticker is valid and data sources are accessible.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetCurrentPrice(Account account, Ticker ticker)
|
||||||
|
{
|
||||||
|
var processor = GetProcessor(account);
|
||||||
|
return await processor.GetCurrentPrice(account, ticker);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Candle> GetCandle(Account account, Ticker ticker, DateTime date)
|
public async Task<Candle> GetCandle(Account account, Ticker ticker, DateTime date)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace Managing.Infrastructure.Exchanges.Exchanges
|
|||||||
public abstract Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
|
public abstract Task<List<Candle>> GetCandles(Account account, Ticker ticker, DateTime startDate, Timeframe interval);
|
||||||
public abstract decimal GetFee(Account account, bool isForPaperTrading = false);
|
public abstract decimal GetFee(Account account, bool isForPaperTrading = false);
|
||||||
public abstract Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
|
public abstract Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date);
|
||||||
|
public abstract Task<decimal> GetCurrentPrice(Account account, Ticker ticker);
|
||||||
public abstract Task<decimal> GetQuantityInPosition(Account account, Ticker ticker);
|
public abstract Task<decimal> GetQuantityInPosition(Account account, Ticker ticker);
|
||||||
public abstract Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
public abstract Task<Trade> GetTrade(Account account, string order, Ticker ticker);
|
||||||
public abstract Task<List<Trade>> GetTrades(Account account, Ticker ticker);
|
public abstract Task<List<Trade>> GetTrades(Account account, Ticker ticker);
|
||||||
|
|||||||
@@ -85,7 +85,59 @@ public class EvmProcessor : BaseProcessor
|
|||||||
|
|
||||||
public override async Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date)
|
public override async Task<decimal> GetPrice(Account account, Ticker ticker, DateTime date)
|
||||||
{
|
{
|
||||||
return (await GetCandles(account, ticker, date, Timeframe.OneMinute, true)).Last().Close;
|
var candles = await GetCandles(account, ticker, date, Timeframe.OneMinute, true);
|
||||||
|
|
||||||
|
if (candles == null || !candles.Any())
|
||||||
|
{
|
||||||
|
_logger.LogError($"No candles available for ticker {ticker} at date {date:yyyy-MM-dd HH:mm:ss}. This could indicate a data source issue or invalid ticker.");
|
||||||
|
throw new InvalidOperationException($"Cannot get price for {ticker} - no candle data available for the requested date {date:yyyy-MM-dd HH:mm:ss}. Please check if the ticker is valid and data source is accessible.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return candles.Last().Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current/latest price from the broker for live trading purposes.
|
||||||
|
/// This method is optimized for getting real-time prices and handles empty data gracefully.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="account">The trading account</param>
|
||||||
|
/// <param name="ticker">The ticker symbol</param>
|
||||||
|
/// <returns>The current price from the broker</returns>
|
||||||
|
public override async Task<decimal> GetCurrentPrice(Account account, Ticker ticker)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to get the most recent candle data (last 5 minutes)
|
||||||
|
var recentDate = DateTime.UtcNow.AddMinutes(-5);
|
||||||
|
var candles = await GetCandles(account, ticker, recentDate, Timeframe.OneMinute, true);
|
||||||
|
|
||||||
|
if (candles != null && candles.Any())
|
||||||
|
{
|
||||||
|
var latestCandle = candles.OrderByDescending(c => c.Date).First();
|
||||||
|
_logger.LogDebug($"Retrieved current price {latestCandle.Close} for {ticker} from candle data");
|
||||||
|
return latestCandle.Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Try to get price from a broader time range
|
||||||
|
var fallbackDate = DateTime.UtcNow.AddHours(-1);
|
||||||
|
var fallbackCandles = await GetCandles(account, ticker, fallbackDate, Timeframe.OneMinute, true);
|
||||||
|
|
||||||
|
if (fallbackCandles != null && fallbackCandles.Any())
|
||||||
|
{
|
||||||
|
var latestFallbackCandle = fallbackCandles.OrderByDescending(c => c.Date).First();
|
||||||
|
_logger.LogWarning($"Using fallback price {latestFallbackCandle.Close} for {ticker} from 1-hour old data");
|
||||||
|
return latestFallbackCandle.Close;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all else fails, throw a descriptive error
|
||||||
|
_logger.LogError($"No price data available for {ticker} from any source");
|
||||||
|
throw new InvalidOperationException($"Cannot get current price for {ticker} - no price data available from broker. Please check if the ticker is valid and the broker is accessible.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Error getting current price for {ticker}");
|
||||||
|
throw new InvalidOperationException($"Failed to get current price for {ticker}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<decimal> GetQuantityInPosition(Account account, Ticker ticker)
|
public override async Task<decimal> GetQuantityInPosition(Account account, Ticker ticker)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Managing.Domain.Evm;
|
|||||||
using Managing.Infrastructure.Evm;
|
using Managing.Infrastructure.Evm;
|
||||||
using Managing.Infrastructure.Evm.Abstractions;
|
using Managing.Infrastructure.Evm.Abstractions;
|
||||||
using Managing.Infrastructure.Evm.Services;
|
using Managing.Infrastructure.Evm.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Moq;
|
using Moq;
|
||||||
@@ -299,6 +300,7 @@ public class EvmManagerTests
|
|||||||
{
|
{
|
||||||
var settings = new Web3ProxySettings { BaseUrl = baseUrl };
|
var settings = new Web3ProxySettings { BaseUrl = baseUrl };
|
||||||
var options = Options.Create(settings);
|
var options = Options.Create(settings);
|
||||||
return new Web3ProxyService(options);
|
var logger = new Mock<ILogger<Web3ProxyService>>().Object;
|
||||||
|
return new Web3ProxyService(options, logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,12 +441,27 @@ public class EvmManager : IEvmManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gmxPrices == null)
|
if (gmxPrices == null)
|
||||||
return null;
|
{
|
||||||
|
Console.WriteLine($"Warning: GMX API returned null for ticker {ticker}, timeframe {timeframe}, startDate {startDate:yyyy-MM-dd HH:mm:ss}");
|
||||||
|
return new List<Candle>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gmxPrices.Candles == null || !gmxPrices.Candles.Any())
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Warning: GMX API returned empty candles array for ticker {ticker}, timeframe {timeframe}, startDate {startDate:yyyy-MM-dd HH:mm:ss}");
|
||||||
|
return new List<Candle>();
|
||||||
|
}
|
||||||
|
|
||||||
var filteredCandles = gmxPrices.Candles.Where(p => p[0] >= startDate.AddMinutes(-1).ToUnixTimestamp()).ToList();
|
var filteredCandles = gmxPrices.Candles.Where(p => p[0] >= startDate.AddMinutes(-1).ToUnixTimestamp()).ToList();
|
||||||
|
|
||||||
|
if (!filteredCandles.Any())
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Warning: No candles found after filtering for ticker {ticker}, timeframe {timeframe}, startDate {startDate:yyyy-MM-dd HH:mm:ss}. Total candles before filtering: {gmxPrices.Candles.Count}");
|
||||||
|
return new List<Candle>();
|
||||||
|
}
|
||||||
|
|
||||||
var candles = new List<Candle>();
|
var candles = new List<Candle>();
|
||||||
var timeBetweenCandles =
|
var timeBetweenCandles = CandleHelpers.GetBaseIntervalInSeconds(timeframe);
|
||||||
gmxPrices.Candles.Count > 2 ? gmxPrices.Candles[0][0] - gmxPrices.Candles[1][0] : 900; // Default 15 minutes
|
|
||||||
|
|
||||||
for (int i = 0; i < filteredCandles.Count; i++)
|
for (int i = 0; i < filteredCandles.Count; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user