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; } }