在量化交易和金融应用开发领域,稳定、实时的行情数据是系统运行的基石。脉动数据作为专业的行情服务提供商,为开发者提供了完整的WebSocket和HTTP接口解决方案。本文将详细介绍脉动数据API的特性,并通过实战代码演示如何快速对接全球期货市场数据。
一、脉动数据平台概述
脉动数据专注于提供全球金融市场的实时行情数据服务,涵盖外汇、国际期货、国内期货、数字货币、国际股指期货等多个品种。其API接口具有以下核心特性:
- 数据时效性:WebSocket实时推送,行情更新即推送
- 接入方式:同时支持WebSocket实时推送和HTTP REST接口
- 数据格式:统一的JSON格式,便于解析和处理
- 授权机制:服务器IP白名单授权,确保数据安全
1.1 接入前准备
二、API接口体系
脉动数据提供五大类接口,满足不同的数据需求:
| 接口类型 | 功能描述 | 适用场景 |
|---|---|---|
| WebSocket实时推送 | 实时分笔明细数据推送 | 高频交易、实时监控 |
| 获取实时数据接口 | HTTP方式获取实时行情 | 中低频查询、应用集成 |
| 获取K线图接口 | 历史K线数据查询 | 技术分析、回测 |
| 查询产品分类接口 | 获取产品分类ID | 数据浏览、分类查询 |
| 查询产品订阅代码 | 获取产品代码列表 | 订阅准备、代码映射 |
三、WebSocket实时数据推送实战
WebSocket接口适合需要实时监控行情的高频交易场景
3.1 WebSocket连接与心跳维持
import asyncio
import websockets
import json
import time
class PulseDataWebSocket:
def __init__(self, uri="ws://39.107.99.235/ws"):
self.uri = uri
self.websocket = None
self.subscribed_symbols = []
self.running = False
async def connect(self):
"""建立WebSocket连接"""
try:
self.websocket = await websockets.connect(self.uri)
self.running = True
print(f"WebSocket连接成功: {self.uri}")
# 启动心跳任务
asyncio.create_task(self.heartbeat())
# 如果有之前订阅的代码,重新订阅
if self.subscribed_symbols:
await self.subscribe(self.subscribed_symbols)
# 开始接收消息
await self.receive_messages()
except Exception as e:
print(f"连接失败: {e}")
await self.reconnect()
async def heartbeat(self):
"""每10秒发送心跳"""
while self.running:
if self.websocket:
try:
ping_msg = {"ping": int(time.time())}
await self.websocket.send(json.dumps(ping_msg))
print(f"发送心跳: {ping_msg}")
await asyncio.sleep(10)
except Exception as e:
print(f"心跳发送失败: {e}")
break
async def subscribe(self, symbols):
"""订阅产品代码"""
self.subscribed_symbols = symbols
if self.websocket:
subscribe_msg = {"Key": ",".join(symbols)}
await self.websocket.send(json.dumps(subscribe_msg))
print(f"订阅产品: {subscribe_msg}")
async def receive_messages(self):
"""接收服务器推送的消息"""
while self.running:
try:
message = await self.websocket.recv()
data = json.loads(message)
# 处理pong响应
if "pong" in data:
print(f"收到心跳响应: {data}")
else:
# 处理行情数据
self.process_market_data(data)
except websockets.exceptions.ConnectionClosed:
print("连接关闭,尝试重连...")
await self.reconnect()
break
except Exception as e:
print(f"接收消息错误: {e}")
def process_market_data(self, data):
"""处理行情数据"""
if "body" in data:
body = data["body"]
print(f"""
产品代码: {body.get('StockCode')}
最新价: {body.get('Price')}
开盘价: {body.get('Open')}
最高价: {body.get('High')}
最低价: {body.get('Low')}
时间: {body.get('Time')}
""")
# 处理深度数据
if "Depth" in body:
depth = body["Depth"]
if "Buy" in depth and depth["Buy"]:
print(f"买一价: {depth['Buy'][0].get('BP1')}, 买一量: {depth['Buy'][0].get('BV1')}")
if "Sell" in depth and depth["Sell"]:
print(f"卖一价: {depth['Sell'][0].get('SP1')}, 卖一量: {depth['Sell'][0].get('SV1')}")
async def reconnect(self):
"""断线重连机制"""
self.running = False
if self.websocket:
await self.websocket.close()
# 指数退避重连
retry_count = 0
while True:
retry_count += 1
wait_time = min(30, 2 ** retry_count)
print(f"{wait_time}秒后尝试第{retry_count}次重连...")
await asyncio.sleep(wait_time)
try:
await self.connect()
print("重连成功")
break
except Exception as e:
print(f"重连失败: {e}")
async def close(self):
"""关闭连接"""
self.running = False
if self.websocket:
await self.websocket.close()
# 使用示例
async def main():
client = PulseDataWebSocket()
# 连接并订阅产品
await client.connect()
await client.subscribe(["btcusdt", "ethusdt"])
# 运行60秒后关闭
await asyncio.sleep(60)
await client.close()
if __name__ == "__main__":
asyncio.run(main())
四、HTTP实时数据接口实战
对于不需要实时推送的应用场景,可以使用HTTP接口获取实时行情。需要注意请求频率限制:每个产品每秒最大支持请求3次。
4.1 获取实时行情
import requests
import gzip
import json
from io import BytesIO
import time
from functools import lru_cache
class PulseDataHTTP:
def __init__(self, base_url="http://39.107.99.235:1008"):
self.base_url = base_url
self.session = requests.Session()
# 启用gzip压缩
self.session.headers.update({
'Accept-Encoding': 'gzip',
'User-Agent': 'Mozilla/5.0 (compatible; PulseDataClient/1.0)'
})
def _handle_response(self, response):
"""处理gzip压缩的响应"""
if response.headers.get('Content-Encoding') == 'gzip':
buf = BytesIO(response.content)
with gzip.GzipFile(fileobj=buf) as f:
return json.loads(f.read().decode('utf-8'))
return response.json()
def get_quote(self, code):
"""
获取实时行情
:param code: 产品代码,如 btcusdt
:return: 行情数据
"""
url = f"{self.base_url}/getQuote.php"
params = {'code': code}
try:
response = self.session.get(url, params=params, timeout=10)
response.raise_for_status()
data = self._handle_response(response)
if data.get('code') == 200:
return data.get('data', {})
else:
print(f"API错误: {data.get('msg')}")
return None
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
@lru_cache(maxsize=128)
def get_cached_quote(self, code, cache_seconds=1):
"""
带缓存的行情查询,减少API调用
:param code: 产品代码
:param cache_seconds: 缓存时间(秒)
"""
# 通过lru_cache实现简单缓存,实际应用建议使用Redis等
return self.get_quote(code)
def parse_quote_data(self, data):
"""解析行情数据"""
if not data or 'body' not in data:
return None
body = data['body']
quote_info = {
'code': body.get('StockCode'),
'price': body.get('Price'),
'open': body.get('Open'),
'high': body.get('High'),
'low': body.get('Low'),
'last_close': body.get('LastClose'),
'time': body.get('Time'),
'timestamp': body.get('LastTime'),
'volume': body.get('TotalVol'),
'diff': data.get('Diff'),
'diff_rate': data.get('DiffRate')
}
# 解析盘口数据
if 'Depth' in body:
depth = body['Depth']
quote_info['bid'] = depth.get('Buy', [])
quote_info['ask'] = depth.get('Sell', [])
# 解析实时成交
if 'BS' in body:
quote_info['trades'] = body.get('BS', [])
return quote_info
# 使用示例
def demo_http_api():
client = PulseDataHTTP()
# 查询BTCUSDT行情
data = client.get_quote('btcusdt')
if data:
quote = client.parse_quote_data(data)
if quote:
print(f"""
=== {quote['code']} 实时行情 ===
最新价: {quote['price']}
开盘价: {quote['open']}
最高价: {quote['high']}
最低价: {quote['low']}
成交量: {quote['volume']}
涨跌额: {quote['diff']}
涨跌幅: {quote['diff_rate']}%
更新时间: {quote['time']}
""")
# 打印盘口
if quote.get('bid'):
print("\n买盘:")
for i, bid in enumerate(quote['bid'][:3]):
print(f"买{i+1}: {bid.get(f'BP{i+1}')} @ {bid.get(f'BV{i+1}')}")
if quote.get('ask'):
print("\n卖盘:")
for i, ask in enumerate(quote['ask'][:3]):
print(f"卖{i+1}: {ask.get(f'SP{i+1}')} @ {ask.get(f'SV{i+1}')}")
if __name__ == "__main__":
demo_http_api()
五、K线数据获取与处理
K线数据是技术分析的基础,脉动数据支持多种时间周期的K线查询。
5.1 K线数据接口封装
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
class PulseDataKLine(PulseDataHTTP):
"""K线数据查询类,继承基础HTTP客户端"""
# 时间周期映射
TIME_FRAMES = {
'1m': '1分钟',
'5m': '5分钟',
'15m': '15分钟',
'30m': '30分钟',
'1h': '1小时',
'1d': '日线',
'1M': '月线'
}
# 最大条数限制
MAX_ROWS = {
'1m': 600,
'5m': 300,
'15m': 300,
'30m': 300,
'1h': 300,
'1d': 300,
'1M': 100
}
def get_kline(self, code, timeframe='1m', rows=100):
"""
获取K线数据
:param code: 产品代码
:param timeframe: 时间周期,支持 1m,5m,15m,30m,1h,1d,1M
:param rows: 获取条数,不能超过对应周期的最大限制
:return: K线数据列表
"""
# 参数验证
if timeframe not in self.TIME_FRAMES:
raise ValueError(f"不支持的时间周期: {timeframe},支持: {list(self.TIME_FRAMES.keys())}")
max_rows = self.MAX_ROWS.get(timeframe, 100)
if rows > max_rows:
print(f"警告: {timeframe}周期最大支持{max_rows}条数据,将使用{max_rows}")
rows = max_rows
url = f"{self.base_url}/redis.php"
params = {
'code': code,
'time': timeframe,
'rows': rows
}
try:
response = self.session.get(url, params=params, timeout=10)
response.raise_for_status()
data = self._handle_response(response)
if isinstance(data, list):
return self._parse_kline_data(data)
else:
print(f"API返回异常: {data}")
return None
except Exception as e:
print(f"K线查询失败: {e}")
return None
def _parse_kline_data(self, raw_data):
"""解析K线原始数据"""
klines = []
for item in raw_data:
if len(item) >= 7:
kline = {
'timestamp': item[0], # 毫秒时间戳
'open': float(item[1]),
'high': float(item[2]),
'low': float(item[3]),
'close': float(item[4]),
'time_str': item[5], # 格式化时间字符串
'volume': float(item[6]) if item[6] else 0 # 成交量
}
klines.append(kline)
return klines
def kline_to_dataframe(self, klines):
"""将K线数据转换为Pandas DataFrame"""
if not klines:
return None
df = pd.DataFrame(klines)
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('datetime', inplace=True)
# 计算常用技术指标
df['ma5'] = df['close'].rolling(window=5).mean()
df['ma10'] = df['close'].rolling(window=10).mean()
df['ma20'] = df['close'].rolling(window=20).mean()
return df
def plot_kline(self, df, title=None):
"""绘制K线图(简化版,仅显示收盘价)"""
if df is None or df.empty:
return
plt.figure(figsize=(12, 6))
# 绘制收盘价
plt.subplot(2, 1, 1)
plt.plot(df.index, df['close'], label='Close', color='black')
plt.plot(df.index, df['ma5'], label='MA5', color='blue', alpha=0.7)
plt.plot(df.index, df['ma10'], label='MA10', color='orange', alpha=0.7)
plt.plot(df.index, df['ma20'], label='MA20', color='red', alpha=0.7)
plt.title(title or 'K线图')
plt.legend()
plt.grid(True, alpha=0.3)
# 绘制成交量
plt.subplot(2, 1, 2)
plt.bar(df.index, df['volume'], color='gray', alpha=0.5)
plt.title('成交量')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 使用示例
def demo_kline():
client = PulseDataKLine()
# 获取BTCUSDT 15分钟K线,最近50条
klines = client.get_kline('btcusdt', timeframe='15m', rows=50)
if klines:
# 转换为DataFrame
df = client.kline_to_dataframe(klines)
print(f"获取到 {len(df)} 条K线数据")
print(df[['open', 'high', 'low', 'close', 'volume']].tail())
# 绘制K线图
client.plot_kline(df, title='BTCUSDT 15分钟K线')
# 简单分析
latest = df.iloc[-1]
print(f"""
最新K线:
时间: {latest.name}
开盘: {latest['open']}
最高: {latest['high']}
最低: {latest['low']}
收盘: {latest['close']}
成交量: {latest['volume']}
涨跌幅: {(latest['close'] - df.iloc[-2]['close']) / df.iloc[-2]['close'] * 100:.2f}%
""")
if __name__ == "__main__":
demo_kline()
六、产品分类与代码查询
在实际应用中,需要先查询产品分类和订阅代码。
6.1 产品分类查询
class PulseDataSymbol(PulseDataHTTP):
"""产品代码查询类"""
def get_categories(self):
"""获取产品分类"""
url = f"{self.base_url}/getCategory.php"
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
data = self._handle_response(response)
if data.get('code') == 200:
return data.get('data', {}).get('list', [])
else:
print(f"获取分类失败: {data.get('msg')}")
return []
except Exception as e:
print(f"分类查询异常: {e}")
return []
def get_symbols(self, category_id, page=1, page_size=10):
"""
获取产品订阅代码
:param category_id: 分类ID(从get_categories获取)
:param page: 页码
:param page_size: 每页条数
:return: 产品列表
"""
url = f"{self.base_url}/getSymbolList.php"
params = {
'category': category_id,
'page': page,
'pageSize': page_size
}
try:
response = self.session.get(url, params=params, timeout=10)
response.raise_for_status()
data = self._handle_response(response)
if data.get('code') == 200:
return data.get('data', {})
else:
print(f"获取产品列表失败: {data.get('msg')}")
return {}
except Exception as e:
print(f"产品查询异常: {e}")
return {}
def search_symbols_by_name(self, keyword):
"""根据名称搜索产品(遍历所有分类)"""
results = []
# 获取所有分类
categories = self.get_categories()
for category in categories:
cat_id = category['id']
cat_name = category['name']
# 获取第一页数据
data = self.get_symbols(cat_id, page=1, page_size=50)
if data and 'list' in data:
for symbol in data['list']:
if keyword.lower() in symbol['name'].lower() or keyword.lower() in symbol['code'].lower():
symbol['category'] = cat_name
results.append(symbol)
return results
# 使用示例
def demo_symbol_query():
client = PulseDataSymbol()
# 1. 获取所有分类
print("=== 产品分类 ===")
categories = client.get_categories()
for cat in categories:
print(f"ID: {cat['id']}, 名称: {cat['name']}")
# 2. 查询数字货币分类下的产品
print("\n=== 数字货币产品(第1页)===")
data = client.get_symbols('4', page=1, page_size=5)
if data:
print(f"总条数: {data.get('total')}")
for symbol in data.get('list', []):
print(f"代码: {symbol['code']}, 名称: {symbol['name']}")
# 3. 搜索比特币相关产品
print("\n=== 搜索 'BTC' 相关产品 ===")
results = client.search_symbols_by_name('BTC')
for symbol in results:
print(f"[{symbol['category']}] {symbol['code']} - {symbol['name']}")
if __name__ == "__main__":
demo_symbol_query()
七、总结
脉动数据API为开发者提供了完整的期货、数字货币等金融产品行情数据接入方案。通过本文的实战指南,您可以快速掌握:
- WebSocket实时数据推送:适用于高频交易和实时监控场景,需实现心跳维持和断线重连机制
- HTTP接口调用:适用于中低频查询,需注意请求频率限制,建议配合缓存使用
- K线数据获取:支持多种时间周期,可用于技术分析和策略回测
- 产品分类与代码查询:便于动态获取可订阅的产品列表
- 系统集成最佳实践:包括限流、缓存、错误处理等关键机制
脉动数据的主要优势包括:
- 多市场覆盖:外汇、期货、数字货币等全球品种
- 双通道接入:WebSocket实时推送 + HTTP查询
- 数据丰富:不仅包含基础行情,还有深度盘口、实时成交等
- 开发者友好:统一的JSON格式,清晰的字段说明
无论是构建量化交易系统、开发行情分析工具,还是创建风险管理平台,脉动数据API都能提供稳定可靠的数据支持。建议开发者根据实际需求选择合适的接入方式,并合理设计限流、缓存和错误处理机制,确保系统的稳定性和性能。
注:期货和数字货币交易具有高风险,投资者应充分了解相关风险并谨慎决策。本文示例代码仅供参考,实际使用时请根据具体需求进行调整优化。