AssetManager.API/AssetManager.Infrastructure/Services/YahooMarketService.cs

110 lines
3.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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