AssetManager.API/AssetManager.Infrastructure/Services/MarketDataService.cs
niannian zheng 8e75b894ad feat(市场数据): 添加Alpaca市场数据服务实现
添加IMarketDataService接口及实现,支持获取股票和加密货币的实时价格及历史数据
添加MarketDTO定义市场价格和历史数据结构
添加Alpaca.Markets和Microsoft.Extensions.Logging.Abstractions依赖包
在Program.cs中注册MarketDataService服务
2026-02-28 18:49:05 +08:00

225 lines
7.5 KiB
C#

using Alpaca.Markets;
using AssetManager.Models.DTOs;
using Microsoft.Extensions.Logging;
namespace AssetManager.Infrastructure.Services;
/// <summary>
/// 市场数据服务实现
/// </summary>
public class MarketDataService : IMarketDataService
{
private readonly ILogger<MarketDataService> _logger;
private readonly IAlpacaDataClient _dataClient;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logger">日志记录器</param>
public MarketDataService(ILogger<MarketDataService> logger)
{
_logger = logger;
// 初始化 Alpaca 客户端 (7.2 版本)
var secretKey = new SecretKey("YOUR_API_KEY", "YOUR_SECRET_KEY");
// 使用 Paper Trading 环境进行测试,生产环境请使用 Environments.Live
_dataClient = Environments.Paper.GetAlpacaDataClient(secretKey);
}
/// <summary>
/// 获取股票实时价格
/// </summary>
/// <param name="symbol">股票代码</param>
/// <returns>股票价格信息</returns>
public async Task<MarketPriceResponse> GetStockPriceAsync(string symbol)
{
try
{
_logger.LogInformation($"Requesting stock price for symbol: {symbol}");
var request = new LatestMarketDataRequest(symbol);
var latestTrade = await _dataClient.GetLatestTradeAsync(request);
return new MarketPriceResponse
{
Symbol = symbol,
Price = latestTrade.Price,
Timestamp = latestTrade.TimestampUtc,
AssetType = "Stock"
};
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting stock price for {symbol}");
throw;
}
}
/// <summary>
/// 获取加密货币实时价格
/// </summary>
/// <param name="symbol">加密货币代码</param>
/// <returns>加密货币价格信息</returns>
public async Task<MarketPriceResponse> GetCryptoPriceAsync(string symbol)
{
try
{
_logger.LogInformation($"Requesting crypto price for symbol: {symbol}");
var request = new LatestMarketDataRequest(symbol);
var latestTrade = await _dataClient.GetLatestTradeAsync(request);
return new MarketPriceResponse
{
Symbol = symbol,
Price = latestTrade.Price,
Timestamp = latestTrade.TimestampUtc,
AssetType = "Crypto"
};
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting crypto price for {symbol}");
throw;
}
}
/// <summary>
/// 获取股票历史数据
/// </summary>
/// <param name="symbol">股票代码</param>
/// <param name="timeframe">时间周期</param>
/// <param name="limit">数据点数量</param>
/// <returns>历史数据列表</returns>
public async Task<List<MarketDataResponse>> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit)
{
try
{
_logger.LogInformation($"Requesting stock historical data for symbol: {symbol}, timeframe: {timeframe}, limit: {limit}");
var barTimeFrame = GetBarTimeFrame(timeframe);
var endDate = DateTime.UtcNow;
var startDate = CalculateStartDate(endDate, timeframe, limit);
var request = new HistoricalBarsRequest(symbol, startDate, endDate, barTimeFrame);
var barsPage = await _dataClient.GetHistoricalBarsAsync(request);
var result = new List<MarketDataResponse>();
foreach (var kvp in barsPage.Items)
{
foreach (var bar in kvp.Value)
{
result.Add(new MarketDataResponse
{
Symbol = symbol,
Open = bar.Open,
High = bar.High,
Low = bar.Low,
Close = bar.Close,
Volume = bar.Volume,
Timestamp = bar.TimeUtc,
AssetType = "Stock"
});
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting stock historical data for {symbol}");
throw;
}
}
/// <summary>
/// 获取加密货币历史数据
/// </summary>
/// <param name="symbol">加密货币代码</param>
/// <param name="timeframe">时间周期</param>
/// <param name="limit">数据点数量</param>
/// <returns>历史数据列表</returns>
public async Task<List<MarketDataResponse>> GetCryptoHistoricalDataAsync(string symbol, string timeframe, int limit)
{
try
{
_logger.LogInformation($"Requesting crypto historical data for symbol: {symbol}, timeframe: {timeframe}, limit: {limit}");
var barTimeFrame = GetBarTimeFrame(timeframe);
var endDate = DateTime.UtcNow;
var startDate = CalculateStartDate(endDate, timeframe, limit);
var request = new HistoricalBarsRequest(symbol, startDate, endDate, barTimeFrame);
var barsPage = await _dataClient.GetHistoricalBarsAsync(request);
var result = new List<MarketDataResponse>();
foreach (var kvp in barsPage.Items)
{
foreach (var bar in kvp.Value)
{
result.Add(new MarketDataResponse
{
Symbol = symbol,
Open = bar.Open,
High = bar.High,
Low = bar.Low,
Close = bar.Close,
Volume = bar.Volume,
Timestamp = bar.TimeUtc,
AssetType = "Crypto"
});
}
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting crypto historical data for {symbol}");
throw;
}
}
/// <summary>
/// 转换时间周期
/// </summary>
/// <param name="timeframe">时间周期字符串</param>
/// <returns>BarTimeFrame 对象</returns>
private BarTimeFrame GetBarTimeFrame(string timeframe)
{
return timeframe.ToLower() switch
{
"1min" => BarTimeFrame.Minute,
"5min" => BarTimeFrame.Minute,
"15min" => BarTimeFrame.Minute,
"1h" => BarTimeFrame.Hour,
"1d" => BarTimeFrame.Day,
"1w" => BarTimeFrame.Week,
"1m" => BarTimeFrame.Month,
_ => BarTimeFrame.Day
};
}
/// <summary>
/// 计算开始日期
/// </summary>
/// <param name="endDate">结束日期</param>
/// <param name="timeframe">时间周期</param>
/// <param name="limit">数据点数量</param>
/// <returns>开始日期</returns>
private DateTime CalculateStartDate(DateTime endDate, string timeframe, int limit)
{
return timeframe.ToLower() switch
{
"1min" => endDate.AddMinutes(-limit),
"5min" => endDate.AddMinutes(-limit * 5),
"15min" => endDate.AddMinutes(-limit * 15),
"1h" => endDate.AddHours(-limit),
"1d" => endDate.AddDays(-limit),
"1w" => endDate.AddDays(-limit * 7),
"1m" => endDate.AddMonths(-limit),
_ => endDate.AddDays(-limit)
};
}
}