feat(市场数据): 添加Alpaca市场数据服务实现

添加IMarketDataService接口及实现,支持获取股票和加密货币的实时价格及历史数据
添加MarketDTO定义市场价格和历史数据结构
添加Alpaca.Markets和Microsoft.Extensions.Logging.Abstractions依赖包
在Program.cs中注册MarketDataService服务
This commit is contained in:
niannian zheng 2026-02-28 18:49:05 +08:00
parent 09bd91e686
commit 8e75b894ad
5 changed files with 345 additions and 0 deletions

View File

@ -68,6 +68,7 @@ builder.Services.AddScoped<AssetManager.Services.Services.WechatService>();
builder.Services.AddScoped<AssetManager.Services.Services.JwtService>();
builder.Services.AddScoped<AssetManager.Services.IPortfolioService, AssetManager.Services.PortfolioService>();
builder.Services.AddScoped<AssetManager.Services.IStrategyService, AssetManager.Services.StrategyService>();
builder.Services.AddScoped<AssetManager.Infrastructure.Services.IMarketDataService, AssetManager.Infrastructure.Services.MarketDataService>();
builder.Logging.ClearProviders();
builder.Logging.AddConsole();

View File

@ -4,6 +4,11 @@
<ProjectReference Include="..\AssetManager.Models\AssetManager.Models.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Alpaca.Markets" Version="7.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>

View File

@ -0,0 +1,41 @@
using AssetManager.Models.DTOs;
namespace AssetManager.Infrastructure.Services;
/// <summary>
/// 市场数据服务接口
/// </summary>
public interface IMarketDataService
{
/// <summary>
/// 获取股票实时价格
/// </summary>
/// <param name="symbol">股票代码</param>
/// <returns>股票价格信息</returns>
Task<MarketPriceResponse> GetStockPriceAsync(string symbol);
/// <summary>
/// 获取加密货币实时价格
/// </summary>
/// <param name="symbol">加密货币代码</param>
/// <returns>加密货币价格信息</returns>
Task<MarketPriceResponse> GetCryptoPriceAsync(string symbol);
/// <summary>
/// 获取股票历史数据
/// </summary>
/// <param name="symbol">股票代码</param>
/// <param name="timeframe">时间周期</param>
/// <param name="limit">数据点数量</param>
/// <returns>历史数据列表</returns>
Task<List<MarketDataResponse>> GetStockHistoricalDataAsync(string symbol, string timeframe, int limit);
/// <summary>
/// 获取加密货币历史数据
/// </summary>
/// <param name="symbol">加密货币代码</param>
/// <param name="timeframe">时间周期</param>
/// <param name="limit">数据点数量</param>
/// <returns>历史数据列表</returns>
Task<List<MarketDataResponse>> GetCryptoHistoricalDataAsync(string symbol, string timeframe, int limit);
}

View File

@ -0,0 +1,225 @@
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)
};
}
}

View File

@ -0,0 +1,73 @@
namespace AssetManager.Models.DTOs;
/// <summary>
/// 市场价格响应
/// </summary>
public class MarketPriceResponse
{
/// <summary>
/// 标的代码
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// 价格
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// 时间戳
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// 资产类型
/// </summary>
public string AssetType { get; set; }
}
/// <summary>
/// 市场数据响应
/// </summary>
public class MarketDataResponse
{
/// <summary>
/// 标的代码
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// 时间戳
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// 开盘价
/// </summary>
public decimal Open { get; set; }
/// <summary>
/// 最高价
/// </summary>
public decimal High { get; set; }
/// <summary>
/// 最低价
/// </summary>
public decimal Low { get; set; }
/// <summary>
/// 收盘价
/// </summary>
public decimal Close { get; set; }
/// <summary>
/// 成交量
/// </summary>
public decimal Volume { get; set; }
/// <summary>
/// 资产类型
/// </summary>
public string AssetType { get; set; }
}