diff --git a/AssetManager.API/Program.cs b/AssetManager.API/Program.cs index d6c98da..b5dd482 100644 --- a/AssetManager.API/Program.cs +++ b/AssetManager.API/Program.cs @@ -68,6 +68,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Logging.ClearProviders(); builder.Logging.AddConsole(); diff --git a/AssetManager.Infrastructure/AssetManager.Infrastructure.csproj b/AssetManager.Infrastructure/AssetManager.Infrastructure.csproj index e9b146f..cd480e1 100644 --- a/AssetManager.Infrastructure/AssetManager.Infrastructure.csproj +++ b/AssetManager.Infrastructure/AssetManager.Infrastructure.csproj @@ -4,6 +4,11 @@ + + + + + net8.0 enable diff --git a/AssetManager.Infrastructure/Services/IMarketDataService.cs b/AssetManager.Infrastructure/Services/IMarketDataService.cs new file mode 100644 index 0000000..4681220 --- /dev/null +++ b/AssetManager.Infrastructure/Services/IMarketDataService.cs @@ -0,0 +1,41 @@ +using AssetManager.Models.DTOs; + +namespace AssetManager.Infrastructure.Services; + +/// +/// 市场数据服务接口 +/// +public interface IMarketDataService +{ + /// + /// 获取股票实时价格 + /// + /// 股票代码 + /// 股票价格信息 + Task GetStockPriceAsync(string symbol); + + /// + /// 获取加密货币实时价格 + /// + /// 加密货币代码 + /// 加密货币价格信息 + Task GetCryptoPriceAsync(string symbol); + + /// + /// 获取股票历史数据 + /// + /// 股票代码 + /// 时间周期 + /// 数据点数量 + /// 历史数据列表 + Task> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit); + + /// + /// 获取加密货币历史数据 + /// + /// 加密货币代码 + /// 时间周期 + /// 数据点数量 + /// 历史数据列表 + Task> GetCryptoHistoricalDataAsync(string symbol, string timeframe, int limit); +} \ No newline at end of file diff --git a/AssetManager.Infrastructure/Services/MarketDataService.cs b/AssetManager.Infrastructure/Services/MarketDataService.cs new file mode 100644 index 0000000..132dbeb --- /dev/null +++ b/AssetManager.Infrastructure/Services/MarketDataService.cs @@ -0,0 +1,225 @@ +using Alpaca.Markets; +using AssetManager.Models.DTOs; +using Microsoft.Extensions.Logging; + +namespace AssetManager.Infrastructure.Services; + +/// +/// 市场数据服务实现 +/// +public class MarketDataService : IMarketDataService +{ + private readonly ILogger _logger; + private readonly IAlpacaDataClient _dataClient; + + /// + /// 构造函数 + /// + /// 日志记录器 + public MarketDataService(ILogger 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); + } + + /// + /// 获取股票实时价格 + /// + /// 股票代码 + /// 股票价格信息 + public async Task 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; + } + } + + /// + /// 获取加密货币实时价格 + /// + /// 加密货币代码 + /// 加密货币价格信息 + public async Task 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; + } + } + + /// + /// 获取股票历史数据 + /// + /// 股票代码 + /// 时间周期 + /// 数据点数量 + /// 历史数据列表 + public async Task> 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(); + 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; + } + } + + /// + /// 获取加密货币历史数据 + /// + /// 加密货币代码 + /// 时间周期 + /// 数据点数量 + /// 历史数据列表 + public async Task> 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(); + 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; + } + } + + /// + /// 转换时间周期 + /// + /// 时间周期字符串 + /// BarTimeFrame 对象 + 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 + }; + } + + /// + /// 计算开始日期 + /// + /// 结束日期 + /// 时间周期 + /// 数据点数量 + /// 开始日期 + 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) + }; + } +} \ No newline at end of file diff --git a/AssetManager.Models/DTOs/MarketDTO.cs b/AssetManager.Models/DTOs/MarketDTO.cs new file mode 100644 index 0000000..413fd32 --- /dev/null +++ b/AssetManager.Models/DTOs/MarketDTO.cs @@ -0,0 +1,73 @@ +namespace AssetManager.Models.DTOs; + +/// +/// 市场价格响应 +/// +public class MarketPriceResponse +{ + /// + /// 标的代码 + /// + public string Symbol { get; set; } + + /// + /// 价格 + /// + public decimal Price { get; set; } + + /// + /// 时间戳 + /// + public DateTime Timestamp { get; set; } + + /// + /// 资产类型 + /// + public string AssetType { get; set; } +} + +/// +/// 市场数据响应 +/// +public class MarketDataResponse +{ + /// + /// 标的代码 + /// + public string Symbol { get; set; } + + /// + /// 时间戳 + /// + public DateTime Timestamp { get; set; } + + /// + /// 开盘价 + /// + public decimal Open { get; set; } + + /// + /// 最高价 + /// + public decimal High { get; set; } + + /// + /// 最低价 + /// + public decimal Low { get; set; } + + /// + /// 收盘价 + /// + public decimal Close { get; set; } + + /// + /// 成交量 + /// + public decimal Volume { get; set; } + + /// + /// 资产类型 + /// + public string AssetType { get; set; } +} \ No newline at end of file