Install
openclaw skills install @jeffersonling1217-png/futu-trading-botUse Futu Trade Bot Skills to run account, quote, and trade workflows with real HK market data.
openclaw skills install @jeffersonling1217-png/futu-trading-botThis skill can control a live brokerage account via Futu OpenD. Misuse can cause irreversible financial loss.
Hard rules for the agent:
trd_env="REAL" unless the user explicitly requests live trading and confirms the exact order parameters.confirm=True:
unlock_trade / lock_tradesubmit_order when trd_env="REAL"modify_order / cancel_order when trd_env="REAL"cancel_all_orders in any environmentget_account_info(persist=False)) may run without confirm.trade_password_md5 preferred) or an explicit parameter the user already provided out-of-band.json/account_info.json unless the user asks to cache accounts (persist=True).SIMULATE/REAL, and log/PID paths.~/.com.futunn.FutuOpenD/Log, tell the user before suggesting host / elevated mode — never silently escalate.中文硬规则: 默认模拟盘;真金白银/解锁/全部撤单必须先复述参数并得到用户明确同意,再传 confirm=True;禁止 stdin 要密码;默认不把账户信息写盘。
English Version: A trading bot skill based on Futu OpenAPI that enables natural language trading. This skill encapsulates Futu's market quote and order execution APIs, allowing agents to perform real-time trading operations through simple commands or scripts. Perfect for implementing natural language trading strategies and automated workflows.
Important: Always use the encapsulated functions provided in this skill (e.g., submit_order, get_market_snapshot). Never call Futu SDK functions directly (ctx.place_order, ctx.get_market_snapshot), as this will bypass connection management, parameter validation, and error handling, leading to unpredictable failures and resource leaks.
中文版本: 基于富途牛牛API接口的交易机器人技能,帮助用户用自然语言进行交易。本技能已将富途牛牛的行情报价、下单交易等功能做了完整封装,可供智能助手随时调用。建议通过命令行或脚本来实现自然语言的策略生成和交易执行。
重要提示:请始终使用本技能提供的封装函数(如 submit_order、get_market_snapshot)。切勿直接调用富途SDK的原始函数(例如 ctx.place_order),否则会绕过连接管理、参数校验和错误处理,导致不可预料的失败和资源泄漏。
get_account_info(),默认不落盘)unlock_trade(confirm=True) / lock_trade(confirm=True)SIMULATE|REAL,用户确认后再调用;REAL 必须 confirm=Truecancel_all_orders(..., confirm=True)SIMULATEDo not map vague chat like “看看市场怎么样” to unlock/order/cancel.
Note to agent: Always use this skill's wrappers (get_account_info, get_market_snapshot, submit_order, …). Never call Futu SDK functions directly. Never skip confirm gates for privileged actions.
Prerequisites / 前提条件:
127.0.0.1:11111). The SDK may also need write access to ~/.com.futunn.FutuOpenD/Log.host / elevated mode. Do not silently escalate privileges.host / elevated,不要静默提权。Setup Steps / 安装步骤:
Install this skill via ClawHub (if not installed yet):
clawhub install futu-trading-bot
Enter the skill folder (default OpenClaw workspace path):
cd ~/.openclaw/workspace/skills/futu-trading-bot
If you installed to a different location, cd into that folder instead.
Create virtual environment (recommended):
python3 -m venv .venv
source .venv/bin/activate
Install package:
pip install -e .
Configure credentials:
cp json/config_example.json json/config.json
# Edit json/config.json with your Futu credentials
# 编辑json/config.json填写你的富途账户信息
本技能通过 pip install -e . 安装(版本见 requirements.txt / pyproject.toml):
futu-api==9.6.5608pydantic>=2.7.0,<3account_manager
get_account_info(persist=False) — default no disk writeunlock_trade(..., confirm=True) — privilegedlock_trade(..., confirm=True) — privilegedquote_service (read-oriented)
get_stock_basicinfo, get_market_statesubscribe, unsubscribe, unsubscribe_all, query_subscription, callbacksget_market_snapshot, get_cur_kline, request_history_kline, get_rt_tickerstart_quote_stream, start_orderbook_streamtrade_service (privileged)
submit_order(..., confirm=) — REAL requires confirmmodify_order / cancel_order — REAL requires confirmcancel_all_orders(..., confirm=True) — always requires confirmstrategy_runtime / strategy helperspreflight_check first.get_account_info() (no persist) and select acc_id.HK.00700).unlock_trade(confirm=True).acc_id + trd_env (prefer SIMULATE; REAL needs confirm=True).lock_trade(confirm=True) if the user wants trading locked again.get_market_snapshot, get_stock_basicinfo, get_market_state, get_cur_kline, request_history_kline, and get_rt_ticker now close their quote context automatically after returning.submit_order, modify_order, and cancel_all_orders now close their trade/quote contexts automatically after returning.get_account_info, unlock_trade, and lock_trade now close their contexts automatically after returning.subscribe, unsubscribe, unsubscribe_all, query_subscription, set_quote_callback, and set_orderbook_callback, call close_quote_service() explicitly when you are done with the session.# Always use these import paths – do not import from futu directly
from preflight_check import run_preflight
from strategy import (
StrategyState, TradeGuard, in_trading_window,
trading_window_status, cooldown_elapsed, holding_timeout_exceeded
)
from strategy_runtime import run_strategy
from account_manager import get_account_info, unlock_trade, lock_trade
from quote_service import (
get_stock_basicinfo, get_market_state, get_market_snapshot,
get_cur_kline, request_history_kline, get_rt_ticker,
subscribe, unsubscribe, unsubscribe_all, query_subscription,
set_quote_callback, set_orderbook_callback,
start_quote_stream, start_orderbook_stream
)
from trade_service import submit_order, modify_order, cancel_order, cancel_all_orders
preflight = run_preflight()
if not preflight["success"]:
print(preflight)
raise SystemExit("Preflight failed")
# Get list of accounts (in-memory only by default)
info = get_account_info() # persist=False
if info['success']:
accounts = info['accounts']
print(accounts)
# Optional: cache accounts locally only if user asked
# info = get_account_info(persist=True)
# Unlock trade ONLY after explicit user approval
unlock_trade(confirm=True) # loads trade_password_md5 / trade_password from config
# Lock trade after user approval
lock_trade(confirm=True)
get_stock_basicinfo(market="HK", sec_type="STOCK", code_list=["HK.00700"])
get_market_state(["HK.00700"])
snap = get_market_snapshot(["HK.00700"])
if snap['success']:
price = snap['data'][0]['last_price']
# Current K-line (requires subscription, will auto-subscribe if needed)
kline = get_cur_kline(code="HK.00700", num=5, ktype="K_DAY", autype="QFQ")
# Historical K-line
hist = request_history_kline(
code="HK.00700",
start="2026-02-20",
end="2026-03-06",
ktype="K_DAY"
)
tickers = get_rt_ticker(code="HK.00700", num=10)
def on_quote(payload):
print(payload)
set_quote_callback(on_quote)
subscribe(["HK.00700"], ["QUOTE"], is_first_push=True, subscribe_push=True)
query_subscription()
unsubscribe(["HK.00700"], ["QUOTE"])
unsubscribe_all()
close_quote_service()
def on_quote(payload):
print(payload)
start_quote_stream(["HK.00700"], on_quote)
state = StrategyState()
guard = TradeGuard()
if in_trading_window(start_time="09:30", end_time="16:00"):
with guard.locked():
pass
# Preferred: SIMULATE (no confirm required by the gate)
result = submit_order(
code="HK.00700",
side="BUY",
qty=200,
acc_id=6017237,
trd_env="SIMULATE",
price=150,
order_type="NORMAL",
)
# REAL only after user explicitly approves the exact parameters
# result = submit_order(..., trd_env="REAL", confirm=True)
modify_order(
op="NORMAL",
order_id="123456789",
trd_env="SIMULATE",
price=151,
qty=200,
acc_id=6017237,
)
cancel_order(order_id="123456789", trd_env="SIMULATE", acc_id=6017237)
# Bulk cancel ALWAYS requires confirm=True
cancel_all_orders(trd_env="SIMULATE", acc_id=6017237, confirm=True)
This skill does not auto-start long-running processes. Only if the user explicitly asks to start/stop a strategy:
Do not launch background strategies for vague requests. Do not use elevated/host mode without telling the user why.
| User Request | Agent Action |
|---|---|
| “Start a strategy…” (explicit) | Confirm params → write template script → start after approval → return PID/log |
| “How is my strategy doing?” | Read the agreed log file / check PID → summarize |
| “Stop my strategy” | Confirm → stop the recorded PID → clean up |
When generating a strategy script, use the following template. It handles signals, logging, and state persistence correctly.
#!/usr/bin/env python3
import sys
import time
import json
import os
import signal
import logging
from pathlib import Path
# If you installed the skill with `pip install -e .`, you can import modules directly.
# Only use sys.path/PYTHONPATH hacks when you didn't install the package.
from trade_service import submit_order
from quote_service import get_market_snapshot
# ===== Strategy parameters – fill by agent =====
# Replace these placeholders with your own strategy settings.
SYMBOL = "HK.00700"
ACC_ID = 0 # fill from get_account_info()
TRD_ENV = "SIMULATE" # default to SIMULATE; use REAL only with explicit confirmation
QTY = 0 # position sizing / order quantity
LOG_FILE = Path("strategy.log")
PID_FILE = Path("strategy.pid")
# ===============================================
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Handle termination signals
def handle_exit(signum, frame):
logging.info("Received signal, stopping strategy")
sys.exit(0)
signal.signal(signal.SIGTERM, handle_exit)
signal.signal(signal.SIGINT, handle_exit)
# Write PID file
with open(PID_FILE, "w") as f:
f.write(str(os.getpid()))
logging.info(f"Strategy started: {SYMBOL}")
try:
while True:
snap = get_market_snapshot([SYMBOL])
if not snap["success"]:
logging.error(f"Quote failed: {snap['message']}")
time.sleep(60)
continue
price = snap["data"][0]["last_price"]
logging.info(f"Current price: {price}")
# --- Insert your strategy logic here ---
# Decide whether to trade based on your own signals/logic, then call submit_order(...).
time.sleep(60) # check every minute
except Exception as e:
logging.exception("Strategy crashed")
finally:
if PID_FILE.exists():
PID_FILE.unlink()
User: “Start a range strategy for Tencent, buy below 540, sell above 550.”
Agent:
TRD_ENV=SIMULATE. Ask for confirmation.get_account_info() for acc_id.range_00700.py).User: “How is my strategy doing?”
Agent:
tail -n 20 strategy_00700.log.ps -p 12345.User: “Stop my strategy.”
Agent:
success (bool) and message (str).data or order_id may be present.success first before using other fields.Example:
result = submit_order(...)
if result["success"]:
print(f"Order ID: {result['order_id']}")
else:
print(f"Error: {result['message']}")
If OpenD connection fails, recheck:
lsof -i :11111)config.json matches OpenDIf the skill fails before quote/trade functions are even called, recheck:
host / elevated mode~/.com.futunn.FutuOpenD/LogPYTHONPATH=src python -m preflight_check first and follow its suggestionsjson/config.jsonfutu_api.host (default: 127.0.0.1)futu_api.port (default: 11111)futu_api.security_firm (e.g., FUTUSECURITIES)trade_password_md5 (32-char lowercase MD5)trade_password fallback (MD5 at runtime)json/config.json privatejson/account_info.json only when get_account_info(persist=True)This skill is licensed under MIT-0 (MIT No Attribution).
Copyright © 2026 jeffersonling1217-png