Skip to content

Commit 6db110f

Browse files
committed
Document virtualenv setup commands
1 parent 96a41aa commit 6db110f

File tree

14 files changed

+1300
-1
lines changed

14 files changed

+1300
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__/
2+
*.pyc
3+
.env

README.md

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,108 @@
1-
# 1
1+
# 量化交易自动化流水线
2+
3+
本项目提供一个端到端的自动化交易流程示例,涵盖以下四个核心阶段:
4+
5+
1. 每 3 分钟通过 Futu OpenAPI 采集行情与账户信息。
6+
2. 将原始数据整理成结构化的 Prompt 载荷。
7+
3. 将 Prompt 发送至兼容 DeepSeek 的大语言模型获取交易决策。
8+
4. 根据决策结果调用 Futu 交易接口自动下单。
9+
10+
> **免责声明:** 本项目仅用于学习与技术验证,真实资金交易存在重大风险,请谨慎使用。
11+
12+
## 项目结构
13+
14+
```
15+
src/
16+
├── main.py # 命令行入口,负责流程编排
17+
└── trading_bot/
18+
├── __init__.py # 包装对外可用的公共接口
19+
├── data_fetcher.py # Futu 行情与账户数据采集逻辑
20+
├── data_models.py # 组件间共享的 Pydantic 数据模型
21+
├── indicators.py # 技术指标计算工具
22+
├── llm_client.py # DeepSeek/OpenAI API 调用封装
23+
├── order_executor.py # 下单与交易模式控制
24+
└── scheduler.py # 异步调度器,协调周期性任务
25+
```
26+
27+
## 安装
28+
29+
1. 在终端创建并激活 Python 3.10 及以上版本的虚拟环境:
30+
31+
```bash
32+
python -m venv .venv
33+
# Linux / macOS
34+
source .venv/bin/activate
35+
# Windows PowerShell
36+
.\.venv\Scripts\Activate.ps1
37+
```
38+
39+
2. 安装项目依赖:
40+
41+
```bash
42+
pip install -e .
43+
```
44+
45+
3. 确保本地已启动并可访问 Futu OpenD 服务。
46+
47+
## 环境配置
48+
49+
`env.example` 复制为 `.env`,并根据实际情况填写变量:
50+
51+
- `DEEPSEEK_API_KEY`:DeepSeek 兼容接口的 API Key(若未设置则回退至 `OPENAI_API_KEY`)。
52+
- `DEEPSEEK_API_BASE_URL`:自定义 DeepSeek API 地址(可选)。
53+
- `FUTU_HOST`:Futu OpenD 服务的主机名或 IP。
54+
- `FUTU_QUOTE_PORT` / `FUTU_TRADE_PORT`:OpenD 暴露的行情与交易端口。
55+
- `FUTU_OPENAPI_APP_ID` / `FUTU_OPENAPI_APP_SECRET`:富途牛牛提供的 OpenAPI 应用凭证。
56+
- `FUTU_TRADE_PASSWORD`:解锁真实交易所需的交易密码(仅在实盘模式必填)。
57+
- `FUTU_TRADING_MODE`:交易模式,`paper` 表示模拟交易,`live` 表示真实交易(默认 `paper`)。
58+
- `FUTU_TRADING_MARKET`:交易市场,支持 `hk`(港股)、`us`(美股)和 `cn`(A 股)。
59+
- `FUTU_ACCOUNT_ID`:在存在多个交易账户时指定具体账户(可选)。
60+
- `POLL_INTERVAL_SECONDS`:默认轮询周期(秒),当未通过 CLI 指定时使用。
61+
- `HK_SYMBOLS`:逗号分隔的港股代码列表,例如 `HK.00700,HK.00005`
62+
- `CN_SYMBOLS`:逗号分隔的 A 股代码列表,例如 `SH.000300,SZ.000001`
63+
- `US_SYMBOLS`:逗号分隔的美股代码列表,例如 `US.AAPL,US.MSFT`
64+
65+
## 使用说明
66+
67+
通过命令行运行交易循环,并指定需要跟踪的股票代码:
68+
69+
```bash
70+
python -m trading_bot.main SH.000300
71+
```
72+
73+
常用命令行参数:
74+
75+
- `--interval`:轮询周期(秒),默认 180。
76+
- `--language`:Prompt 语言,可选 `en``zh`
77+
- `--host` / `--quote-port` / `--trade-port`:Futu OpenD 连接参数。
78+
- `--trading-mode``paper``live`,切换模拟/实盘环境。
79+
- `--trading-market`:订单路由市场,支持 `hk``us``cn`
80+
- `--trade-password`:交易密码(若未设置则回退至 `FUTU_TRADE_PASSWORD`)。
81+
- `--account-id`:指定使用的 Futu 账户 ID(若未设置则回退至 `FUTU_ACCOUNT_ID`)。
82+
- `--log-level`:日志等级。
83+
84+
当命令行未显式传入股票代码时,程序会依次读取环境变量 `HK_SYMBOLS``CN_SYMBOLS``US_SYMBOLS` 作为默认跟踪列表。
85+
86+
按下 `Ctrl+C` 可以安全退出循环。
87+
88+
## 扩展方向
89+
90+
- 通过继承 `PromptFormatter` 自定义 Prompt 模板或语言风格。
91+
-`OrderExecutor` 中加入风控或仓位管理策略以增强安全性。
92+
- 扩展 `TradingScheduler`,将行情与决策结果落盘持久化或推送到外部系统。
93+
94+
## 测试
95+
96+
安装开发依赖并运行自动化测试:
97+
98+
```bash
99+
pip install -e .[dev]
100+
pytest
101+
```
102+
103+
如只需验证语法,可执行:
104+
105+
```bash
106+
python -m compileall src
107+
```
108+

env.example

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copy this file to .env and fill in the required values.
2+
3+
# DeepSeek / OpenAI configuration
4+
DEEPSEEK_API_KEY=your-deepseek-api-key
5+
DEEPSEEK_API_BASE_URL=https://api.deepseek.com
6+
7+
# Futu OpenD connection settings and API credentials
8+
FUTU_HOST=127.0.0.1
9+
FUTU_QUOTE_PORT=11111
10+
FUTU_TRADE_PORT=11111
11+
FUTU_OPENAPI_APP_ID=your-opend-app-id
12+
FUTU_OPENAPI_APP_SECRET=your-opend-app-secret
13+
FUTU_TRADE_PASSWORD=your-trading-password
14+
FUTU_TRADING_MODE=paper
15+
FUTU_TRADING_MARKET=hk
16+
FUTU_ACCOUNT_ID=
17+
18+
# Default polling interval in seconds for the trading loop
19+
POLL_INTERVAL_SECONDS=180
20+
21+
# Comma-separated symbol lists for each market (use official codes)
22+
HK_SYMBOLS=HK.00700,HK.00005
23+
CN_SYMBOLS=SH.000300,SZ.000001
24+
US_SYMBOLS=US.AAPL,US.MSFT

pyproject.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[project]
2+
name = "trading-bot"
3+
version = "0.1.0"
4+
description = "Automated trading pipeline integrating Futu market data, prompt generation, LLM decisioning, and order execution."
5+
requires-python = ">=3.10"
6+
authors = [
7+
{ name = "Automated Agent" }
8+
]
9+
dependencies = [
10+
"futu-api>=7.2.4600",
11+
"pydantic>=2.7",
12+
"openai>=1.3",
13+
"httpx>=0.27",
14+
"python-dotenv>=1.0"
15+
]
16+
17+
[project.optional-dependencies]
18+
dev = [
19+
"pytest>=7.4"
20+
]
21+
22+
[build-system]
23+
requires = ["setuptools>=61.0"]
24+
build-backend = "setuptools.build_meta"

src/main.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""Command line entry point for the automated trading workflow."""
2+
3+
from __future__ import annotations
4+
5+
import argparse
6+
import asyncio
7+
import logging
8+
import os
9+
import signal
10+
from contextlib import suppress
11+
from typing import List, Optional
12+
13+
from dotenv import load_dotenv
14+
15+
from trading_bot import (
16+
DeepSeekLLMClient,
17+
FutuDataFetcher,
18+
OrderExecutor,
19+
PromptFormatter,
20+
TradingScheduler,
21+
)
22+
from trading_bot.order_executor import resolve_trading_market, resolve_trading_mode
23+
24+
LOGGER = logging.getLogger(__name__)
25+
26+
27+
def parse_args(argv: List[str] | None = None) -> argparse.Namespace:
28+
parser = argparse.ArgumentParser(description="Automated trading pipeline")
29+
parser.add_argument("symbols", nargs="*", help="Symbols to monitor, e.g. SH.000300")
30+
parser.add_argument("--interval", type=int, default=None, help="Polling interval in seconds")
31+
parser.add_argument("--language", choices=["en", "zh"], default="en", help="Prompt language")
32+
parser.add_argument("--host", default=None, help="Futu OpenD host")
33+
parser.add_argument("--quote-port", type=int, default=None, help="Futu quote port")
34+
parser.add_argument("--trade-port", type=int, default=None, help="Futu trade port")
35+
parser.add_argument("--log-level", default="INFO", help="Logging level")
36+
parser.add_argument(
37+
"--trading-mode",
38+
choices=["paper", "live"],
39+
default=None,
40+
help="Trading environment to use (paper or live)",
41+
)
42+
parser.add_argument(
43+
"--trading-market",
44+
choices=["hk", "us", "cn"],
45+
default=None,
46+
help="Market to route orders to",
47+
)
48+
parser.add_argument(
49+
"--trade-password",
50+
default=None,
51+
help="Trading password to unlock the account",
52+
)
53+
parser.add_argument(
54+
"--account-id",
55+
default=None,
56+
help="Specific trading account identifier",
57+
)
58+
return parser.parse_args(argv)
59+
60+
61+
def _get_int_setting(env_name: str, cli_value: int | None, default: int) -> int:
62+
if cli_value is not None:
63+
return cli_value
64+
env_value = os.getenv(env_name)
65+
if env_value is None or env_value.strip() == "":
66+
return default
67+
try:
68+
return int(env_value)
69+
except ValueError as exc:
70+
raise ValueError(f"Environment variable {env_name} must be an integer, got {env_value!r}") from exc
71+
72+
73+
def _get_str_setting(env_name: str, cli_value: Optional[str], default: Optional[str] = None) -> Optional[str]:
74+
if cli_value is not None and cli_value.strip() != "":
75+
return cli_value
76+
env_value = os.getenv(env_name)
77+
if env_value is None or env_value.strip() == "":
78+
return default
79+
return env_value
80+
81+
82+
def _resolve_symbols(cli_symbols: List[str]) -> List[str]:
83+
if cli_symbols:
84+
return cli_symbols
85+
symbols: List[str] = []
86+
for env_name in ("HK_SYMBOLS", "CN_SYMBOLS", "US_SYMBOLS"):
87+
env_value = os.getenv(env_name, "")
88+
symbols.extend([s.strip() for s in env_value.split(",") if s.strip()])
89+
if symbols:
90+
return symbols
91+
raise ValueError(
92+
"No symbols specified. Provide symbols via CLI arguments or set HK_SYMBOLS/CN_SYMBOLS/US_SYMBOLS in the environment."
93+
)
94+
95+
96+
async def main_async(args: argparse.Namespace) -> None:
97+
logging.basicConfig(level=getattr(logging, args.log_level.upper(), logging.INFO))
98+
load_dotenv()
99+
100+
host = args.host or os.getenv("FUTU_HOST", "127.0.0.1")
101+
quote_port = _get_int_setting("FUTU_QUOTE_PORT", args.quote_port, 11111)
102+
trade_port = _get_int_setting("FUTU_TRADE_PORT", args.trade_port, 11111)
103+
interval = _get_int_setting("POLL_INTERVAL_SECONDS", args.interval, 180)
104+
symbols = _resolve_symbols(args.symbols)
105+
trading_mode = _get_str_setting("FUTU_TRADING_MODE", args.trading_mode, "paper")
106+
trading_market = _get_str_setting("FUTU_TRADING_MARKET", args.trading_market, "hk")
107+
trade_password = _get_str_setting("FUTU_TRADE_PASSWORD", args.trade_password)
108+
account_id = _get_str_setting("FUTU_ACCOUNT_ID", args.account_id)
109+
futu_app_id = _get_str_setting("FUTU_OPENAPI_APP_ID", None)
110+
futu_app_secret = _get_str_setting("FUTU_OPENAPI_APP_SECRET", None)
111+
112+
data_fetcher = FutuDataFetcher(host=host, quote_port=quote_port, account_port=trade_port)
113+
prompt_formatter = PromptFormatter(language=args.language)
114+
llm_client = DeepSeekLLMClient()
115+
order_executor = OrderExecutor(
116+
host=host,
117+
port=trade_port,
118+
trd_env=resolve_trading_mode(trading_mode or "paper"),
119+
trd_market=resolve_trading_market(trading_market or "hk"),
120+
trade_password=trade_password,
121+
account_id=account_id,
122+
app_id=futu_app_id,
123+
app_secret=futu_app_secret,
124+
)
125+
scheduler = TradingScheduler(
126+
data_fetcher=data_fetcher,
127+
prompt_formatter=prompt_formatter,
128+
llm_client=llm_client,
129+
order_executor=order_executor,
130+
symbols=symbols,
131+
interval_seconds=interval,
132+
)
133+
134+
loop = asyncio.get_running_loop()
135+
stop_event = asyncio.Event()
136+
137+
def _handle_stop(*_: object) -> None:
138+
LOGGER.info("Received stop signal. Shutting down...")
139+
stop_event.set()
140+
141+
for sig in (signal.SIGINT, signal.SIGTERM):
142+
loop.add_signal_handler(sig, _handle_stop)
143+
144+
scheduler_task = asyncio.create_task(scheduler.start())
145+
stop_task = asyncio.create_task(stop_event.wait())
146+
147+
done, pending = await asyncio.wait({scheduler_task, stop_task}, return_when=asyncio.FIRST_COMPLETED)
148+
if stop_task in done:
149+
LOGGER.info("Stop event acknowledged.")
150+
if scheduler_task in done:
151+
exc = scheduler_task.exception()
152+
if exc:
153+
raise exc
154+
155+
stop_event.set()
156+
await scheduler.stop()
157+
scheduler.shutdown()
158+
159+
for task in pending:
160+
task.cancel()
161+
with suppress(asyncio.CancelledError):
162+
await task
163+
scheduler_task.cancel()
164+
with suppress(asyncio.CancelledError):
165+
await scheduler_task
166+
stop_task.cancel()
167+
with suppress(asyncio.CancelledError):
168+
await stop_task
169+
170+
171+
def main() -> None:
172+
args = parse_args()
173+
try:
174+
asyncio.run(main_async(args))
175+
except KeyboardInterrupt:
176+
LOGGER.info("Interrupted by user")
177+
except ValueError as exc:
178+
LOGGER.error("%s", exc)
179+
raise SystemExit(1) from exc
180+
181+
182+
if __name__ == "__main__":
183+
main()

src/trading_bot/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Trading bot package integrating data collection, prompt formatting, LLM decisioning, and order execution."""
2+
3+
from .data_models import MarketDataPayload, AccountData, TradingDecision, OrderRequest, OrderResponse
4+
from .data_fetcher import FutuDataFetcher
5+
from .prompt_formatter import PromptFormatter
6+
from .llm_client import DeepSeekLLMClient
7+
from .order_executor import OrderExecutor, resolve_trading_market, resolve_trading_mode
8+
from .scheduler import TradingScheduler
9+
10+
__all__ = [
11+
"MarketDataPayload",
12+
"AccountData",
13+
"TradingDecision",
14+
"OrderRequest",
15+
"OrderResponse",
16+
"FutuDataFetcher",
17+
"PromptFormatter",
18+
"DeepSeekLLMClient",
19+
"OrderExecutor",
20+
"resolve_trading_mode",
21+
"resolve_trading_market",
22+
"TradingScheduler",
23+
]

0 commit comments

Comments
 (0)