sj-trading-mcp
by scorpio-su
README.md
# sj-trading-mcp
永豐 Shioaji **MCP Server**,讓 Claude(或任何 MCP 客戶端)能直接操作台灣股票、期貨與選擇權交易,並查詢即時行情與帳務資料。
遵循 **OOP + Clean Code(Uncle Bob)** 原則,預設執行於 `simulation=True` 模擬環境。
---
## 專案結構
```
sj_trading-mcp/
├── config/
│ ├── credentials.json.example ← 金鑰範本(JSON 格式)
│ ├── credentials.txt.example ← 金鑰範本(key=value 格式)
│ ├── credentials.json ← 實際金鑰(git ignored)
│ └── credentials.txt ← 實際金鑰(git ignored)
├── src/
│ ├── core/
│ │ ├── credentials.py ← 金鑰載入器(支援 .json / .txt 自動判斷)
│ │ └── api_client.py ← Shioaji 連線封裝(支援 with 語法)
│ ├── market/
│ │ ├── stock_market.py ← 股票行情:快照、K 線、Tick、資券、券源
│ │ ├── futures_market.py ← 期貨行情:歷史 K 線(含連續合約)
│ │ └── market_info.py ← 市場資訊:掃描器排行、處置股、注意股
│ ├── accounting/
│ │ ├── stock_account.py ← 股票帳務:餘額、交易額度、交割資訊
│ │ ├── futures_account.py ← 期貨帳務:保證金查詢
│ │ └── common_account.py ← 跨帳戶:損益查詢、持倉明細
│ ├── trading/
│ │ ├── stock_trader.py ← 股票交易:下單、改單、市價單、查持倉
│ │ ├── futures_trader.py ← 期貨交易:開平倉、改單、查持倉
│ │ └── options_trader.py ← 選擇權交易:下單、刪單、查持倉
│ ├── option_strategy/
│ │ ├── _types.py ← 資料模型:Action / Right / InstrumentType / Leg / Strategy
│ │ ├── _pure_option.py ← 純選擇權策略(45 種)
│ │ ├── _stock_legged.py ← 含股票腿策略(6 種)
│ │ ├── _future_legged.py ← 含期貨腿策略(7 種)
│ │ └── strategies.py ← 策略總表 STRATEGIES + get_strategy()
│ └── grid_martingale_strategy/
│ ├── __init__.py ← 對外 export 兩策略 + factories
│ ├── base/ ← BaseStrategy、TradeJournal、RiskPolicy
│ ├── grid/ ← GridStrategy、GridConfig、build_grid_strategy
│ ├── martingale/ ← MartingaleStrategy、MartingaleConfig、build_martingale_strategy
│ ├── adapters/ ← MarketAdapter + stock/futures 實作
│ ├── risk/ ← GridRisk、MartingaleStockRisk、MartingaleFuturesRisk
│ ├── config/ ← 向後相容 re-export(grid/、martingale/ 為 canonical)
│ └── _types.py ← 共用 enum / dataclass
├── tests/
│ ├── test_stock.py ← 股票模擬測試
│ ├── test_futures.py ← 期貨模擬測試
│ ├── test_market.py ← 行情資料模擬測試
│ ├── test_accounting.py ← 帳務查詢模擬測試
│ └── test_options.py ← 選擇權模擬測試
├── .claude/
│ └── settings.json ← Claude Code MCP 設定
├── server.py ← MCP Server 主程式
├── main.py ← 快速端對端測試(含 grid / martingale)
├── Dockerfile
├── pyproject.toml
└── requirements.txt
```
---
## 設定金鑰
複製範本後填入真實值:
```bash
# JSON 格式
cp config/credentials.json.example config/credentials.json
# 或 txt 格式
cp config/credentials.txt.example config/credentials.txt
```
**僅行情 / 股票下單**(`credentials.json`,不需 CA):
```json
{
"api_key": "YOUR_API_KEY",
"secret_key": "YOUR_SECRET_KEY"
}
```
**期貨 / 選擇權下單**須填寫 CA 憑證欄位(模擬與真實模式皆需):
```json
{
"api_key": "YOUR_API_KEY",
"secret_key": "YOUR_SECRET_KEY",
"ca_path": "config/ca/YOUR_CA.pfx",
"ca_password": "YOUR_CA_PASSWORD",
"person_id": "YOUR_PERSON_ID"
}
```
> CA 欄位存在時,`ApiClient` 在連線後**立即啟動憑證**(無論模擬或真實模式)。
> TAIFEX(期貨交易所)即使在模擬環境也需要帳戶層級的 CA 簽署才能接受委託。
---
## Docker 啟動(推薦)
```bash
# 第一次 build
docker build -t sj-trading-mcp .
# 啟動(模擬模式)
docker run -d \
--name sj-trading-mcp \
-p 127.0.0.1:9090:9090 \
-v ./config:/app/config:ro \
-v ./logs:/app/logs \
--restart unless-stopped \
sj-trading-mcp
# 真實下單模式
docker run -d \
--name sj-trading-mcp \
-p 127.0.0.1:9090:9090 \
-v ./config:/app/config:ro \
-v ./logs:/app/logs \
-e SJ_SIMULATION=false \
--restart unless-stopped \
sj-trading-mcp
```
**停止 / 重啟:**
```bash
docker stop sj-trading-mcp
docker start sj-trading-mcp
```
**查 log:**
```bash
docker logs -f sj-trading-mcp
# 或查 log 檔
cat logs/server.log
```
---
## 整合 Claude Code
`.claude/settings.json` 已預設配置,在此專案目錄下開啟 Claude Code 即自動載入。
**CLI 加入(專案範圍):**
```bash
claude mcp add sj-trading --transport http http://127.0.0.1:9090/mcp --scope project
```
---
## MCP 工具清單
### 連線管理
| 工具 | 說明 |
| ------------------------- | -------------------------------------------------------- |
| `connect_api(simulation)` | 連線 API(`simulation=true` 預設模擬,`false` 真實下單) |
| `disconnect_api()` | 登出斷線 |
| `connection_status()` | 查詢連線狀態 |
### 股票交易
| 工具 | 說明 |
| --------------------------------------------------- | ------------------------- |
| `stock_buy(symbol, price, quantity)` | 整股限價買進(ROD) |
| `stock_sell(symbol, price, quantity)` | 整股限價賣出(ROD) |
| `stock_market_buy(symbol, quantity)` | 整股市價買進(MKT + IOC) |
| `stock_market_sell(symbol, quantity)` | 整股市價賣出(MKT + IOC) |
| `stock_buy_odd(symbol, price, quantity, intraday)` | 零股買進 |
| `stock_sell_odd(symbol, price, quantity, intraday)` | 零股賣出 |
| `stock_margin_buy(symbol, price, quantity)` | 融資買進 |
| `stock_margin_sell(symbol, price, quantity)` | 融資賣出/還券 |
| `stock_short_sell(symbol, price, quantity)` | 融券賣出 |
| `stock_short_cover(symbol, price, quantity)` | 融券回補 |
| `stock_cancel(order_id)` | 刪除委託單 |
| `stock_update_order(order_id, price, qty)` | 改價或減量(二選一) |
| `stock_list_trades()` | 查詢所有委託紀錄 |
| `stock_positions()` | 查詢股票持倉 |
| `stock_contract_info(symbol)` | 查詢合約基本資訊 |
### 期貨交易
| 工具 | 說明 |
| ---------------------------------------------- | ----------------------------- |
| `futures_open_long(symbol, price, quantity)` | 開多倉(Buy + OCType.New) |
| `futures_open_short(symbol, price, quantity)` | 開空倉(Sell + OCType.New) |
| `futures_close_long(symbol, price, quantity)` | 平多倉(Sell + OCType.Cover) |
| `futures_close_short(symbol, price, quantity)` | 平空倉(Buy + OCType.Cover) |
| `futures_cancel(order_id)` | 刪除委託單 |
| `futures_update_order(order_id, price, qty)` | 改價或減量 |
| `futures_positions()` | 查詢期貨持倉 |
| `futures_contract_info(symbol)` | 查詢合約基本資訊 |
### 選擇權交易
| 工具 | 說明 |
| ----------------------------------------------- | ---------------------------------------- |
| `options_buy(symbol, price, quantity, octype)` | 買進選擇權(LMT + ROD) |
| `options_sell(symbol, price, quantity, octype)` | 賣出選擇權(LMT + ROD) |
| `options_cancel(order_id)` | 刪除委託單 |
| `options_positions()` | 查詢選擇權/期貨持倉 |
| `options_contract_info(symbol)` | 查詢合約資訊(到期月、履約價、Call/Put) |
> `symbol` 格式:`TXO20260620200C`(商品代碼 + YYYYMMDD + 履約價 + C/P)
> `octype`:`"Auto"`(預設)/ `"NewPosition"`(新倉)/ `"Cover"`(平倉)
### 選擇權策略(多腿組合)
| 工具 | 說明 |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `strategy_list()` | 列出所有可用策略(純選擇權 45 種、含股票腿 6 種、含期貨腿 7 種,共 58 種) |
| `strategy_info(strategy_id)` | 查詢策略腿部結構(方向、標的類型、Put/Call、履約價位、口數) |
| `strategy_execute(strategy_id, leg_symbols, leg_prices, base_qty)` | 執行多腿策略,逐腿送出委託 |
> 使用流程:`strategy_list()` → `strategy_info()` 確認腿部順序 → `strategy_execute()` 下單
> `leg_symbols` / `leg_prices` 長度須與策略腿數一致;`base_qty` 為口數乘數(預設 `1`)
### 網格策略(Grid)
| 工具 | 說明 |
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `grid_create(market_type, symbol, upper_price, lower_price, grid_count, grid_unit_qty, ...)` | 建立純網格策略,回傳 `grid-N` |
| `grid_start(strategy_id)` | 啟動(idle → running) |
| `grid_stop(strategy_id)` | 停止行情訂閱 |
| `grid_status(strategy_id)` | 查詢狀態(`strategy_type=grid`、格位數、triggered_levels) |
| `grid_rebalance(strategy_id)` | 重新對齊格位 |
| `grid_destroy(strategy_id)` | 停止並移除 |
| `grid_list()` | 列出所有網格策略 |
> **使用流程**:`grid_create(...)` → `grid_start('grid-1')` → `grid_status(...)` → `grid_stop` / `grid_destroy`
> `grid_create` **不再**包含馬丁參數;`grid_mode`:`"arithmetic"` / `"geometric"`;`direction`:`"LONG"` / `"SHORT"`(僅期貨)
### 馬丁格爾策略(Martingale)
| 工具 | 說明 |
| ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `martingale_create(market_type, symbol, mart_multiplier, mart_trigger_pct, mart_max_times, mart_initial_qty, mart_take_profit_pct, ...)` | 建立純馬丁策略,回傳 `mart-N` |
| `martingale_start(strategy_id)` | 啟動(idle → running) |
| `martingale_stop(strategy_id)` | 停止行情訂閱 |
| `martingale_status(strategy_id)` | 查詢狀態(layer_count、avg_cost、frozen、liquidating) |
| `martingale_destroy(strategy_id)` | 停止並移除 |
| `martingale_list()` | 列出所有馬丁策略 |
> **使用流程**:`martingale_create(...)` → `martingale_start('mart-1')` → `martingale_status(...)` → `martingale_stop` / `martingale_destroy`
> `qty_mode`:`"multiply"` / `"linear"`;策略 ID 前綴為 `mart-`(與網格的 `grid-` 分開管理)
> 網格與馬丁為**完全獨立**策略:`grid/` 與 `martingale/` 互不 import,可各自建立、各自運行。原 `GridMartingaleStrategy` / `build_strategy` 已移除。
### 行情資料
| 工具 | 說明 |
| ------------------------------------------------- | ------------------------------------------------------------------- |
| `stock_snapshot(symbols)` | 即時快照(最多 500 檔,含現價 / 漲跌 / 量) |
| `stock_kbars(symbol, start, end)` | 歷史 K 棒(1 分鐘),`start`/`end` 格式 `YYYY-MM-DD` |
| `stock_ticks(symbol, date, query_type, ...)` | 歷史 Tick,`query_type`: `"AllDay"` / `"RangeTime"` / `"LastCount"` |
| `stock_credit_enquiries(symbols)` | 個股融資融券餘額 |
| `stock_short_sources(symbols)` | 可借券來源與張數 |
| `futures_kbars(symbol, start, end)` | 期貨歷史 K 棒(支援連續合約 `TXFR1`/`TXFR2`) |
| `market_scanners(scanner_type, ascending, count)` | 市場排行掃描器 |
| `market_punish()` | 處置股清單 |
| `market_notice()` | 注意股清單 |
> `scanner_type` 可用值:`"ChangePercentRank"` / `"ChangePriceRank"` / `"DayRangeRank"` / `"VolumeRank"` / `"AmountRank"`
### 帳務查詢
| 工具 | 說明 |
| ------------------------------------------------------ | ------------------------------------ |
| `account_balance()` | 股票帳戶可用餘額 |
| `stock_trading_limits()` | 股票交易額度(含融資 / 融券額度) |
| `list_settlements()` | 交割資訊(T / T+1 / T+2) |
| `futures_margin()` | 期貨保證金(可用保證金、風險指標等) |
| `list_profit_loss(account_type, begin_date, end_date)` | 已實現損益,按日期區間查詢 |
| `list_position_detail(account_type, detail_id)` | 持倉明細(含成本價、進場日) |
> `account_type`:`"stock"` 或 `"futopt"`
---
## 核心模組說明
### `CredentialsLoader`(`src/core/credentials.py`)
- 單一職責:只負責讀取與驗證金鑰
- 以 `frozen=True` dataclass 保存,防止意外修改
- 缺少欄位時拋出含明確說明的 `KeyError`
### `ApiClient`(`src/core/api_client.py`)
- 封裝 `login` / `logout` 流程
- 支援 `with` 語法,確保 `logout` 必然執行
- 未連線前訪問 `.api` 屬性拋出 `RuntimeError`
- 所有模組共用同一 `ApiClient` 實例(避免超過 5 connections/person ID 上限)
- 若 credentials 含 CA 欄位,則**模擬與真實模式皆啟動憑證**(期貨 / 選擇權下單需要)
### Trading 模組(`src/trading/`)
| 模組 | 類別 | 主要方法 |
| ------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `stock_trader.py` | `StockTrader` | buy / sell / market_buy / market_sell / buy_odd / sell_odd / margin_buy / short_sell / cancel / update_order / list_trades / list_positions |
| `futures_trader.py` | `FuturesTrader` | open_long / open_short / close_long / close_short / cancel / update_order / list_positions |
| `options_trader.py` | `OptionsTrader` | buy / sell / cancel / contract_info / list_positions |
### Market 模組(`src/market/`)
| 模組 | 類別 | 主要方法 |
| ------------------- | --------------- | ----------------------------------------------------------------------------------- |
| `stock_market.py` | `StockMarket` | stock_snapshot / stock_kbars / stock_ticks / credit_enquiries / short_stock_sources |
| `futures_market.py` | `FuturesMarket` | futures_kbars |
| `market_info.py` | `MarketInfo` | market_scanners / market_punish / market_notice |
### Accounting 模組(`src/accounting/`)
| 模組 | 類別 | 主要方法 |
| -------------------- | ---------------- | --------------------------------------------------- |
| `stock_account.py` | `StockAccount` | account_balance / trading_limits / list_settlements |
| `futures_account.py` | `FuturesAccount` | futures_margin |
| `common_account.py` | `CommonAccount` | list_profit_loss / list_position_detail |
### OptionStrategy 模組(`src/option_strategy/`)
純資料驅動的多腿策略定義層,不依賴 Shioaji API,可獨立測試。
| 類型 | 策略數量 | 範例 |
| ------------------------------- | -------- | ----------------------------------------------------------------------------- |
| 純選擇權(`_pure_option.py`) | 45 種 | long_call / straddle / iron_condor / calendar_call_spread / double_diagonal … |
| 含股票腿(`_stock_legged.py`) | 6 種 | covered_call / protective_put / collar / cash_secured_put … |
| 含期貨腿(`_future_legged.py`) | 7 種 | long_synthetic_future / protective_futures_put / futures_collar … |
- `Strategy` — frozen dataclass,持有 `strategy_id` 與 `legs: tuple[Leg, ...]`
- `OptionLeg` / `StockLeg` / `FutureLeg` — 各腿類型,描述 action(BUY/SELL)、qty、right、strike、expiry
- `get_strategy(strategy_id)` — 依 ID 取得策略,找不到時拋出 `KeyError`
- `server.py` 使用此模組實作 `strategy_list` / `strategy_info` / `strategy_execute` 三個 MCP 工具
### 策略模組(`src/grid_martingale_strategy/`)
網格與馬丁已拆分為兩個獨立策略,共用 `base/` 骨架(Template Method)與 `adapters/` 基礎設施。
`grid/` 與 `martingale/` **互不 import**,各自持有專屬 config 與 position book。
| 子模組 | 職責 |
| ------------- | ----------------------------------------------------------------------------------------- |
| `base/` | `BaseStrategy`、`TradeJournal`、`RiskPolicy`(`GridRiskPolicy` / `MartingaleRiskPolicy`) |
| `grid/` | `GridStrategy`、`GridConfig`、`GridPositionBook`、`build_grid_strategy()` |
| `martingale/` | `MartingaleStrategy`、`MartingaleConfig`、`LayerBook`、`build_martingale_strategy()` |
| `adapters/` | `MarketAdapter` + stock/futures 實作 |
| `risk/` | `GridRisk`、`MartingaleStockRisk`、`MartingaleFuturesRisk` |
**對外 API(`__init__.py`):**
| 符號 | 說明 |
| --------------------------------------------------------------------- | ------------------ |
| `GridStrategy` / `build_grid_strategy` | 純網格 |
| `MartingaleStrategy` / `build_martingale_strategy` | 純馬丁 |
| `GridConfig` / `MartingaleConfig` | 各自設定 dataclass |
| `MarketType` / `Direction` / `GridMode` / `QtyMode` / `StrategyState` | 共用列舉 |
> `MartingaleConfig` 第一個參數為 `symbol`(拆分後新增)。
> 舊路徑 `config/grid_config.py`、`config/martingale_config.py` 仍 re-export,canonical 位置分別在 `grid/`、`martingale/`。
```python
from src.grid_martingale_strategy import (
build_grid_strategy, build_martingale_strategy,
GridConfig, MartingaleConfig,
MarketType, Direction, GridMode, QtyMode,
)
grid_cfg = GridConfig("2330", 1000.0, 800.0, 10, GridMode.ARITHMETIC, grid_unit_qty=1)
grid = build_grid_strategy(MarketType.STOCK, client, grid_cfg)
grid.initialize(); grid.start()
mart_cfg = MartingaleConfig("2330", 2.0, -0.05, 3, 2, 0.03, QtyMode.MULTIPLY)
mart = build_martingale_strategy(MarketType.STOCK, client, mart_cfg)
mart.initialize(); mart.start()
```
---
## 執行測試
所有測試皆在 `simulation=True` 模擬模式下執行,**不會觸發真實交易**。
```bash
uv run python tests/test_stock.py
uv run python tests/test_futures.py
uv run python tests/test_market.py
uv run python tests/test_accounting.py
uv run python tests/test_options.py
```
| 測試檔 | 必要條件 | 備註 |
| -------------------- | ----------------------- | ---------------------------------------------------------- |
| `test_stock.py` | 僅 api_key / secret_key | 股票模擬完整測試 |
| `test_futures.py` | CA 憑證(建議) | 無 CA 或帳戶未在 TAIFEX 簽署時,下單步驟自動跳過並顯示警告 |
| `test_market.py` | 僅 api_key / secret_key | 行情查詢不需 CA |
| `test_accounting.py` | 僅 api_key / secret_key | 帳務查詢不需 CA |
| `test_options.py` | 僅 api_key / secret_key | 合約與持倉查詢;自動尋找當前有效 TXO 合約 |
**`main.py` 快速端對端測試**(含 Grid / Martingale):
```bash
python main.py # 全部測試(stock + futures + options + grid + martingale)
python main.py stock # 只測股票
python main.py futures # 只測期貨
python main.py options # 只測選擇權
python main.py grid # 只測純網格(現股)
python main.py grid_futures # 只測純網格(期貨)
python main.py martingale # 只測純馬丁(現股)
python main.py martingale_futures # 只測純馬丁(期貨)
```
---
## 環境變數
| 變數 | 預設 | 說明 |
| --------------- | --------- | -------------------- |
| `SJ_SIMULATION` | `true` | `false` 切換真實下單 |
| `MCP_HOST` | `0.0.0.0` | server 監聽 host |
| `MCP_PORT` | `9090` | server 監聽 port |
| `LOG_DIR` | `logs` | log 檔目錄 |
---
## Design Principles
- **SRP**:每個 class 只負責一件事(交易 / 行情 / 帳務明確分層)
- **依賴注入**:所有模組接收 `ApiClient`,不直接建立連線
- **共用連線**:`_Session` 管理單一 `ApiClient` 生命週期,防止超過連線數上限
- **語意方法名**:`open_long` / `close_short` 優於 `place_order(octype=...)`
- **不回傳 None**:錯誤以例外表達(`ValueError` / `RuntimeError`)
- **非同步行情與帳務**:市場資料與帳務工具使用 `async + run_in_executor` 避免卡頓
---
## ⚠️ 注意事項
- **預設 `SJ_SIMULATION=true`,絕對不會動用真實帳戶。**
- 期貨 / 選擇權委託(含模擬模式)需在 `credentials` 填入 CA 欄位,且帳戶需已在 TAIFEX 完成 CA 簽署。
- 切換真實模式前,請確認 CA 欄位(`ca_path`、`ca_password`、`person_id`)正確填寫。
- 真實下單操作不可逆,請在確認合約、價格、數量後再執行。
- 行情查詢限速:50 req / 5s;帳務查詢限速:25 req / 5s。
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues