Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 105 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Longbridge FS 是一个基于文件系统的股票交易框架,通过读写文

- **文件驱动** — 通过文件读写完成所有交易操作,AI Agent 无需学习 API
- **AI 友好** — JSON 输出,天然适配 AI Agent 的文件操作能力
- **实时行情** — 支持 WebSocket 订阅,高效获取实时行情推送
- **审计追踪** — 所有交易记录在 beancount 格式的 append-only 账本中
- **盈亏追踪** — 自动生成 `pnl.json` 和 `portfolio.json`,实时追踪持仓盈亏
- **风控止损** — 配置 `risk_control.json` 实现自动止损/止盈
Expand Down Expand Up @@ -58,7 +59,9 @@ longbridge-fs/
│ │ └── blocks/ # 归档区块
│ └── quote/
│ ├── hold/{SYMBOL}/ # 行情数据 (.txt + .json)
│ ├── track/ # 行情触发器
│ ├── track/ # 行情触发器(一次性拉取)
│ ├── subscribe/ # WebSocket 订阅请求
│ ├── unsubscribe/ # WebSocket 取消订阅请求
│ └── portfolio.json # 组合总览
├── Makefile
├── go.mod
Expand Down Expand Up @@ -140,27 +143,72 @@ Controller 会自动检测新 ORDER 并执行,结果追加为 EXECUTION 或 RE

### 查询行情

#### 方式一:WebSocket 实时订阅(推荐)

通过 WebSocket 订阅后,行情数据会自动实时更新,无需手动触发:

```bash
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果文件中堆积了很多数据,模型在查看的时候,会全部 stdout 出来,然后把上下文窗口占满

应该是由分页机制,cat 设置数据长度 limit,可以通过参数查看下一页

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added pagination examples throughout the documentation to prevent context overflow:

  • K-line data: jq '.[0:10]' file.json for slicing arrays
  • Beancount ledger: tail -50, head -50, sed -n '100,150p' for viewing ranges
  • PnL/Portfolio: jq '.positions[0:5]' when many holdings

Small files like overview.json are noted as safe for direct cat viewing.

Commit: 9d894ab

# 订阅实时行情
touch fs/quote/subscribe/AAPL.US
touch fs/quote/subscribe/700.HK

# Controller 会自动处理订阅请求
# 订阅成功后,overview.json 和 overview.txt 会自动更新
cat fs/quote/hold/AAPL.US/overview.json # 实时更新的行情(文件较小,适合直接查看)

# 取消订阅
touch fs/quote/unsubscribe/AAPL.US
```

**优势**:
- 高效:WebSocket 长连接,延迟低
- 实时:价格变动时自动推送更新
- 省资源:无需轮询,减少 API 调用

#### 方式二:一次性拉取(按需获取)

适合需要历史 K 线、分时等完整行情数据的场景:

```bash
# 触发行情获取
touch fs/quote/track/AAPL.US

# Controller 获取后数据在 hold 目录,track 文件被自动删除
cat fs/quote/hold/AAPL.US/overview.json # 实时报价 (JSON)
cat fs/quote/hold/AAPL.US/overview.txt # 实时报价 (文本)
cat fs/quote/hold/AAPL.US/D.json # 日K (120天, JSON)
cat fs/quote/hold/AAPL.US/W.json # 周K (52周, JSON)
cat fs/quote/hold/AAPL.US/5D.json # 5分钟K线 (JSON)
cat fs/quote/hold/AAPL.US/intraday.json # 分时数据 (JSON)
cat fs/quote/hold/AAPL.US/overview.json # 实时报价 (JSON,小文件)
cat fs/quote/hold/AAPL.US/overview.txt # 实时报价 (文本,小文件)

# 查看历史 K 线数据(使用分页避免输出过多)
head -20 fs/quote/hold/AAPL.US/D.json # 查看前 20 行
tail -20 fs/quote/hold/AAPL.US/D.json # 查看后 20 行
jq '.[0:10]' fs/quote/hold/AAPL.US/D.json # 查看前 10 条 K 线数据
jq '.[-10:]' fs/quote/hold/AAPL.US/D.json # 查看最后 10 条 K 线数据

# 其他 K 线文件
jq '.[0:10]' fs/quote/hold/AAPL.US/W.json # 周K 前 10 条
jq '.[0:10]' fs/quote/hold/AAPL.US/5D.json # 5分钟K 前 10 条
jq '.[0:10]' fs/quote/hold/AAPL.US/intraday.json # 分时数据前 10 条
```

**使用建议**:
- 使用 `subscribe/` 订阅需要实时监控的股票(如持仓股票、关注列表)
- 使用 `track/` 按需获取历史数据(如回测、分析)
- 两种方式可以同时使用,互不影响

### 查看盈亏

```bash
# PnL 报告 (持仓 + 当前价格 → 未实现盈亏)
cat fs/account/pnl.json

# 如果持仓较多,使用 jq 分页查看
jq '.positions[0:5]' fs/account/pnl.json # 查看前 5 个持仓
jq '.positions[-5:]' fs/account/pnl.json # 查看最后 5 个持仓

# 组合总览 (所有持仓 + 行情)
cat fs/quote/portfolio.json

# 如果持仓较多,使用 jq 分页查看
jq '.holdings[0:10]' fs/quote/portfolio.json # 查看前 10 个持仓
```

### 风控止损
Expand Down Expand Up @@ -195,6 +243,35 @@ make deps # 下载依赖

## 进阶功能

### WebSocket 实时行情订阅

系统支持两种行情获取方式:

1. **WebSocket 订阅**:适合需要实时监控的场景
- 订阅后自动推送更新,无需轮询
- 低延迟,高效率
- 适合监控持仓、关注列表

2. **一次性拉取**:适合按需查询的场景
- 通过 `track/` 目录触发
- 获取完整数据(K线、分时等)
- 适合数据分析、回测

订阅示例:

```bash
# 订阅实时行情
touch fs/quote/subscribe/AAPL.US

# 订阅成功后,每当 AAPL.US 价格变化时
# fs/quote/hold/AAPL.US/overview.json 会自动更新

# 取消订阅
touch fs/quote/unsubscribe/AAPL.US
```

**注意**:WebSocket 订阅仅更新 `overview.json` 和 `overview.txt`。如需 K 线、分时等数据,请使用 `track/` 方式。

### 批量订单处理

Controller 会批量处理所有未执行的 ORDER,每个订单处理完成后追加结果:
Expand Down Expand Up @@ -250,6 +327,27 @@ fs/trade/blocks/

主账本文件 `beancount.txt` 只保留未执行的订单,保持文件精简。

### 查看账本记录(分页)

由于 `beancount.txt` 是 append-only 账本,随着交易增加会不断增长。建议使用分页方式查看:

```bash
# 查看最后 50 行(最近的订单)
tail -50 fs/trade/beancount.txt

# 查看前 50 行(最早的订单)
head -50 fs/trade/beancount.txt

# 查看特定范围的行(例如第 100-150 行)
sed -n '100,150p' fs/trade/beancount.txt

# 搜索特定股票的订单
grep "AAPL.US" fs/trade/beancount.txt | tail -20

# 查看归档的历史区块
tail -50 fs/trade/blocks/block_0001.txt
```

### 行情数据格式

行情数据同时提供 JSON 和文本两种格式:
Expand Down
18 changes: 17 additions & 1 deletion cmd/longbridge-fs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ func cmdInit() {
filepath.Join(*root, "trade", "blocks"),
filepath.Join(*root, "quote", "hold"),
filepath.Join(*root, "quote", "track"),
filepath.Join(*root, "quote", "subscribe"),
filepath.Join(*root, "quote", "unsubscribe"),
filepath.Join(*root, "quote", "market"),
}
for _, d := range dirs {
Expand Down Expand Up @@ -133,6 +135,7 @@ func cmdController() {

var tc *trade.TradeContext
var qc *quote.QuoteContext
var subManager *market.SubscriptionManager
useMock := *mock

if !useMock {
Expand Down Expand Up @@ -168,6 +171,12 @@ func cmdController() {
log.Println("running in MOCK mode (no API calls)")
}

// Initialize subscription manager
subManager = market.NewSubscriptionManager(qc, *root)
if qc != nil {
log.Println("WebSocket subscription manager initialized")
}

log.Printf("controller started: root=%s interval=%s compact-after=%d", *root, *interval, *compactAfter)

executedCount := 0
Expand Down Expand Up @@ -202,7 +211,14 @@ func cmdController() {
}
}

// Refresh quotes (only with real API)
// Process WebSocket subscription requests (subscribe/unsubscribe)
if subManager != nil {
if err := subManager.ProcessSubscriptions(ctx); err != nil {
log.Printf("subscription processing failed: %v", err)
}
}

// Refresh quotes via track files (one-shot poll-based)
if qc != nil {
market.RefreshQuotes(ctx, qc, *root)
}
Expand Down
Loading