fix: 复用YahooQuotes实例,添加并发限制防止429错误
This commit is contained in:
parent
2edac30fd8
commit
b39044bfe1
@ -25,14 +25,23 @@ public interface IYahooMarketService
|
|||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Yahoo财经市场数据服务实现
|
/// Yahoo财经市场数据服务实现
|
||||||
|
/// <para>⚠️ Yahoo API 有频率限制,频繁请求会触发 429 错误</para>
|
||||||
|
/// <para>建议:依赖缓存机制减少 API 调用</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class YahooMarketService : IYahooMarketService
|
public class YahooMarketService : IYahooMarketService
|
||||||
{
|
{
|
||||||
private readonly ILogger<YahooMarketService> _logger;
|
private readonly ILogger<YahooMarketService> _logger;
|
||||||
|
private readonly YahooQuotes _yahooQuotes;
|
||||||
|
private readonly SemaphoreSlim _semaphore = new(1, 1); // 限制并发请求
|
||||||
|
|
||||||
public YahooMarketService(ILogger<YahooMarketService> logger)
|
public YahooMarketService(ILogger<YahooMarketService> logger)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
// 复用单个实例,设置足够长的历史范围(2年)
|
||||||
|
// YahooQuotes 内部会缓存 cookie/crumb,避免频繁请求
|
||||||
|
_yahooQuotes = new YahooQuotesBuilder()
|
||||||
|
.WithHistoryStartDate(Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(-730)))
|
||||||
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -50,34 +59,16 @@ public class YahooMarketService : IYahooMarketService
|
|||||||
return symbol.ToUpper().Replace(".", "-");
|
return symbol.ToUpper().Replace(".", "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 根据 timeframe 和 limit 计算需要的开始日期
|
|
||||||
/// </summary>
|
|
||||||
private DateTime CalculateStartDate(string timeframe, int limit)
|
|
||||||
{
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
|
|
||||||
// 根据周期类型计算需要的天数,预留缓冲
|
|
||||||
return timeframe.ToLower() switch
|
|
||||||
{
|
|
||||||
"1d" or "daily" or "day" => now.AddDays(-limit * 1.5), // 日线:limit * 1.5 天
|
|
||||||
"1w" or "weekly" or "week" => now.AddDays(-limit * 8), // 周线:limit * 8 天
|
|
||||||
"1m" or "monthly" or "month" => now.AddDays(-limit * 35), // 月线:limit * 35 天
|
|
||||||
_ => now.AddDays(-Math.Max(limit * 2, 365)) // 默认:至少365天
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<MarketPriceResponse> GetStockPriceAsync(string symbol)
|
public async Task<MarketPriceResponse> GetStockPriceAsync(string symbol)
|
||||||
{
|
{
|
||||||
var yahooSymbol = ConvertToYahooSymbol(symbol);
|
var yahooSymbol = ConvertToYahooSymbol(symbol);
|
||||||
_logger.LogInformation("Yahoo获取股票价格: {Symbol} -> {YahooSymbol}", symbol, yahooSymbol);
|
_logger.LogInformation("Yahoo获取股票价格: {Symbol} -> {YahooSymbol}", symbol, yahooSymbol);
|
||||||
|
|
||||||
// 实时价格不需要历史数据,使用最小范围
|
// 限制并发,避免触发 429
|
||||||
var yahooQuotes = new YahooQuotesBuilder()
|
await _semaphore.WaitAsync();
|
||||||
.WithHistoryStartDate(Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(-7)))
|
try
|
||||||
.Build();
|
{
|
||||||
|
Snapshot? snapshot = await _yahooQuotes.GetSnapshotAsync(yahooSymbol);
|
||||||
Snapshot? snapshot = await yahooQuotes.GetSnapshotAsync(yahooSymbol);
|
|
||||||
if (snapshot is null)
|
if (snapshot is null)
|
||||||
throw new InvalidOperationException($"Yahoo未知标的: {symbol}");
|
throw new InvalidOperationException($"Yahoo未知标的: {symbol}");
|
||||||
|
|
||||||
@ -94,28 +85,29 @@ public class YahooMarketService : IYahooMarketService
|
|||||||
|
|
||||||
return new MarketPriceResponse
|
return new MarketPriceResponse
|
||||||
{
|
{
|
||||||
Symbol = symbol, // 返回原始代码
|
Symbol = symbol,
|
||||||
Price = price,
|
Price = price,
|
||||||
PreviousClose = previousClose,
|
PreviousClose = previousClose,
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
AssetType = "Stock"
|
AssetType = "Stock"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_semaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<MarketDataResponse>> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit)
|
public async Task<List<MarketDataResponse>> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit)
|
||||||
{
|
{
|
||||||
var yahooSymbol = ConvertToYahooSymbol(symbol);
|
var yahooSymbol = ConvertToYahooSymbol(symbol);
|
||||||
_logger.LogInformation("Yahoo获取历史数据: {Symbol} -> {YahooSymbol}, {Timeframe}, {Limit}", symbol, yahooSymbol, timeframe, limit);
|
_logger.LogInformation("Yahoo获取历史数据: {Symbol} -> {YahooSymbol}, {Timeframe}, {Limit}", symbol, yahooSymbol, timeframe, limit);
|
||||||
|
|
||||||
// 根据请求参数动态计算开始日期
|
// 限制并发,避免触发 429
|
||||||
var startDate = CalculateStartDate(timeframe, limit);
|
await _semaphore.WaitAsync();
|
||||||
_logger.LogDebug("历史数据开始日期: {StartDate}", startDate.ToString("yyyy-MM-dd"));
|
try
|
||||||
|
{
|
||||||
var yahooQuotes = new YahooQuotesBuilder()
|
Result<History> result = await _yahooQuotes.GetHistoryAsync(yahooSymbol);
|
||||||
.WithHistoryStartDate(Instant.FromDateTimeUtc(startDate))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
Result<History> result = await yahooQuotes.GetHistoryAsync(yahooSymbol);
|
|
||||||
if (result.Value == null)
|
if (result.Value == null)
|
||||||
throw new InvalidOperationException($"Yahoo获取历史数据失败,标的: {symbol}");
|
throw new InvalidOperationException($"Yahoo获取历史数据失败,标的: {symbol}");
|
||||||
|
|
||||||
@ -128,7 +120,7 @@ public class YahooMarketService : IYahooMarketService
|
|||||||
{
|
{
|
||||||
marketDataList.Add(new MarketDataResponse
|
marketDataList.Add(new MarketDataResponse
|
||||||
{
|
{
|
||||||
Symbol = symbol, // 返回原始代码
|
Symbol = symbol,
|
||||||
Timestamp = tick.Date.ToDateTimeUtc(),
|
Timestamp = tick.Date.ToDateTimeUtc(),
|
||||||
Open = (decimal)tick.Open,
|
Open = (decimal)tick.Open,
|
||||||
High = (decimal)tick.High,
|
High = (decimal)tick.High,
|
||||||
@ -149,4 +141,9 @@ public class YahooMarketService : IYahooMarketService
|
|||||||
|
|
||||||
return sortedData;
|
return sortedData;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_semaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user