feat(市场数据): 添加Alpaca市场数据服务实现
添加IMarketDataService接口及实现,支持获取股票和加密货币的实时价格及历史数据 添加MarketDTO定义市场价格和历史数据结构 添加Alpaca.Markets和Microsoft.Extensions.Logging.Abstractions依赖包 在Program.cs中注册MarketDataService服务
This commit is contained in:
parent
09bd91e686
commit
8e75b894ad
@ -68,6 +68,7 @@ builder.Services.AddScoped<AssetManager.Services.Services.WechatService>();
|
|||||||
builder.Services.AddScoped<AssetManager.Services.Services.JwtService>();
|
builder.Services.AddScoped<AssetManager.Services.Services.JwtService>();
|
||||||
builder.Services.AddScoped<AssetManager.Services.IPortfolioService, AssetManager.Services.PortfolioService>();
|
builder.Services.AddScoped<AssetManager.Services.IPortfolioService, AssetManager.Services.PortfolioService>();
|
||||||
builder.Services.AddScoped<AssetManager.Services.IStrategyService, AssetManager.Services.StrategyService>();
|
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.ClearProviders();
|
||||||
builder.Logging.AddConsole();
|
builder.Logging.AddConsole();
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
<ProjectReference Include="..\AssetManager.Models\AssetManager.Models.csproj" />
|
<ProjectReference Include="..\AssetManager.Models\AssetManager.Models.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Alpaca.Markets" Version="7.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|||||||
41
AssetManager.Infrastructure/Services/IMarketDataService.cs
Normal file
41
AssetManager.Infrastructure/Services/IMarketDataService.cs
Normal 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);
|
||||||
|
}
|
||||||
225
AssetManager.Infrastructure/Services/MarketDataService.cs
Normal file
225
AssetManager.Infrastructure/Services/MarketDataService.cs
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
73
AssetManager.Models/DTOs/MarketDTO.cs
Normal file
73
AssetManager.Models/DTOs/MarketDTO.cs
Normal 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; }
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user