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;
}
}