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

127 lines
4.4 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();
}
/// <summary>
/// 转换股票代码为 Yahoo Finance 格式
/// <para>BRK.B → BRK-B伯克希尔B类股</para>
/// <para>BRK.A → BRK-A伯克希尔A类股</para>
/// </summary>
private string ConvertToYahooSymbol(string symbol)
{
if (string.IsNullOrEmpty(symbol))
return symbol;
// Yahoo Finance 用连字符表示类别股,而非点号
// BRK.B → BRK-B, BRK.A → BRK-A
return symbol.ToUpper().Replace(".", "-");
}
public async Task<MarketPriceResponse> GetStockPriceAsync(string symbol)
{
var yahooSymbol = ConvertToYahooSymbol(symbol);
_logger.LogInformation("Yahoo获取股票价格: {Symbol} -> {YahooSymbol}", symbol, yahooSymbol);
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"
};
}
public async Task<List<MarketDataResponse>> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit)
{
var yahooSymbol = ConvertToYahooSymbol(symbol);
_logger.LogInformation("Yahoo获取历史数据: {Symbol} -> {YahooSymbol}, {Timeframe}, {Limit}", symbol, yahooSymbol, timeframe, limit);
Result<History> 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<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;
}
}