Skip to content

[Bug] Undocumented per-symbol rate limit on Candlesticks API causes 301606 errors #456

@AliguOuget

Description

@AliguOuget

[Bug] Undocumented per-symbol rate limit on Candlesticks API causes 301606 errors

问题描述

在使用 Longbridge OpenAPI SDK 调用 Candlesticks 接口时,发现存在一个未记录在文档中的 per-symbol 频率限制:当同一 symbol 在约 57ms 内被请求两次时,第二次会返回 code:301606 message:request rate limit

这个限制与官方文档中的全局频率限制(10 req/s)相互独立,即使全局窗口远未到达上限(本次复现时仅使用 6/10 的请求量),也会被触发。

具体场景(可在低延迟服务器上稳定复现):

  1. 对同一 symbol(如 TSLA.US)同时发起两个 Candlesticks 请求(并发/串行均可)
  2. 第一个请求成功返回(约 7-11ms)
  3. 第二个请求在第一个完成约 57ms 后发出,被返回 301606(4ms 内即拒绝,为服务端主动限流)

问题:

  1. per-symbol 的 Candlesticks 调用冷却时间到底是多少毫秒?
  2. 这个限制是否有官方文档说明?
  3. 对同一 symbol 同时请求不同 count(如 count=5 和 count=1),是否应该受此限制?

代码例子

package main

import (
    "context"
    "fmt"
    "sync"
    "time"

    lbconfig "github.com/longportapp/openapi-go/config"
    "github.com/longportapp/openapi-go/quote"
)

func candlesticks(client *quote.QuoteContext, cycleStart time.Time, label, symbol string, limit int32) error {
    t := time.Now()
    result, err := client.Candlesticks(context.Background(), symbol, quote.Period(1), limit, quote.AdjustTypeNo)
    elapsed := time.Since(t)
    total := time.Since(cycleStart)

    icon := "✅"
    detail := fmt.Sprintf("count=%d", len(result))
    if err != nil {
        icon = "❌"
        detail = fmt.Sprintf("err=%v", err)
    }
    fmt.Printf("  [T+%6.1fms] %s %-30s %s (API耗时=%.0fms)\n",
        float64(total.Microseconds())/1000, icon, label, detail,
        float64(elapsed.Microseconds())/1000)
    return err
}

func main() {
    cfg := &lbconfig.Config{
        AppKey:      "YOUR_APP_KEY",
        AppSecret:   "YOUR_APP_SECRET",
        AccessToken: "YOUR_ACCESS_TOKEN",
    }

    client, err := quote.NewFromCfg(cfg)
    if err != nil {
        panic(err)
    }
    defer client.Close()

    fmt.Println("=== Reproducing per-symbol rate limit (301606) ===")
    cycleStart := time.Now()
    var wg sync.WaitGroup

    // 并发:独立请求 TSLA.US(limit=5) + 9988.HK(limit=5)
    for _, sym := range []string{"TSLA.US", "9988.HK"} {
        sym := sym
        wg.Add(1)
        go func() {
            defer wg.Done()
            candlesticks(client, cycleStart, "concurrent "+sym+" limit=5", sym, 5)
        }()
    }

    // 串行批量:NVDA.US → 700.HK → 9988.HK → TSLA.US
    // 模拟批量接口内部对多个 symbol 依次调用 Candlesticks(等每个响应才发下一个)
    wg.Add(1)
    go func() {
        defer wg.Done()
        for _, sym := range []string{"NVDA.US", "700.HK", "9988.HK", "TSLA.US"} {
            candlesticks(client, cycleStart, "serial batch "+sym+" limit=1", sym, 1)
        }
    }()

    wg.Wait()
}

注意:此问题需在低延迟网络环境下才能稳定复现(服务器靠近长桥节点,API 响应时间约 7-11ms)。
在高延迟网络(如本地开发机,响应时间约 70ms)下,串行到 TSLA.US 时已超出冷却窗口,不会触发。


错误信息

在服务器(香港/新加坡节点)上运行的实际输出:

=== Reproducing per-symbol rate limit (301606) ===
  [T+   7.4ms] ✅ concurrent NVDA.US limit=1        count=1 (API耗时=7ms)
  [T+  11.3ms] ✅ concurrent TSLA.US limit=5        count=5 (API耗时=11ms)
  [T+  11.4ms] ✅ concurrent 9988.HK limit=5        count=5 (API耗时=11ms)
  [T+  52.0ms] ✅ serial batch 700.HK  limit=1      count=1 (API耗时=45ms)
  [T+  68.3ms] ✅ serial batch 9988.HK limit=1      count=1 (API耗时=16ms)
  [T+  72.5ms] ❌ serial batch TSLA.US limit=1      err=longbridge protocol api error, status:3 code:301606 message:request rate limit (API耗时=4ms)

时序分析:

事件 时间
第 1 次 TSLA.US (limit=5) 完成 T+11.3ms
第 2 次 TSLA.US (limit=1) 发出 T+68.5ms
两次请求间隔 ~57ms
第 2 次响应耗时 4ms(服务端立即拒绝,非超时)
全局请求数(1秒窗口内) 6 / 10(未超限)

关键点:

  • 第 1 次和第 2 次调用的是完全相同的 API(Candlesticks),仅 count 参数不同(5 vs 1)
  • 两次请求间隔仅约 57ms,未超过任何已知文档限制
  • 全局 10 req/s 窗口内总请求数为 6,远未到达上限
  • 301606 在 4ms 内即返回,确认为服务端主动限流(非网络问题)

你的环境信息

  • 操作系统:Linux (x86_64)
  • 开发语言:Go
  • SDK 版本号:github.com/longportapp/openapi-go v0.17.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions