添加IMarketDataService接口及实现,支持获取股票和加密货币的实时价格及历史数据 添加MarketDTO定义市场价格和历史数据结构 添加Alpaca.Markets和Microsoft.Extensions.Logging.Abstractions依赖包 在Program.cs中注册MarketDataService服务
225 lines
7.5 KiB
C#
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)
|
|
};
|
|
}
|
|
} |