AssetManager.API/AssetManager.Infrastructure/Services/OkxMarketService.cs
OpenClaw Agent 4ce29a1036 refactor: 架构优化 P0-P3
P0 - 安全修复:
- 移除硬编码 API Key,启动时校验必填环境变量

P1 - 高优先级:
- Entity 拆分:Position.cs, Transaction.cs 独立文件
- Controller Facade 封装:IPortfolioFacade 减少依赖注入

P2 - 中优先级:
- Repository 抽象:IPortfolioRepository, IMarketDataRepository
- MarketDataService 拆分:组合模式整合 Tencent/Tiingo/OKX

P3 - 低优先级:
- DTO 命名规范:统一 PascalCase
- 单元测试框架:xUnit + Moq + FluentAssertions
2026-03-15 12:54:05 +00:00

145 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Net.Http.Json;
using AssetManager.Models.DTOs;
using Microsoft.Extensions.Logging;
namespace AssetManager.Infrastructure.Services;
/// <summary>
/// OKX 加密货币市场数据服务接口
/// </summary>
public interface IOkxMarketService
{
/// <summary>
/// 获取加密货币实时价格
/// </summary>
Task<MarketPriceResponse> GetCryptoPriceAsync(string symbol);
/// <summary>
/// 获取加密货币历史K线数据
/// </summary>
Task<List<MarketDataResponse>> GetCryptoHistoricalDataAsync(string symbol, string timeframe, int limit);
}
/// <summary>
/// OKX 加密货币市场数据服务实现
/// </summary>
public class OkxMarketService : IOkxMarketService
{
private readonly ILogger<OkxMarketService> _logger;
private readonly HttpClient _httpClient;
public OkxMarketService(ILogger<OkxMarketService> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClient = httpClientFactory.CreateClient();
}
public async Task<MarketPriceResponse> GetCryptoPriceAsync(string symbol)
{
_logger.LogInformation("OKX 获取加密货币价格: {Symbol}", symbol);
// OKX 实时 ticker 接口symbol 格式如 BTC-USDT
var url = $"https://www.okx.com/api/v5/market/ticker?instId={symbol}";
var response = await _httpClient.GetFromJsonAsync<OkxTickerResponse>(url);
if (response == null || response.code != "0" || response.data == null || response.data.Count == 0)
{
throw new Exception($"No data found for {symbol}, code: {response?.code}, msg: {response?.msg}");
}
var latest = response.data[0];
return new MarketPriceResponse
{
Symbol = symbol,
Price = decimal.TryParse(latest.last, out var price) ? price : 0,
PreviousClose = decimal.TryParse(latest.sodUtc0, out var prevClose) ? prevClose : 0,
Timestamp = DateTime.UtcNow,
AssetType = "Crypto"
};
}
public async Task<List<MarketDataResponse>> GetCryptoHistoricalDataAsync(string symbol, string timeframe, int limit)
{
_logger.LogInformation("OKX 获取历史数据: {Symbol}, 周期: {Timeframe}, 数量: {Limit}", symbol, timeframe, limit);
// 转换 timeframe 为 OKX 支持的粒度
var bar = ConvertToOkxBar(timeframe);
// OKX K线接口
var url = $"https://www.okx.com/api/v5/market/candles?instId={symbol}&bar={bar}&limit={limit}";
var response = await _httpClient.GetFromJsonAsync<OkxCandlesResponse>(url);
if (response == null || response.code != "0" || response.data == null)
{
throw new Exception($"No data found for {symbol}, code: {response?.code}, msg: {response?.msg}");
}
var result = new List<MarketDataResponse>();
foreach (var item in response.data)
{
if (item.Length < 6) continue;
if (long.TryParse(item[0], out var ts) &&
decimal.TryParse(item[1], out var open) &&
decimal.TryParse(item[2], out var high) &&
decimal.TryParse(item[3], out var low) &&
decimal.TryParse(item[4], out var close) &&
decimal.TryParse(item[5], out var volume))
{
result.Add(new MarketDataResponse
{
Symbol = symbol,
Open = open,
High = high,
Low = low,
Close = close,
Volume = volume,
Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(ts).UtcDateTime,
AssetType = "Crypto"
});
}
}
// 按时间升序排列
return result.OrderBy(x => x.Timestamp).ToList();
}
private string ConvertToOkxBar(string timeframe)
{
return timeframe.ToLower() switch
{
"1min" => "1m",
"5min" => "5m",
"15min" => "15m",
"1h" => "1H",
"4h" => "4H",
"1d" => "1D",
"1w" => "1W",
"1m" => "1M",
_ => "1D"
};
}
}
// OKX 响应模型
internal class OkxTickerResponse
{
public string code { get; set; }
public string msg { get; set; }
public List<OkxTickerData> data { get; set; }
}
internal class OkxTickerData
{
public string instId { get; set; }
public string last { get; set; }
public string sodUtc0 { get; set; }
}
internal class OkxCandlesResponse
{
public string code { get; set; }
public string msg { get; set; }
public List<string[]> data { get; set; }
}