110 lines
3.7 KiB
C#
110 lines
3.7 KiB
C#
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;
|
||
}
|
||
} |