using NodaTime; using YahooQuotesApi; using AssetManager.Data; using AssetManager.Models.DTOs; using Microsoft.Extensions.Logging; using System.Collections.Immutable; namespace AssetManager.Infrastructure.Services; /// /// Yahoo财经市场数据服务接口 /// public interface IYahooMarketService { /// /// 获取股票实时价格 /// Task GetStockPriceAsync(string symbol); /// /// 获取股票历史K线数据 /// Task> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit); } /// /// Yahoo财经市场数据服务实现 /// public class YahooMarketService : IYahooMarketService { private readonly ILogger _logger; private readonly YahooQuotes _yahooQuotes; public YahooMarketService(ILogger logger) { _logger = logger; // 默认获取最近60天的历史数据 _yahooQuotes = new YahooQuotesBuilder() .WithHistoryStartDate(Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(-60))) .Build(); } public async Task GetStockPriceAsync(string symbol) { _logger.LogInformation("Yahoo获取股票价格: {Symbol}", symbol); Snapshot? snapshot = await _yahooQuotes.GetSnapshotAsync(symbol); 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" }; } public async Task> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit) { _logger.LogInformation("Yahoo获取历史数据: {Symbol}, {Timeframe}, {Limit}", symbol, timeframe, limit); Result result = await _yahooQuotes.GetHistoryAsync(symbol); 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) { 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} 条", symbol, sortedData.Count); return sortedData; } }