diff --git a/AssetManager.Infrastructure/Services/YahooMarketService.cs b/AssetManager.Infrastructure/Services/YahooMarketService.cs
index 2225d0c..c9f286d 100644
--- a/AssetManager.Infrastructure/Services/YahooMarketService.cs
+++ b/AssetManager.Infrastructure/Services/YahooMarketService.cs
@@ -25,14 +25,23 @@ public interface IYahooMarketService
}
///
/// Yahoo财经市场数据服务实现
+/// ⚠️ Yahoo API 有频率限制,频繁请求会触发 429 错误
+/// 建议:依赖缓存机制减少 API 调用
///
public class YahooMarketService : IYahooMarketService
{
private readonly ILogger _logger;
+ private readonly YahooQuotes _yahooQuotes;
+ private readonly SemaphoreSlim _semaphore = new(1, 1); // 限制并发请求
public YahooMarketService(ILogger logger)
{
_logger = logger;
+ // 复用单个实例,设置足够长的历史范围(2年)
+ // YahooQuotes 内部会缓存 cookie/crumb,避免频繁请求
+ _yahooQuotes = new YahooQuotesBuilder()
+ .WithHistoryStartDate(Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(-730)))
+ .Build();
}
///
@@ -50,56 +59,43 @@ public class YahooMarketService : IYahooMarketService
return symbol.ToUpper().Replace(".", "-");
}
- ///
- /// 根据 timeframe 和 limit 计算需要的开始日期
- ///
- 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 GetStockPriceAsync(string symbol)
{
var yahooSymbol = ConvertToYahooSymbol(symbol);
_logger.LogInformation("Yahoo获取股票价格: {Symbol} -> {YahooSymbol}", symbol, yahooSymbol);
- // 实时价格不需要历史数据,使用最小范围
- var yahooQuotes = new YahooQuotesBuilder()
- .WithHistoryStartDate(Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(-7)))
- .Build();
-
- Snapshot? snapshot = await yahooQuotes.GetSnapshotAsync(yahooSymbol);
- if (snapshot is null)
- throw new InvalidOperationException($"Yahoo未知标的: {symbol}");
-
- decimal price = snapshot.RegularMarketPrice;
- if (price <= 0)
- throw new InvalidOperationException($"Yahoo获取价格失败,标的: {symbol}");
-
- decimal previousClose = snapshot.RegularMarketPreviousClose;
- if (previousClose <= 0)
- previousClose = price;
-
- _logger.LogDebug("Yahoo接口返回 {Symbol}:最新价 {CurrentPrice},昨收价 {PrevClose}",
- symbol, price, previousClose);
-
- return new MarketPriceResponse
+ // 限制并发,避免触发 429
+ await _semaphore.WaitAsync();
+ try
{
- Symbol = symbol, // 返回原始代码
- Price = price,
- PreviousClose = previousClose,
- Timestamp = DateTime.UtcNow,
- AssetType = "Stock"
- };
+ Snapshot? snapshot = await _yahooQuotes.GetSnapshotAsync(yahooSymbol);
+ if (snapshot is null)
+ throw new InvalidOperationException($"Yahoo未知标的: {symbol}");
+
+ decimal price = snapshot.RegularMarketPrice;
+ if (price <= 0)
+ throw new InvalidOperationException($"Yahoo获取价格失败,标的: {symbol}");
+
+ decimal previousClose = snapshot.RegularMarketPreviousClose;
+ if (previousClose <= 0)
+ previousClose = price;
+
+ _logger.LogDebug("Yahoo接口返回 {Symbol}:最新价 {CurrentPrice},昨收价 {PrevClose}",
+ symbol, price, previousClose);
+
+ return new MarketPriceResponse
+ {
+ Symbol = symbol,
+ Price = price,
+ PreviousClose = previousClose,
+ Timestamp = DateTime.UtcNow,
+ AssetType = "Stock"
+ };
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
}
public async Task> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit)
@@ -107,46 +103,47 @@ public class YahooMarketService : IYahooMarketService
var yahooSymbol = ConvertToYahooSymbol(symbol);
_logger.LogInformation("Yahoo获取历史数据: {Symbol} -> {YahooSymbol}, {Timeframe}, {Limit}", symbol, yahooSymbol, timeframe, limit);
- // 根据请求参数动态计算开始日期
- var startDate = CalculateStartDate(timeframe, limit);
- _logger.LogDebug("历史数据开始日期: {StartDate}", startDate.ToString("yyyy-MM-dd"));
-
- var yahooQuotes = new YahooQuotesBuilder()
- .WithHistoryStartDate(Instant.FromDateTimeUtc(startDate))
- .Build();
-
- Result result = await yahooQuotes.GetHistoryAsync(yahooSymbol);
- if (result.Value == null)
- throw new InvalidOperationException($"Yahoo获取历史数据失败,标的: {symbol}");
-
- History history = result.Value;
- var ticks = history.Ticks;
-
- var marketDataList = new List();
-
- foreach (var tick in ticks)
+ // 限制并发,避免触发 429
+ await _semaphore.WaitAsync();
+ try
{
- marketDataList.Add(new MarketDataResponse
+ Result result = await _yahooQuotes.GetHistoryAsync(yahooSymbol);
+ if (result.Value == null)
+ throw new InvalidOperationException($"Yahoo获取历史数据失败,标的: {symbol}");
+
+ History history = result.Value;
+ var ticks = history.Ticks;
+
+ var marketDataList = new List();
+
+ foreach (var tick in ticks)
{
- Symbol = symbol, // 返回原始代码
- Timestamp = tick.Date.ToDateTimeUtc(),
- Open = (decimal)tick.Open,
- High = (decimal)tick.High,
- Low = (decimal)tick.Low,
- Close = (decimal)tick.Close,
- Volume = (decimal)tick.Volume,
- AssetType = "Stock"
- });
+ marketDataList.Add(new MarketDataResponse
+ {
+ Symbol = symbol,
+ Timestamp = tick.Date.ToDateTimeUtc(),
+ Open = (decimal)tick.Open,
+ High = (decimal)tick.High,
+ Low = (decimal)tick.Low,
+ Close = (decimal)tick.Close,
+ Volume = (decimal)tick.Volume,
+ AssetType = "Stock"
+ });
+ }
+
+ // 按时间戳排序并取最近的limit条
+ var sortedData = marketDataList
+ .OrderBy(x => x.Timestamp)
+ .TakeLast(limit)
+ .ToList();
+
+ _logger.LogInformation("Yahoo获取 {Symbol} 历史数据 {Count} 条(请求 {Limit} 条)", symbol, sortedData.Count, limit);
+
+ return sortedData;
+ }
+ finally
+ {
+ _semaphore.Release();
}
-
- // 按时间戳排序并取最近的limit条
- var sortedData = marketDataList
- .OrderBy(x => x.Timestamp)
- .TakeLast(limit)
- .ToList();
-
- _logger.LogInformation("Yahoo获取 {Symbol} 历史数据 {Count} 条(请求 {Limit} 条)", symbol, sortedData.Count, limit);
-
- return sortedData;
}
}
\ No newline at end of file