using System.Net.Http.Json;
using AssetManager.Models.DTOs;
using Microsoft.Extensions.Logging;
namespace AssetManager.Infrastructure.Services;
///
/// OKX 加密货币市场数据服务接口
///
public interface IOkxMarketService
{
///
/// 获取加密货币实时价格
///
Task GetCryptoPriceAsync(string symbol);
///
/// 获取加密货币历史K线数据
///
Task> GetCryptoHistoricalDataAsync(string symbol, string timeframe, int limit);
}
///
/// OKX 加密货币市场数据服务实现
///
public class OkxMarketService : IOkxMarketService
{
private readonly ILogger _logger;
private readonly HttpClient _httpClient;
public OkxMarketService(ILogger logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClient = httpClientFactory.CreateClient();
}
public async Task 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(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> 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(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();
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? 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? data { get; set; }
}