1. 项目背景与核心需求
去年我在管理一个小型投资组合时,经常遇到这样的困扰:明明设置了价格提醒,但收到通知时股价早已波动了好几个百分点。传统的股票APP推送延迟高,而专业级交易软件又过于复杂。于是我用Python+飞书搭建了一套实时监控系统,现在股价波动超过1%时,5秒内就能在飞书群里收到带K线图的预警。
这个方案的核心价值在于:
- 实时性:基于websocket的行情接口,比传统轮询方式快3-5秒
- 可视化:自动生成带关键指标的图文消息
- 可定制:监控规则完全自主定义(如波动阈值、成交量异动等)
- 零成本:全部使用免费API和开源库
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体数据流
mermaid复制graph TD
A[券商API] -->|WebSocket| B(Python处理引擎)
B --> C{触发条件?}
C -->|是| D[生成可视化图表]
C -->|否| B
D --> E[飞书机器人]
E --> F[手机/PC端提醒]
2.2 关键组件选型
| 组件类型 | 候选方案 | 最终选择 | 选择理由 |
|---|---|---|---|
| 行情接口 | 阿里云金融API、Tushare、Yahoo Finance | Tushare Pro | 免费额度足够(500次/分钟),支持A股实时行情 |
| 消息推送 | 邮件、Server酱、钉钉机器人 | 飞书机器人 | 支持富文本+图片,企业级可靠性 |
| 可视化 | Matplotlib、Pyecharts、Plotly | Pyecharts | 交互式图表导出为静态图片效果最佳 |
| 计算引擎 | 纯Python、Pandas、NumPy | Pandas | 时间序列处理效率高 |
提示:Tushare需要注册获取token,建议使用企业邮箱申请可获得更高频次权限
3. 核心代码实现
3.1 实时数据获取
python复制import tushare as ts
from websocket import create_connection
class StockMonitor:
def __init__(self, token):
self.pro = ts.pro_api(token)
self.ws_url = "wss://api.tushare.pro/streaming"
def subscribe(self, codes):
"""订阅股票实时行情"""
ws = create_connection(self.ws_url)
auth = {"action":"auth","params":{"token":self.pro.token}}
sub = {"action":"sub","params":{"codes":codes}}
ws.send(json.dumps(auth))
ws.send(json.dumps(sub))
return ws
3.2 波动检测逻辑
python复制def check_alert(prev_data, current_data):
"""检测是否触发预警条件"""
alert_rules = {
'price_change': lambda p,c: abs((c['price']-p['price'])/p['price']) > 0.01,
'volume_spike': lambda p,c: c['volume'] > p['volume']*2,
'breakthrough': lambda p,c: c['price'] > p['upper'] or c['price'] < p['lower']
}
triggers = []
for name, rule in alert_rules.items():
if rule(prev_data, current_data):
triggers.append(name)
return triggers if triggers else None
3.3 飞书消息生成
python复制from pyecharts.charts import Kline
from io import BytesIO
def generate_kline_chart(data):
"""生成K线图PNG"""
kline = (
Kline()
.add_xaxis(data['dates'])
.add_yaxis("", data['prices'])
.set_global_opts(title_opts={"text": f"{data['name']}近期走势"})
)
return kline.render_embed()
def build_lark_message(stock, triggers):
"""构造飞书富文本消息"""
content = [[
{"tag":"text","text":f"⚠️ {stock['name']}({stock['code']}) 触发告警:"},
{"tag":"text","text":", ".join(triggers),"un_escape":True}
]]
if 'price_change' in triggers:
content.append([{
"tag":"img",
"image_key": upload_image(generate_kline_chart(stock)),
"width": 300
}])
return {"msg_type":"interactive","card":{
"header":{"title":{"content":"股票异动预警"}},
"elements": content
}}
4. 部署与优化实践
4.1 生产环境部署方案
推荐使用以下架构保证稳定性:
code复制[券商API] → [Python处理服务] → [Redis缓存] → [飞书机器人]
↑
[定时任务] ← [MySQL持久化]
关键配置参数:
yaml复制# config.yaml
monitor:
interval: 3s # 扫描间隔
timeout: 10s # 接口超时
retry: 3 # 重试次数
stocks:
- code: 600519
name: 贵州茅台
rules:
price_change: 1%
volume_spike: true
- code: 000858
name: 五粮液
rules:
breakthrough: true
4.2 性能优化技巧
- 连接池管理:复用WebSocket连接而非每次新建
python复制class ConnectionPool:
def __init__(self, max_conn=5):
self.pool = Queue(max_conn)
for _ in range(max_conn):
self.pool.put(create_connection(WS_URL))
def get_conn(self):
return self.pool.get()
def release(self, conn):
self.pool.put(conn)
- 批量处理策略:攒批处理避免频繁触发飞书限流(100次/分钟)
python复制from collections import defaultdict
class BatchNotifier:
def __init__(self, interval=60):
self.buffer = defaultdict(list)
self.interval = interval
def add_alert(self, alert):
self.buffer[alert['code']].append(alert)
def flush(self):
for code, alerts in self.buffer.items():
merged = self._merge_alerts(alerts)
send_lark_message(merged)
self.buffer.clear()
5. 常见问题解决方案
5.1 飞书消息发送失败
典型错误及处理方案:
| 错误码 | 原因 | 解决方案 |
|---|---|---|
| 19001 | 频率限制 | 实现消息队列+批量发送 |
| 19022 | 图片过大 | 压缩PNG到300KB以下 |
| 10003 | 权限不足 | 检查机器人webhook是否启用 |
5.2 数据断连处理
python复制def resilient_fetch(ws, max_retry=3):
retry = 0
while retry < max_retry:
try:
data = ws.recv()
return json.loads(data)
except ConnectionResetError:
ws = reconnect_websocket()
retry += 1
raise Exception("Max retries exceeded")
5.3 监控规则扩展
通过继承基类实现自定义规则:
python复制class MACDAlertRule(AlertRule):
def __init__(self, fast=12, slow=26, signal=9):
self.fast = fast
self.slow = slow
def check(self, data):
close_prices = [x['close'] for x in data]
macd = talib.MACD(close_prices,
fastperiod=self.fast,
slowperiod=self.slow)
return macd[-1] > 0 and macd[-2] <= 0 # 金叉检测
这套系统在我实际使用中,对短线操作的帮助非常大。特别是在今年3月的白酒板块异动中,比主流财经APP提前2分钟捕捉到五粮液的放量突破,及时提示了买入机会。建议大家可以先从监控3-5支重点股票开始,逐步扩展规则库
