引言
对于C#开发者而言,无论是开发外汇分析软件、量化交易系统,还是为应用程序添加实时汇率功能,首要解决的都是如何稳定、高效地获取外汇行情数据。本文将以一个真实的API接口为例,手把手带您完成在C#环境中对接实时外汇数据流与历史K线数据的全过程。我们将涵盖WebSocket实时推送和HttpClient请求K线接口两大核心场景,并提供关键的代码示例。

一、 准备工作:环境与授权

  1. 获取API权限

    • 行情地址:http://39.107.99.235:1008/market (用于查询外汇产品代码,如 fx_eurusd)

  2. 创建C#项目
    创建一个新的.NET Console应用程序。本文将使用.NET 6+的顶级语句风格,但代码同样适用于其他版本的.NET Framework/Core。

  3. 引入NuGet包
    我们需要两个重要的NuGet包来简化开发。

    通过Visual Studio的NuGet包管理器或使用CLI命令安装:

    dotnet add package Newtonsoft.Json

    二、 实战一:使用WebSocket获取实时外汇行情

    实时数据是交易和即时分析的生命线。以下是实现的核心步骤。

    步骤1:建立WebSocket连接并发送心跳

    using System;
    using System.Net.WebSockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using Newtonsoft.Json.Linq;
    
    // 为保持连接稳定,通常会将WebSocket客户端封装在一个长期运行的后台服务中。
    class Program
    {
        private static ClientWebSocket _webSocket = new ClientWebSocket();
        private static readonly Uri _serverUri = new Uri("ws://39.107.99.235/ws");
        private static readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    
        static async Task Main(string[] args)
        {
            await ConnectAndListen();
        }
    
        static async Task ConnectAndListen()
        {
            try
            {
                // 1. 建立连接
                await _webSocket.ConnectAsync(_serverUri, _cancellationTokenSource.Token);
                Console.WriteLine("WebSocket连接已建立。");
    
                // 2. 启动后台任务发送心跳
                _ = Task.Run(async () => await SendHeartbeat(_cancellationTokenSource.Token));
    
                // 3. 订阅外汇产品 (例如:欧元美元、英镑美元)
                string subscribeMessage = "{\"Key\":\"fx_eurusd,fx_gbpusd\"}";
                byte[] subscribeBytes = Encoding.UTF8.GetBytes(subscribeMessage);
                await _webSocket.SendAsync(new ArraySegment<byte>(subscribeBytes), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
                Console.WriteLine($"已发送订阅请求: {subscribeMessage}");
    
                // 4. 启动监听循环,持续接收消息
                await StartListening();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"发生错误: {ex.Message}");
                // 在实际应用中,应在此实现重连逻辑。
            }
        }
    
        static async Task SendHeartbeat(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                try
                {
                    // 每隔10秒发送一次心跳
                    await Task.Delay(10000, token);
                    long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                    string heartbeatMessage = $"{{\"ping\":{timestamp}}}";
                    byte[] heartbeatBytes = Encoding.UTF8.GetBytes(heartbeatMessage);
                    if (_webSocket.State == WebSocketState.Open)
                    {
                        await _webSocket.SendAsync(new ArraySegment<byte>(heartbeatBytes), WebSocketMessageType.Text, true, token);
                        Console.WriteLine($"心跳已发送: {heartbeatMessage}");
                    }
                }
                catch (TaskCanceledException)
                {
                    break;
                }
            }
        }
    }

    步骤2:监听并解析实时数据

    将下面的StartListening方法添加到上述代码中。

    static async Task StartListening()
    {
        byte[] buffer = new byte[4096];
        while (_webSocket.State == WebSocketState.Open)
        {
            try
            {
                var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationTokenSource.Token);
                if (result.MessageType == WebSocketMessageType.Text)
                {
                    string receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);
                    // Console.WriteLine($"收到原始消息: {receivedMessage}"); // 调试用
    
                    // 解析JSON消息
                    ProcessReceivedMessage(receivedMessage);
                }
                else if (result.MessageType == WebSocketMessageType.Close)
                {
                    await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                    Console.WriteLine("WebSocket连接已关闭。");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"接收消息时出错: {ex.Message}");
                break;
            }
        }
    }
    
    static void ProcessReceivedMessage(string jsonMessage)
    {
        try
        {
            JObject messageObj = JObject.Parse(jsonMessage);
    
            // 处理心跳响应
            if (messageObj["pong"] != null)
            {
                Console.WriteLine($"收到心跳响应: pong - {messageObj["pong"]}");
                return;
            }
    
            // 处理行情数据
            if (messageObj["body"] != null)
            {
                var body = messageObj["body"];
                string stockCode = body["StockCode"]?.ToString();
                decimal price = body["Price"]?.ToObject<decimal>() ?? 0;
    
                Console.WriteLine($"产品: {stockCode}, 最新价: {price}");
    
                // 您可以在这里进一步解析其他字段,如买一价(body["BP1"]), 卖一价(body["SP1"]), 五档深度(body["Depth"])等。
                // 并将数据更新到UI或触发交易逻辑。
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"解析消息失败: {ex.Message}, 原始消息: {jsonMessage}");
        }
    }

    三、 实战二:使用HttpClient获取外汇K线历史数据

    对于回测或初始化图表,获取历史K线数据至关重要。

    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Newtonsoft.Json.Linq;
    
    class HistoricalDataFetcher
    {
        private static readonly HttpClient _httpClient = new HttpClient();
    
        static async Task Main(string[] args) // 如果这是独立程序,保留Main方法
        {
            await GetKLineData();
        }
    
        // 作为一个可调用方法
        public static async Task GetKLineData()
        {
            try
            {
                // 1. 构建请求URL
                string baseUrl = "http://39.107.99.235:1008/redis.php";
                string code = "fx_eurusd"; // 外汇产品代码
                string time = "5m";        // 5分钟K线
                int rows = 100;            // 获取100条
    
                string requestUrl = $"{baseUrl}?code={code}&time={time}&rows={rows}";
    
                // 2. 添加GZIP压缩支持,提升性能
                _httpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
    
                // 3. 发送GET请求
                HttpResponseMessage response = await _httpClient.GetAsync(requestUrl);
                response.EnsureSuccessStatusCode(); // 确保响应成功
    
                // 4. 读取并解析响应内容
                string responseContent = await response.Content.ReadAsStringAsync();
                JArray klineArray = JArray.Parse(responseContent);
    
                Console.WriteLine($"成功获取到 {klineArray.Count} 条K线数据:");
                foreach (JArray kline in klineArray)
                {
                    // 解析数组,格式为:[毫秒时间戳, 开盘价, 最高价, 最低价, 收盘价, 时间字符串, 成交量]
                    long timestamp = (long)kline[0];
                    decimal open = (decimal)kline[1];
                    decimal high = (decimal)kline[2];
                    decimal low = (decimal)kline[3];
                    decimal close = (decimal)kline[4];
                    string timeStr = (string)kline[5];
                    // 注意:外汇数据成交量可能为0或空,请以实际接口返回为准
                    // decimal volume = (decimal)kline[6];
    
                    Console.WriteLine($"时间:{timeStr}, 开:{open}, 高:{high}, 低:{low}, 收:{close}");
                }
            }
            catch (HttpRequestException ex)
            {
                Console.WriteLine($"HTTP请求失败: {ex.Message}");
                if (ex.StatusCode.HasValue)
                {
                    Console.WriteLine($"状态码: {ex.StatusCode}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"获取K线数据时发生错误: {ex.Message}");
            }
        }
    }

    四、 关键注意事项与最佳实践

    • 错误处理与重连:生产环境中,必须为WebSocket连接实现完善的断线重连机制,例如在catch块或关闭事件中延迟后重新调用ConnectAndListen

    • 性能与频率限制

      • 考虑使用单例模式的HttpClient,而不是每次请求都创建新的实例。

    • 数据解析安全:始终使用try-catch包裹JSON解析逻辑,并使用类似?.的安全导航操作符,以防API返回的数据结构意外变化。

    • 后台服务化:对于需要长期运行的WebSocket客户端,应将其封装在BackgroundService(在ASP.NET Core中)或Windows Service中。

结语
通过本教程,您已经掌握了在C#中使用ClientWebSocketHttpClient对接外汇行情API的核心技能。从建立实时推送到获取历史数据,这套代码框架可以作为您金融数据项目的坚实起点。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐