diff --git a/api/QMT_API.py b/api/QMT_API.py index 829617e..9bf80de 100644 --- a/api/QMT_API.py +++ b/api/QMT_API.py @@ -279,11 +279,10 @@ class MarketDataExHandler(BaseHandler): class FullTickHandler(BaseHandler): def post(self): data = json.loads(self.request.body) - stocks = data.get('stocks', '') + stocks = data.get('stocks', []) if not stocks: raise HTTPError(400, "need args stocks") - code_list = [s.strip() for s in stocks.split(',')] - ret = safe_call(self.ctx().get_full_tick, code_list) + ret = safe_call(self.ctx().get_full_tick, stocks) if not ret: raise HTTPError(500, "获取分笔行情失败") self.write(json.dumps(ret, ensure_ascii=False, default=str)) @@ -1447,12 +1446,25 @@ def make_app(): # ============= Callback 注册 ============= def json_serializer(obj): - """自定义 JSON 序列化器""" if isinstance(obj, datetime.datetime): return obj.strftime("%Y-%m-%d %H:%M:%S") if isinstance(obj, datetime.date): return obj.strftime("%Y-%m-%d") - raise TypeError(f"Type {type(obj)} not serializable") + if hasattr(obj, 'to_dict'): + return obj.to_dict() + attrs = {} + for name in dir(obj): + if name.startswith('_'): + continue + try: + val = getattr(obj, name) + except Exception: + continue + if not callable(val): + attrs[name] = val + if attrs: + return attrs + return str(obj) def write_json(file_key, data,order_id:str=''): """ diff --git a/go-client/apps/cmd/main.go b/go-client/apps/cmd/main.go index f6c29a5..e5fc800 100644 --- a/go-client/apps/cmd/main.go +++ b/go-client/apps/cmd/main.go @@ -2,8 +2,10 @@ package main import ( "context" + "encoding/json" "fmt" "os" + "path/filepath" "sort" "strings" "time" @@ -12,23 +14,22 @@ import ( ) var ( - BaseURL = "http://127.0.0.1:10086" - Token = "QMTbyYanweidong" + BaseURL = "http://127.0.0.1:10086" + Token = "QMTbyYanweidong" AccountType = "stock" - PassCodes = []string{} - Timeout = 15 * time.Second + Timeout = 15 * time.Second ) func main() { - client := sdk.New(BaseURL, Token, AccountType, Timeout) + client := sdk.New(BaseURL, Token, Timeout).SetAccountType(AccountType) ctx, cancel := context.WithTimeout(context.Background(), Timeout) defer cancel() - assets, err := client.Assets(ctx, AccountType) + assets, err := client.Assets(ctx) if err != nil { fatal("获取资产失败: %v", err) } - positions, err := client.Positions(ctx, AccountType) + positions, err := client.Positions(ctx) if err != nil { fatal("获取持仓失败: %v", err) } @@ -54,12 +55,38 @@ func main() { ) } - printTicks(client, Timeout, PassCodes) + codes := loadPassCodes() + printTicks(client, codes) + // if _, err := client.Shutdown(ctx); err != nil { + // fatal("关闭服务失败: %v", err) + // } + // fmt.Println("【服务】已关闭") } -func printTicks(client *sdk.Client, timeout time.Duration, codes []string) { +func loadPassCodes() []string { + dir := strings.TrimSpace(os.Getenv("QMT_DATA_DIR")) + if dir == "" { + fatal("环境变量 QMT_DATA_DIR 为空") + } + path := filepath.Join(dir, "pass_codes.json") + raw, err := os.ReadFile(path) + if err != nil { + fatal("读取 %s 失败: %v", path, err) + } + var codes []string + if err := json.Unmarshal(raw, &codes); err != nil { + fatal("解析 %s 失败: %v", path, err) + } + return codes +} + +func printTicks(client *sdk.Client, codes []string) { fmt.Println(strings.Repeat("-", 80)) - ctx, cancel := context.WithTimeout(context.Background(), timeout) + if len(codes) == 0 { + fmt.Println("【行情】pass_codes.json 为空,跳过") + return + } + ctx, cancel := context.WithTimeout(context.Background(), Timeout) defer cancel() ticks, err := client.FullTick(ctx, codes) if err != nil { @@ -83,18 +110,6 @@ func printTicks(client *sdk.Client, timeout time.Duration, codes []string) { } } -func splitCSV(s string) []string { - parts := strings.Split(s, ",") - out := make([]string, 0, len(parts)) - for _, p := range parts { - p = strings.TrimSpace(p) - if p != "" { - out = append(out, p) - } - } - return out -} - func rawStr(m map[string]any, names ...string) string { for _, name := range names { if v, ok := m[name]; ok && v != nil { @@ -104,13 +119,6 @@ func rawStr(m map[string]any, names ...string) string { return "-" } -func envOr(key, fallback string) string { - if v := strings.TrimSpace(os.Getenv(key)); v != "" { - return v - } - return fallback -} - func fatal(format string, args ...any) { fmt.Fprintf(os.Stderr, format+"\n", args...) os.Exit(1) diff --git a/go-client/apps/zt/boot.go b/go-client/apps/zt/logic/boot.go similarity index 55% rename from go-client/apps/zt/boot.go rename to go-client/apps/zt/logic/boot.go index 6d09b17..dde78a1 100644 --- a/go-client/apps/zt/boot.go +++ b/go-client/apps/zt/logic/boot.go @@ -1,4 +1,4 @@ -package main +package logic import ( "context" @@ -6,10 +6,11 @@ import ( "strings" "time" + "big-qmt/go-client/libs" "big-qmt/go-client/sdk" ) -func overview(cfg Config, assets *sdk.Assets, positions []sdk.Position) { +func Overview(cfg Config, assets *sdk.Assets, positions []sdk.Position) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Printf("【时间】%s\n", time.Now().Format("2006-01-02 15:04:05")) fmt.Printf("【配置】account_id: %s host_key: %s open_money: %.0f\n", cfg.AccountID, cfg.HostKey, cfg.OpenMoney) @@ -31,8 +32,64 @@ func overview(cfg Config, assets *sdk.Assets, positions []sdk.Position) { } } -func runRound(ctx context.Context, client *sdk.Client, books *orderBook, cfg Config, assets *sdk.Assets, ticks map[string]sdk.Tick, positions []sdk.Position) { +func RunOnce(ctx context.Context, client *sdk.Client, books *OrderBook, cfg Config) { + if !tradingTime(time.Now()) { + return + } + roundCtx, cancel := context.WithTimeout(ctx, cfg.HTTPTimeout*4) + defer cancel() + + assets, err := client.Assets(roundCtx) + if err != nil { + logf("ERROR", "获取资产失败: %v", err) + return + } + positions, err := client.Positions(roundCtx) + if err != nil { + logf("ERROR", "获取持仓失败: %v", err) + return + } + signals := fetchSignal(cfg, "dcm_signal") + seen := map[string]struct{}{} + stockList := make([]string, 0, len(signals)+len(positions)) + addCode := func(code string) { + n := normalizeCode(code, "") + if n == "" { + n = strings.ToUpper(strings.TrimSpace(code)) + } + if n == "" { + return + } + if _, ok := seen[n]; ok { + return + } + seen[n] = struct{}{} + stockList = append(stockList, n) + } + for code := range signals { + addCode(code) + } + for _, p := range positions { + addCode(p.StockCode) + } + + ticks := map[string]sdk.Tick{} + if len(stockList) > 0 { + raw, err := client.FullTick(roundCtx, stockList) + if err != nil { + logf("ERROR", "获取行情失败: %v", err) + return + } + for code, tick := range raw { + ticks[normalizeCode(code, "")] = tick + ticks[code] = tick + } + } + runRound(roundCtx, client, books, cfg, assets, ticks, positions, signals) +} + +func runRound(ctx context.Context, client *sdk.Client, books *OrderBook, cfg Config, assets *sdk.Assets, ticks map[string]sdk.Tick, positions []sdk.Position, signals map[string]map[string]any) { books.cancelExpired(ctx, client, cfg) hold := positionCodes(positions) @@ -62,7 +119,7 @@ func runRound(ctx context.Context, client *sdk.Client, books *orderBook, cfg Con } } } - marketOK := marketAllowOpen(cfg) + marketOK := libs.AllowOpen(cfg.APIHost, cfg.HTTPTimeout) if len(openSignals) > 0 { openSignal(ctx, client, books, cfg, assets, ticks, openSignals, marketOK) } diff --git a/go-client/apps/zt/config.go b/go-client/apps/zt/logic/config.go similarity index 92% rename from go-client/apps/zt/config.go rename to go-client/apps/zt/logic/config.go index f316c93..d947d2d 100644 --- a/go-client/apps/zt/config.go +++ b/go-client/apps/zt/logic/config.go @@ -1,4 +1,4 @@ -package main +package logic import ( "os" @@ -29,14 +29,14 @@ type Config struct { ReboundThreshold float64 } -func loadConfig() Config { +func LoadConfig() Config { cfg := Config{ QMTBaseURL: env("QMT_BASE_URL", "http://127.0.0.1:10086"), QMTToken: env("QMT_TOKEN", "QMTbyYanweidong"), AccountType: env("QMT_ACCOUNT", "stock"), AccountID: env("ACCOUNT_ID", ""), HostKey: env("HOST_KEY", ""), - APIHost: strings.TrimRight(env("API_HOST", "http://139.224.247.176:13499"), "/"), + APIHost: strings.TrimRight(env("API_HOST", "http://go.apinb.com"), "/"), DataDir: env("DATA_DIR", "D:/qmt_strategy_state"), HTTPTimeout: durationEnv("HTTP_TIMEOUT_SEC", 5) * time.Second, OrderTimeout: durationEnv("ORDER_TIMEOUT_SEC", 60) * time.Second, @@ -55,10 +55,6 @@ func loadConfig() Config { logf("ERROR", "ACCOUNT_ID 为空") os.Exit(1) } - if strings.TrimSpace(cfg.HostKey) == "" { - logf("ERROR", "HOST_KEY 为空") - os.Exit(1) - } if cfg.MinCashRatio < 0 || cfg.MinCashRatio >= 1 { logf("ERROR", "MIN_CASH_RATIO 必须在 [0, 1)") os.Exit(1) diff --git a/go-client/apps/zt/log.go b/go-client/apps/zt/logic/log.go similarity index 90% rename from go-client/apps/zt/log.go rename to go-client/apps/zt/logic/log.go index dc0a2b9..d716260 100644 --- a/go-client/apps/zt/log.go +++ b/go-client/apps/zt/logic/log.go @@ -1,4 +1,4 @@ -package main +package logic import ( "fmt" diff --git a/go-client/apps/zt/open.go b/go-client/apps/zt/logic/open.go similarity index 97% rename from go-client/apps/zt/open.go rename to go-client/apps/zt/logic/open.go index b4ca305..3e11041 100644 --- a/go-client/apps/zt/open.go +++ b/go-client/apps/zt/logic/open.go @@ -1,4 +1,4 @@ -package main +package logic import ( "context" @@ -19,7 +19,7 @@ var openDip = struct { store map[string]dipWatch }{store: map[string]dipWatch{}} -func openSignal(ctx context.Context, client *sdk.Client, books *orderBook, cfg Config, assets *sdk.Assets, ticks map[string]sdk.Tick, openSignals map[string]map[string]any, marketOK bool) { +func openSignal(ctx context.Context, client *sdk.Client, books *OrderBook, cfg Config, assets *sdk.Assets, ticks map[string]sdk.Tick, openSignals map[string]map[string]any, marketOK bool) { if !marketOK { return } diff --git a/go-client/apps/zt/order.go b/go-client/apps/zt/logic/order.go similarity index 88% rename from go-client/apps/zt/order.go rename to go-client/apps/zt/logic/order.go index e34ef3d..b515e20 100644 --- a/go-client/apps/zt/order.go +++ b/go-client/apps/zt/logic/order.go @@ -1,4 +1,4 @@ -package main +package logic import ( "context" @@ -51,7 +51,7 @@ type submission struct { Tag string } -type orderBook struct { +type OrderBook struct { mu sync.Mutex cached []parsedOrder hasCache bool @@ -60,21 +60,21 @@ type orderBook struct { subs []submission } -func newOrderBook() *orderBook { - return &orderBook{ +func NewOrderBook() *OrderBook { + return &OrderBook{ buyLocks: map[string]time.Time{}, sellLocks: map[string]time.Time{}, } } -func (o *orderBook) invalidate() { +func (o *OrderBook) invalidate() { o.mu.Lock() defer o.mu.Unlock() o.hasCache = false o.cached = nil } -func (o *orderBook) query(ctx context.Context, client *sdk.Client, cfg Config) ([]parsedOrder, error) { +func (o *OrderBook) query(ctx context.Context, client *sdk.Client, cfg Config) ([]parsedOrder, error) { o.mu.Lock() if o.hasCache { out := append([]parsedOrder(nil), o.cached...) @@ -82,7 +82,7 @@ func (o *orderBook) query(ctx context.Context, client *sdk.Client, cfg Config) ( return out, nil } o.mu.Unlock() - raw, err := client.TradeDetailData(ctx, cfg.AccountType, "order") + raw, err := client.TradeDetailData(ctx, "order") if err != nil { logf("ERROR", "[ZT][委托] 查询失败: %v", err) return nil, err @@ -98,13 +98,13 @@ func (o *orderBook) query(ctx context.Context, client *sdk.Client, cfg Config) ( return orders, nil } -func (o *orderBook) refresh(ctx context.Context, client *sdk.Client, cfg Config) bool { +func (o *OrderBook) refresh(ctx context.Context, client *sdk.Client, cfg Config) bool { o.invalidate() _, err := o.query(ctx, client, cfg) return err == nil } -func (o *orderBook) activeSets(ctx context.Context, client *sdk.Client, cfg Config) (buys, sells map[string]struct{}, ok bool) { +func (o *OrderBook) activeSets(ctx context.Context, client *sdk.Client, cfg Config) (buys, sells map[string]struct{}, ok bool) { orders, err := o.query(ctx, client, cfg) if err != nil { return nil, nil, false @@ -123,7 +123,7 @@ func (o *orderBook) activeSets(ctx context.Context, client *sdk.Client, cfg Conf return buys, sells, true } -func (o *orderBook) cancelExpired(ctx context.Context, client *sdk.Client, cfg Config) bool { +func (o *OrderBook) cancelExpired(ctx context.Context, client *sdk.Client, cfg Config) bool { o.invalidate() orders, err := o.query(ctx, client, cfg) if err != nil { @@ -154,7 +154,7 @@ func (o *orderBook) cancelExpired(ctx context.Context, client *sdk.Client, cfg C } seen[key] = struct{}{} if order.OrderID != "" { - can, err := client.CanCancelOrder(ctx, order.OrderID, cfg.AccountType) + can, err := client.CanCancelOrder(ctx, order.OrderID) if err != nil { logf("ERROR", "[ZT][委托] 查询是否可撤失败 %s: %v", order.OrderID, err) continue @@ -164,7 +164,7 @@ func (o *orderBook) cancelExpired(ctx context.Context, client *sdk.Client, cfg C continue } } - ret, err := client.CancelByRule(ctx, order.StockCode, vol, cfg.AccountType) + ret, err := client.CancelByRule(ctx, order.StockCode, vol) if err != nil { logf("ERROR", "[ZT][委托] 撤单失败 %s %s: %v", order.OrderID, order.StockCode, err) continue @@ -183,7 +183,7 @@ func (o *orderBook) cancelExpired(ctx context.Context, client *sdk.Client, cfg C return true } -func (o *orderBook) claimed(state *ZTState, order parsedOrder) bool { +func (o *OrderBook) claimed(state *ZTState, order parsedOrder) bool { if order.RemarkOwned { return true } @@ -212,7 +212,7 @@ func (o *orderBook) claimed(state *ZTState, order parsedOrder) bool { } } -func (o *orderBook) unlockSide(code, side string) { +func (o *OrderBook) unlockSide(code, side string) { o.mu.Lock() defer o.mu.Unlock() delete(o.locks(side), code) @@ -227,28 +227,28 @@ func (o *orderBook) unlockSide(code, side string) { o.subs = o.subs[:n] } -func (o *orderBook) sideBusy(cfg Config, code, side string, active map[string]struct{}) bool { +func (o *OrderBook) sideBusy(cfg Config, code, side string, active map[string]struct{}) bool { if _, ok := active[code]; ok { return true } return o.locked(cfg, code, side) } -func (o *orderBook) locked(cfg Config, code, side string) bool { +func (o *OrderBook) locked(cfg Config, code, side string) bool { o.mu.Lock() defer o.mu.Unlock() ts, ok := o.locks(side)[code] return ok && time.Since(ts) < cfg.OrderTimeout } -func (o *orderBook) locks(side string) map[string]time.Time { +func (o *OrderBook) locks(side string) map[string]time.Time { if side == "buy" { return o.buyLocks } return o.sellLocks } -func (o *orderBook) hasActive(ctx context.Context, client *sdk.Client, cfg Config, code, side string) bool { +func (o *OrderBook) hasActive(ctx context.Context, client *sdk.Client, cfg Config, code, side string) bool { orders, err := o.query(ctx, client, cfg) if err != nil { return true @@ -261,7 +261,7 @@ func (o *orderBook) hasActive(ctx context.Context, client *sdk.Client, cfg Confi return false } -func (o *orderBook) place(ctx context.Context, client *sdk.Client, cfg Config, side, code string, volume int, tag string) bool { +func (o *OrderBook) place(ctx context.Context, client *sdk.Client, cfg Config, side, code string, volume int, tag string) bool { if volume <= 0 || volume%100 != 0 { logf("ERROR", "[ZT][委托] %s 拒绝非整手数量=%d", code, volume) return false diff --git a/go-client/apps/zt/positions.go b/go-client/apps/zt/logic/positions.go similarity index 95% rename from go-client/apps/zt/positions.go rename to go-client/apps/zt/logic/positions.go index a026488..10cc1c2 100644 --- a/go-client/apps/zt/positions.go +++ b/go-client/apps/zt/logic/positions.go @@ -1,4 +1,4 @@ -package main +package logic import ( "context" @@ -32,7 +32,7 @@ func positionCodes(positions []sdk.Position) map[string]struct{} { return out } -func managePositions(ctx context.Context, client *sdk.Client, books *orderBook, cfg Config, ticks map[string]sdk.Tick, positions []sdk.Position, marketOK bool) { +func managePositions(ctx context.Context, client *sdk.Client, books *OrderBook, cfg Config, ticks map[string]sdk.Tick, positions []sdk.Position, marketOK bool) { if positions == nil { logf("ERROR", "[ZT][持仓] 持仓查询失败,本轮跳过") return @@ -129,7 +129,7 @@ func managePositions(ctx context.Context, client *sdk.Client, books *orderBook, state.Save() } -func syncItem(cfg Config, state *ZTState, code string, volume int, avgPrice float64, buys, sells map[string]struct{}, books *orderBook) *SymbolState { +func syncItem(cfg Config, state *ZTState, code string, volume int, avgPrice float64, buys, sells map[string]struct{}, books *OrderBook) *SymbolState { item := state.Get(code) if item == nil { if volume > 0 { @@ -162,7 +162,7 @@ func syncItem(cfg Config, state *ZTState, code string, volume int, avgPrice floa return state.Get(code) } -func syncOpen(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, buys map[string]struct{}, books *orderBook) { +func syncOpen(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, buys map[string]struct{}, books *OrderBook) { if volume > 0 { item.BaseQty, item.BaseCost = volume, avgPrice } @@ -178,7 +178,7 @@ func syncOpen(cfg Config, state *ZTState, item *SymbolState, volume int, avgPric logf("INFO", "[ZT][持仓] %s 开仓确认 数量=%d 成本=%.2f", item.Code, item.BaseQty, item.BaseCost) } -func syncAdd(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, buys map[string]struct{}, books *orderBook) { +func syncAdd(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, buys map[string]struct{}, books *OrderBook) { if volume > item.BaseQty { item.AddQty = volume - item.BaseQty if item.AddQty > 0 { @@ -201,7 +201,7 @@ func syncAdd(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice item.Pending = "" } -func syncSellAdd(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, sells map[string]struct{}, books *orderBook) { +func syncSellAdd(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, sells map[string]struct{}, books *OrderBook) { if volume <= 0 { if !books.sideBusy(cfg, item.Code, "sell", sells) { state.Remove(item.Code) @@ -223,7 +223,7 @@ func syncSellAdd(cfg Config, state *ZTState, item *SymbolState, volume int, avgP } } -func syncSellBase(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, sells map[string]struct{}, books *orderBook) { +func syncSellBase(cfg Config, state *ZTState, item *SymbolState, volume int, avgPrice float64, sells map[string]struct{}, books *OrderBook) { if volume <= 0 { if !books.sideBusy(cfg, item.Code, "sell", sells) { state.Remove(item.Code) @@ -237,7 +237,7 @@ func syncSellBase(cfg Config, state *ZTState, item *SymbolState, volume int, avg } } -func addOnRebound(ctx context.Context, client *sdk.Client, books *orderBook, cfg Config, item *SymbolState, price float64, marketOK bool) { +func addOnRebound(ctx context.Context, client *sdk.Client, books *OrderBook, cfg Config, item *SymbolState, price float64, marketOK bool) { if !marketOK || !dipTriggered(&posDip.mu, posDip.store, cfg, "补仓", item.Code, price) { return } @@ -266,7 +266,7 @@ func retreated(cfg Config, item *SymbolState, leg string, pnl float64) bool { return grid < peak } -func sellLeg(ctx context.Context, client *sdk.Client, books *orderBook, cfg Config, item *SymbolState, usable, volume int, leg string, pnl float64) { +func sellLeg(ctx context.Context, client *sdk.Client, books *OrderBook, cfg Config, item *SymbolState, usable, volume int, leg string, pnl float64) { volume -= volume % 100 if volume <= 0 || usable < volume { logf("INFO", "[ZT][止盈] %s 可用股数不足,需要=%d 可用=%d", item.Code, volume, usable) diff --git a/go-client/apps/zt/remote.go b/go-client/apps/zt/logic/remote.go similarity index 63% rename from go-client/apps/zt/remote.go rename to go-client/apps/zt/logic/remote.go index 3bb8fb6..c617b47 100644 --- a/go-client/apps/zt/remote.go +++ b/go-client/apps/zt/logic/remote.go @@ -1,4 +1,4 @@ -package main +package logic import ( "encoding/json" @@ -116,7 +116,10 @@ func loadDailyFile(path string) *dailyCache { func fetchSignal(cfg Config, name string) map[string]map[string]any { cached := daily(cfg, name, "open_%s.json", func() (any, error) { - q := url.Values{"host_key": {cfg.HostKey}} + var q url.Values + if strings.TrimSpace(cfg.HostKey) != "" { + q = url.Values{"host_key": {cfg.HostKey}} + } payload, err := getJSON(cfg.APIHost+"/a/"+name, q, cfg.HTTPTimeout) if err != nil { return nil, err @@ -179,100 +182,3 @@ func normalizeZT(payload map[string]any) map[string]map[string]any { } return out } - -func passCodes(cfg Config) []string { - load := func() (any, error) { - payload, err := getJSON(cfg.APIHost+"/a/pass_codes", nil, cfg.HTTPTimeout) - if err != nil { - return nil, err - } - data, _ := payload["data"].([]any) - if data == nil { - return nil, fmt.Errorf("接口 data 不是数组") - } - codes := make([]string, 0, len(data)) - for _, item := range data { - s := strings.ToUpper(strings.TrimSpace(fmt.Sprint(item))) - if s != "" && s != "" { - codes = append(codes, s) - } - } - return codes, nil - } - cached := daily(cfg, "pass_codes", "pass_codes_%s.json", load, time.Now()) - codes := codesFromAny(cached) - if len(codes) > 0 { - return codes - } - logf("INFO", "pass_codes 为空,重新获取") - data, err := load() - if err != nil { - logf("ERROR", "pass_codes 重新获取失败: %v", err) - return nil - } - list, _ := data.([]string) - return list -} - -func codesFromAny(cached *dailyCache) []string { - if cached == nil || !cached.OK { - return nil - } - switch v := cached.Data.(type) { - case []string: - return v - case []any: - out := make([]string, 0, len(v)) - for _, item := range v { - s := strings.ToUpper(strings.TrimSpace(fmt.Sprint(item))) - if s != "" && s != "" { - out = append(out, s) - } - } - return out - } - return nil -} - -func marketAllowOpen(cfg Config) bool { - payload, err := getJSON(cfg.APIHost+"/a/market", url.Values{"period": {"60m"}}, cfg.HTTPTimeout) - if err != nil { - logf("ERROR", "获取60m大盘信号失败: %s %v", cfg.APIHost+"/a/market", err) - return false - } - status := marketStatus(payload) - logf("INFO", "大盘信号: status=%s", status) - return status == "UP" -} - -func marketStatus(payload map[string]any) string { - var value any = payload - if m, ok := value.(map[string]any); ok { - if d, exists := m["data"]; exists { - value = d - } - } - if arr, ok := value.([]any); ok { - if len(arr) == 0 { - value = nil - } else { - value = arr[len(arr)-1] - } - } - if m, ok := value.(map[string]any); ok { - if v, exists := m["action"]; exists { - value = v - } else if v, exists := m["status"]; exists { - value = v - } else if v, exists := m["signal"]; exists { - value = v - } - } - s := strings.ToUpper(strings.TrimSpace(fmt.Sprint(value))) - switch s { - case "UP", "DOWN", "NEUTRAL": - return s - default: - return "UNKNOWN" - } -} diff --git a/go-client/apps/zt/state.go b/go-client/apps/zt/logic/state.go similarity index 99% rename from go-client/apps/zt/state.go rename to go-client/apps/zt/logic/state.go index 4850a12..05f98dc 100644 --- a/go-client/apps/zt/state.go +++ b/go-client/apps/zt/logic/state.go @@ -1,4 +1,4 @@ -package main +package logic import ( "encoding/json" diff --git a/go-client/apps/zt/stock.go b/go-client/apps/zt/logic/stock.go similarity index 92% rename from go-client/apps/zt/stock.go rename to go-client/apps/zt/logic/stock.go index 01288ce..4a3e56c 100644 --- a/go-client/apps/zt/stock.go +++ b/go-client/apps/zt/logic/stock.go @@ -1,4 +1,4 @@ -package main +package logic import ( "fmt" @@ -92,13 +92,3 @@ func asIntS(s string) int { } return n } - -func asFloatS(s string) float64 { - s = strings.TrimSpace(s) - if s == "" { - return 0 - } - var f float64 - _, _ = fmt.Sscanf(s, "%f", &f) - return f -} diff --git a/go-client/apps/zt/main.go b/go-client/apps/zt/main.go index 016932c..8bed68d 100644 --- a/go-client/apps/zt/main.go +++ b/go-client/apps/zt/main.go @@ -5,101 +5,46 @@ import ( "log" "os" "os/signal" - "strings" "syscall" "time" + "big-qmt/go-client/apps/zt/logic" "big-qmt/go-client/sdk" ) func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) - cfg := loadConfig() - client := sdk.New(cfg.QMTBaseURL, cfg.QMTToken, cfg.AccountType, cfg.HTTPTimeout) - books := newOrderBook() + cfg := logic.LoadConfig() + client := sdk.New(cfg.QMTBaseURL, cfg.QMTToken, cfg.HTTPTimeout).SetAccountType(cfg.AccountType) + books := logic.NewOrderBook() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() startup := context.Background() - assets, err := client.Assets(startup, cfg.AccountType) + assets, err := client.Assets(startup) if err != nil { - logf("ERROR", "启动获取资产失败: %v", err) + log.Printf("[ERROR] 启动获取资产失败: %v", err) } - positions, err := client.Positions(startup, cfg.AccountType) + positions, err := client.Positions(startup) if err != nil { - logf("ERROR", "启动获取持仓失败: %v", err) + log.Printf("[ERROR] 启动获取持仓失败: %v", err) positions = []sdk.Position{} } - overview(cfg, assets, positions) - logf("INFO", "[ZT] host_key=%s interval=%s", cfg.HostKey, cfg.LoopInterval) - logf("INFO", "[ZT] Init Success, waiting trading session") + logic.Overview(cfg, assets, positions) + log.Printf("[INFO] [ZT] host_key=%s interval=%s signal=%s/a/dcm_signal", cfg.HostKey, cfg.LoopInterval, cfg.APIHost) + log.Printf("[INFO] [ZT] Init Success, waiting trading session") ticker := time.NewTicker(cfg.LoopInterval) defer ticker.Stop() - runOnce(ctx, client, books, cfg) + logic.RunOnce(ctx, client, books, cfg) for { select { case <-ctx.Done(): - logf("INFO", "[ZT] 停止") + log.Printf("[INFO] [ZT] 停止") return case <-ticker.C: - runOnce(ctx, client, books, cfg) + logic.RunOnce(ctx, client, books, cfg) } } } - -func runOnce(ctx context.Context, client *sdk.Client, books *orderBook, cfg Config) { - if !tradingTime(time.Now()) { - return - } - roundCtx, cancel := context.WithTimeout(ctx, cfg.HTTPTimeout*4) - defer cancel() - - assets, err := client.Assets(roundCtx, cfg.AccountType) - if err != nil { - logf("ERROR", "获取资产失败: %v", err) - return - } - positions, err := client.Positions(roundCtx, cfg.AccountType) - if err != nil { - logf("ERROR", "获取持仓失败: %v", err) - return - } - codes := passCodes(cfg) - seen := map[string]struct{}{} - stockList := make([]string, 0, len(codes)+len(positions)) - addCode := func(code string) { - n := normalizeCode(code, "") - if n == "" { - n = strings.ToUpper(strings.TrimSpace(code)) - } - if n == "" { - return - } - if _, ok := seen[n]; ok { - return - } - seen[n] = struct{}{} - stockList = append(stockList, n) - } - for _, code := range codes { - addCode(code) - } - for _, p := range positions { - addCode(p.StockCode) - } - ticks := map[string]sdk.Tick{} - if len(stockList) > 0 { - raw, err := client.FullTick(roundCtx, stockList) - if err != nil { - logf("ERROR", "获取行情失败: %v", err) - return - } - for code, tick := range raw { - ticks[normalizeCode(code, "")] = tick - ticks[code] = tick - } - } - runRound(roundCtx, client, books, cfg, assets, ticks, positions) -} diff --git a/go-client/libs/http.go b/go-client/libs/http.go new file mode 100644 index 0000000..6654abd --- /dev/null +++ b/go-client/libs/http.go @@ -0,0 +1,39 @@ +package libs + + +func getJSON(rawURL string, params url.Values, timeout time.Duration) (map[string]any, error) { + if timeout <= 0 { + timeout = 5 * time.Second + } + if params != nil { + if strings.Contains(rawURL, "?") { + rawURL += "&" + params.Encode() + } else { + rawURL += "?" + params.Encode() + } + } + req, err := http.NewRequest(http.MethodGet, rawURL, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "big-qmt-go/1") + client := &http.Client{Timeout: timeout} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("http %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) + } + out := map[string]any{} + if err := json.Unmarshal(body, &out); err != nil { + return nil, err + } + return out, nil +} diff --git a/go-client/libs/market.go b/go-client/libs/market.go new file mode 100644 index 0000000..aa394de --- /dev/null +++ b/go-client/libs/market.go @@ -0,0 +1,58 @@ +package libs + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strings" + "time" +) + +// AllowOpen 每次开仓或补仓前取 60 分钟大盘信号,只有 UP 才放行。 +func AllowOpen(apiHost string, timeout time.Duration) bool { + rawURL := strings.TrimRight(apiHost, "/") + "/a/market" + payload, err := getJSON(rawURL, url.Values{"period": {"60m"}}, timeout) + if err != nil { + log.Printf("[ERROR] 获取60m大盘信号失败: %s %v", rawURL, err) + return false + } + status := Status(payload) + log.Printf("[INFO] 大盘信号: url=%s status=%s", rawURL, status) + return status == "UP" +} + +// Status 兼容常见响应结构;无法识别的值统一按 UNKNOWN 处理。 +func Status(payload map[string]any) string { + var value any = payload + if m, ok := value.(map[string]any); ok { + if d, exists := m["data"]; exists { + value = d + } + } + if arr, ok := value.([]any); ok { + if len(arr) == 0 { + value = nil + } else { + value = arr[len(arr)-1] + } + } + if m, ok := value.(map[string]any); ok { + if v, exists := m["action"]; exists { + value = v + } else if v, exists := m["status"]; exists { + value = v + } else if v, exists := m["signal"]; exists { + value = v + } + } + s := strings.ToUpper(strings.TrimSpace(fmt.Sprint(value))) + switch s { + case "UP", "DOWN", "NEUTRAL": + return s + default: + return "UNKNOWN" + } +} diff --git a/go-client/sdk/account.go b/go-client/sdk/account.go index 560dea3..2246e63 100644 --- a/go-client/sdk/account.go +++ b/go-client/sdk/account.go @@ -26,19 +26,22 @@ type Position struct { ExpireDate string `json:"ExpireDate"` } -// Assets 对应 /api/v2/assets。 type Assets struct { Total float64 `json:"total"` Available float64 `json:"available"` } -type accountBody struct { - Account string `json:"account"` +func (c *Client) Positions(ctx context.Context) ([]Position, error) { + return c.decodePositions(ctx, "/api/v2/positions") } -func (c *Client) Positions(ctx context.Context, account string) ([]Position, error) { +func (c *Client) Holding(ctx context.Context) ([]Position, error) { + return c.decodePositions(ctx, "/api/holding") +} + +func (c *Client) decodePositions(ctx context.Context, path string) ([]Position, error) { raw := map[string]json.RawMessage{} - if err := c.post(ctx, "/api/v2/positions", accountBody{Account: c.Account(account)}, &raw); err != nil { + if err := c.post(ctx, path, map[string]any{"account": c.accountType}, &raw); err != nil { return nil, err } out := make([]Position, 0, len(raw)) @@ -55,48 +58,29 @@ func (c *Client) Positions(ctx context.Context, account string) ([]Position, err return out, nil } -func (c *Client) Holding(ctx context.Context, account string) ([]Position, error) { - raw := map[string]json.RawMessage{} - if err := c.post(ctx, "/api/holding", accountBody{Account: c.Account(account)}, &raw); err != nil { - return nil, err - } - out := make([]Position, 0, len(raw)) - for code, blob := range raw { - var p Position - if err := json.Unmarshal(blob, &p); err != nil { - return nil, fmt.Errorf("holding %s: %w", code, err) - } - if p.StockCode == "" { - p.StockCode = code - } - out = append(out, p) - } - return out, nil -} - -func (c *Client) Assets(ctx context.Context, account string) (*Assets, error) { +func (c *Client) Assets(ctx context.Context) (*Assets, error) { var out Assets - if err := c.post(ctx, "/api/v2/assets", accountBody{Account: c.Account(account)}, &out); err != nil { + if err := c.post(ctx, "/api/v2/assets", map[string]any{"account": c.accountType}, &out); err != nil { return nil, err } return &out, nil } -func (c *Client) TotalMoney(ctx context.Context, account string) (float64, error) { +func (c *Client) TotalMoney(ctx context.Context) (float64, error) { var out struct { TotalMoney float64 `json:"total_money"` } - if err := c.post(ctx, "/api/money/total", accountBody{Account: c.Account(account)}, &out); err != nil { + if err := c.post(ctx, "/api/money/total", map[string]any{"account": c.accountType}, &out); err != nil { return 0, err } return out.TotalMoney, nil } -func (c *Client) AvailableMoney(ctx context.Context, account string) (float64, error) { +func (c *Client) AvailableMoney(ctx context.Context) (float64, error) { var out struct { AvailableMoney float64 `json:"available_money"` } - if err := c.post(ctx, "/api/money/available", accountBody{Account: c.Account(account)}, &out); err != nil { + if err := c.post(ctx, "/api/money/available", map[string]any{"account": c.accountType}, &out); err != nil { return 0, err } return out.AvailableMoney, nil @@ -141,11 +125,11 @@ type OrderStatus struct { VolumeTraded int `json:"volume_traded"` } -func (c *Client) OrderStatusList(ctx context.Context, account string) ([]OrderStatus, error) { +func (c *Client) OrderStatusList(ctx context.Context) ([]OrderStatus, error) { var out struct { Orders []OrderStatus `json:"orders"` } - if err := c.post(ctx, "/api/order/status", accountBody{Account: c.Account(account)}, &out); err != nil { + if err := c.post(ctx, "/api/order/status", map[string]any{"account": c.accountType}, &out); err != nil { return nil, err } return out.Orders, nil @@ -164,29 +148,29 @@ type CancelAllResult struct { CanceledSysIDs []string `json:"canceled_sys_ids"` } -func (c *Client) CancelAll(ctx context.Context, account string) (*CancelAllResult, error) { +func (c *Client) CancelAll(ctx context.Context) (*CancelAllResult, error) { var out CancelAllResult - if err := c.post(ctx, "/api/order/cancel_all", accountBody{Account: c.Account(account)}, &out); err != nil { + if err := c.post(ctx, "/api/order/cancel_all", map[string]any{"account": c.accountType}, &out); err != nil { return nil, err } return &out, nil } -// CancelByRule 按「代码.市场 + (剩余+已成)」匹配撤单。HTTP 没有按委托号撤单,ZT 用它代替 cancel(orderId)。 -func (c *Client) CancelByRule(ctx context.Context, stock string, volume int, account string) (*CancelAllResult, error) { +// CancelByRule 按「代码.市场 + (剩余+已成)」匹配撤单。HTTP 没有按委托号撤单。 +func (c *Client) CancelByRule(ctx context.Context, stock string, volume int) (*CancelAllResult, error) { var out CancelAllResult - body := map[string]any{"stock": stock, "volume": volume, "account": c.Account(account)} + body := map[string]any{"stock": stock, "volume": volume, "account": c.accountType} if err := c.post(ctx, "/api/order/cancel_order", body, &out); err != nil { return nil, err } return &out, nil } -func (c *Client) Deals(ctx context.Context, account string) ([]map[string]string, error) { +func (c *Client) Deals(ctx context.Context) ([]map[string]string, error) { var out struct { Deals []map[string]string `json:"deals"` } - if err := c.post(ctx, "/api/order/deal", accountBody{Account: c.Account(account)}, &out); err != nil { + if err := c.post(ctx, "/api/order/deal", map[string]any{"account": c.accountType}, &out); err != nil { return nil, err } return out.Deals, nil diff --git a/go-client/sdk/check.go b/go-client/sdk/check.go index 7a473b9..e0109cf 100644 --- a/go-client/sdk/check.go +++ b/go-client/sdk/check.go @@ -3,65 +3,28 @@ package sdk import "context" func (c *Client) IsLastBar(ctx context.Context) (any, error) { - var out struct { - IsLastBar any `json:"is_last_bar"` - } - if err := c.get(ctx, "/api/check/is_last_bar", &out); err != nil { - return nil, err - } - return out.IsLastBar, nil + return c.getField(ctx, "/api/check/is_last_bar", "is_last_bar") } func (c *Client) IsNewBar(ctx context.Context) (any, error) { - var out struct { - IsNewBar any `json:"is_new_bar"` - } - if err := c.get(ctx, "/api/check/is_new_bar", &out); err != nil { - return nil, err - } - return out.IsNewBar, nil + return c.getField(ctx, "/api/check/is_new_bar", "is_new_bar") } func (c *Client) IsSuspendedStock(ctx context.Context, stockcode string) (any, error) { - var out struct { - Stockcode string `json:"stockcode"` - IsSuspended any `json:"is_suspended"` - } - if err := c.post(ctx, "/api/check/is_suspended_stock", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.IsSuspended, nil + return c.postField(ctx, "/api/check/is_suspended_stock", map[string]any{"stockcode": stockcode}, "is_suspended") } func (c *Client) IsSectorStock(ctx context.Context, sectorname, market, stockcode string) (any, error) { - var out struct { - IsInSector any `json:"is_in_sector"` - } body := map[string]any{"sectorname": sectorname, "market": market, "stockcode": stockcode} - if err := c.post(ctx, "/api/check/is_sector_stock", body, &out); err != nil { - return nil, err - } - return out.IsInSector, nil + return c.postField(ctx, "/api/check/is_sector_stock", body, "is_in_sector") } func (c *Client) IsTypedStock(ctx context.Context, stocktypenum int, market, stockcode string) (any, error) { - var out struct { - Result any `json:"result"` - } body := map[string]any{"stocktypenum": stocktypenum, "market": market, "stockcode": stockcode} - if err := c.post(ctx, "/api/check/is_typed_stock", body, &out); err != nil { - return nil, err - } - return out.Result, nil + return c.postField(ctx, "/api/check/is_typed_stock", body, "result") } func (c *Client) IndustryNameOfStock(ctx context.Context, industryType, stockcode string) (any, error) { - var out struct { - IndustryName any `json:"industry_name"` - } body := map[string]any{"industryType": industryType, "stockcode": stockcode} - if err := c.post(ctx, "/api/check/get_industry_name_of_stock", body, &out); err != nil { - return nil, err - } - return out.IndustryName, nil + return c.postField(ctx, "/api/check/get_industry_name_of_stock", body, "industry_name") } diff --git a/go-client/sdk/client.go b/go-client/sdk/client.go index 38d7db6..b8009c8 100644 --- a/go-client/sdk/client.go +++ b/go-client/sdk/client.go @@ -11,25 +11,30 @@ import ( "time" ) - -// Client 调用 QMT HTTP API。 type Client struct { - baseURL string - token string + baseURL string + token string accountType string - http *http.Client + http *http.Client } -func New(baseURL, token, accountType string, timeout time.Duration) *Client { - base := strings.TrimRight(baseURL, "/") - return &Client{baseURL: base, token: token, accountType: accountType, http: &http.Client{Timeout: timeout}} -} - -func (c *Client) Account(override string) string { - if strings.TrimSpace(override) != "" { - return override +func New(baseURL, token string, timeout time.Duration) *Client { + if timeout <= 0 { + timeout = 15 * time.Second } - return c.accountType + return &Client{ + baseURL: strings.TrimRight(baseURL, "/"), + token: token, + accountType: "stock", + http: &http.Client{Timeout: timeout}, + } +} + +func (c *Client) SetAccountType(accountType string) *Client { + if strings.TrimSpace(accountType) != "" { + c.accountType = accountType + } + return c } func (c *Client) get(ctx context.Context, path string, dest any) error { @@ -92,14 +97,32 @@ func (c *Client) do(ctx context.Context, method, path string, body any, dest any return nil } -func truncate(b []byte, n int) string { - if len(b) <= n { - return string(b) +func (c *Client) getField(ctx context.Context, path, key string) (any, error) { + var out map[string]any + if err := c.get(ctx, path, &out); err != nil { + return nil, err } - return string(b[:n]) + "..." + return out[key], nil } -func ArrayJoin(items []string) string { +func (c *Client) postField(ctx context.Context, path string, body any, key string) (any, error) { + var out map[string]any + if err := c.post(ctx, path, body, &out); err != nil { + return nil, err + } + if msg, ok := out["error"].(string); ok && msg != "" { + return nil, &BusinessError{Message: msg} + } + if key == "" { + return out, nil + } + if v, ok := out[key]; ok { + return v, nil + } + return out, nil +} + +func csvJoin(items []string) string { parts := make([]string, 0, len(items)) for _, s := range items { s = strings.TrimSpace(s) @@ -109,3 +132,10 @@ func ArrayJoin(items []string) string { } return strings.Join(parts, ",") } + +func truncate(b []byte, n int) string { + if len(b) <= n { + return string(b) + } + return string(b[:n]) + "..." +} diff --git a/go-client/sdk/coerce.go b/go-client/sdk/coerce.go index 487ce85..0dc2aef 100644 --- a/go-client/sdk/coerce.go +++ b/go-client/sdk/coerce.go @@ -15,20 +15,17 @@ func asString(v any) string { return x case json.Number: return x.String() - case []byte: - return string(x) default: - return strings.TrimSpace(fmtAny(v)) + return strings.TrimSpace(fmtSprint(v)) } } -func fmtAny(v any) string { +func fmtSprint(v any) string { b, err := json.Marshal(v) if err != nil { return "" } - s := strings.Trim(string(b), `"`) - return s + return strings.Trim(string(b), `"`) } func asFloat(v any) float64 { @@ -56,39 +53,11 @@ func asFloat(v any) float64 { } } -func asInt(v any) int { - return int(asFloat(v)) -} - -func asBool(v any) bool { - switch x := v.(type) { - case bool: - return x - case string: - s := strings.ToLower(strings.TrimSpace(x)) - return s == "true" || s == "1" || s == "yes" - default: - return asFloat(v) != 0 - } -} - func mapField(m map[string]any, names ...string) any { for _, name := range names { - if name == "" { - continue - } if v, ok := m[name]; ok && v != nil { return v } } return nil } - -func mapFieldS(m map[string]string, names ...string) string { - for _, name := range names { - if v, ok := m[name]; ok && strings.TrimSpace(v) != "" { - return v - } - } - return "" -} diff --git a/go-client/sdk/context.go b/go-client/sdk/context.go index a1e1b8f..bcaf736 100644 --- a/go-client/sdk/context.go +++ b/go-client/sdk/context.go @@ -16,121 +16,63 @@ type ContextInfo struct { } func (c *Client) ContextPeriod(ctx context.Context) (any, error) { - var out struct { - Period any `json:"period"` - } - if err := c.get(ctx, "/api/context/period", &out); err != nil { - return nil, err - } - return out.Period, nil + return c.getField(ctx, "/api/context/period", "period") } func (c *Client) ContextBarpos(ctx context.Context) (any, error) { - var out struct { - Barpos any `json:"barpos"` - } - if err := c.get(ctx, "/api/context/barpos", &out); err != nil { - return nil, err - } - return out.Barpos, nil + return c.getField(ctx, "/api/context/barpos", "barpos") } func (c *Client) ContextTimeTickSize(ctx context.Context) (any, error) { - var out struct { - TimeTickSize any `json:"time_tick_size"` - } - if err := c.get(ctx, "/api/context/time_tick_size", &out); err != nil { - return nil, err - } - return out.TimeTickSize, nil + return c.getField(ctx, "/api/context/time_tick_size", "time_tick_size") } func (c *Client) ContextStockcode(ctx context.Context) (any, error) { - var out struct { - Stockcode any `json:"stockcode"` - } - if err := c.get(ctx, "/api/context/stockcode", &out); err != nil { - return nil, err - } - return out.Stockcode, nil + return c.getField(ctx, "/api/context/stockcode", "stockcode") } func (c *Client) ContextDividendType(ctx context.Context) (any, error) { - var out struct { - DividendType any `json:"dividend_type"` - } - if err := c.get(ctx, "/api/context/dividend_type", &out); err != nil { - return nil, err - } - return out.DividendType, nil + return c.getField(ctx, "/api/context/dividend_type", "dividend_type") } func (c *Client) ContextMarket(ctx context.Context) (any, error) { - var out struct { - Market any `json:"market"` - } - if err := c.get(ctx, "/api/context/market", &out); err != nil { - return nil, err - } - return out.Market, nil + return c.getField(ctx, "/api/context/market", "market") } func (c *Client) ContextDoBackTest(ctx context.Context) (any, error) { - var out struct { - DoBackTest any `json:"do_back_test"` - } - if err := c.get(ctx, "/api/context/do_back_test", &out); err != nil { - return nil, err - } - return out.DoBackTest, nil + return c.getField(ctx, "/api/context/do_back_test", "do_back_test") } func (c *Client) ContextBenchmark(ctx context.Context) (any, error) { - var out struct { - Benchmark any `json:"benchmark"` - } - if err := c.get(ctx, "/api/context/benchmark", &out); err != nil { - return nil, err - } - return out.Benchmark, nil + return c.getField(ctx, "/api/context/benchmark", "benchmark") } func (c *Client) ContextCapital(ctx context.Context) (any, error) { - var out struct { - Capital any `json:"capital"` - } - if err := c.get(ctx, "/api/context/capital", &out); err != nil { - return nil, err - } - return out.Capital, nil + return c.getField(ctx, "/api/context/capital", "capital") } func (c *Client) ContextUniverse(ctx context.Context) ([]string, error) { - var out struct { - Universe any `json:"universe"` - } - if err := c.get(ctx, "/api/context/universe", &out); err != nil { + v, err := c.getField(ctx, "/api/context/universe", "universe") + if err != nil { return nil, err } - switch v := out.Universe.(type) { + switch u := v.(type) { case nil: return nil, nil case []any: - codes := make([]string, 0, len(v)) - for _, item := range v { - s := asString(item) - if s != "" { + codes := make([]string, 0, len(u)) + for _, item := range u { + if s := asString(item); s != "" { codes = append(codes, s) } } return codes, nil case []string: - return v, nil + return u, nil default: - s := asString(v) - if s == "" { - return nil, nil + if s := asString(u); s != "" { + return []string{s}, nil } - return []string{s}, nil + return nil, nil } } diff --git a/go-client/sdk/data.go b/go-client/sdk/data.go index 046a903..ac6e9ed 100644 --- a/go-client/sdk/data.go +++ b/go-client/sdk/data.go @@ -2,7 +2,8 @@ package sdk import ( "context" - "fmt" + "strconv" + "strings" ) type Tick struct { @@ -12,21 +13,21 @@ type Tick struct { } type HistoryDataRequest struct { - Len int `json:"len"` - Period string `json:"period,omitempty"` - Field string `json:"field,omitempty"` - DividendType int `json:"dividend_type"` - SkipPaused string `json:"skip_paused,omitempty"` + Len int + Period string + Field string + DividendType int + SkipPaused *bool } type MarketDataRequest struct { - Fields string `json:"fields,omitempty"` - StockCode string `json:"stock_code,omitempty"` - StartTime string `json:"start_time,omitempty"` - EndTime string `json:"end_time,omitempty"` - Period string `json:"period,omitempty"` - DividendType string `json:"dividend_type,omitempty"` - Count int `json:"count"` + Fields []string + Stocks []string + StartTime string + EndTime string + Period string + DividendType string + Count int } type SubscribeResult struct { @@ -35,64 +36,30 @@ type SubscribeResult struct { } func (c *Client) StockName(ctx context.Context, stockcode string) (any, error) { - var out struct { - Name any `json:"name"` - } - if err := c.post(ctx, "/api/data/stock_name", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.Name, nil + return c.postField(ctx, "/api/data/stock_name", map[string]any{"stockcode": stockcode}, "name") } func (c *Client) OpenDate(ctx context.Context, stockcode string) (any, error) { - var out struct { - OpenDate any `json:"open_date"` - } - if err := c.post(ctx, "/api/data/open_date", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.OpenDate, nil + return c.postField(ctx, "/api/data/open_date", map[string]any{"stockcode": stockcode}, "open_date") } func (c *Client) LastVolume(ctx context.Context, stockcode string) (any, error) { - var out struct { - LastVolume any `json:"last_volume"` - } - if err := c.post(ctx, "/api/data/last_volume", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.LastVolume, nil + return c.postField(ctx, "/api/data/last_volume", map[string]any{"stockcode": stockcode}, "last_volume") } func (c *Client) BarTimetag(ctx context.Context, index int) (any, error) { - var out struct { - Timetag any `json:"timetag"` - } - if err := c.post(ctx, "/api/data/bar_timetag", map[string]any{"index": index}, &out); err != nil { - return nil, err - } - return out.Timetag, nil + return c.postField(ctx, "/api/data/bar_timetag", map[string]any{"index": index}, "timetag") } func (c *Client) TickTimetag(ctx context.Context) (any, error) { - var out struct { - Timetag any `json:"timetag"` - } - if err := c.get(ctx, "/api/data/tick_timetag", &out); err != nil { - return nil, err - } - return out.Timetag, nil + return c.getField(ctx, "/api/data/tick_timetag", "timetag") } -func (c *Client) Sector(ctx context.Context, sector string, realtime string) ([]any, error) { - body := map[string]any{"sector": sector} - if realtime != "" { - body["realtime"] = realtime - } +func (c *Client) Sector(ctx context.Context, sector string, realtime int) ([]any, error) { var out struct { Stocks []any `json:"stocks"` } - if err := c.post(ctx, "/api/data/sector", body, &out); err != nil { + if err := c.post(ctx, "/api/data/sector", map[string]any{"sector": sector, "realtime": realtime}, &out); err != nil { return nil, err } return out.Stocks, nil @@ -119,99 +86,58 @@ func (c *Client) StockListInSector(ctx context.Context, sectorname string) ([]an } func (c *Client) WeightInIndex(ctx context.Context, indexcode, stockcode string) (any, error) { - var out struct { - Weight any `json:"weight"` - } - body := map[string]any{"indexcode": indexcode, "stockcode": stockcode} - if err := c.post(ctx, "/api/data/weight_in_index", body, &out); err != nil { - return nil, err - } - return out.Weight, nil + return c.postField(ctx, "/api/data/weight_in_index", map[string]any{"indexcode": indexcode, "stockcode": stockcode}, "weight") } func (c *Client) ContractMultiplier(ctx context.Context, contractcode string) (any, error) { - var out struct { - Multiplier any `json:"multiplier"` - } - if err := c.post(ctx, "/api/data/contract_multiplier", map[string]any{"contractcode": contractcode}, &out); err != nil { - return nil, err - } - return out.Multiplier, nil + return c.postField(ctx, "/api/data/contract_multiplier", map[string]any{"contractcode": contractcode}, "multiplier") } func (c *Client) RiskFreeRate(ctx context.Context, index int) (any, error) { - var out struct { - RiskFreeRate any `json:"risk_free_rate"` - } - if err := c.post(ctx, "/api/data/risk_free_rate", map[string]any{"index": index}, &out); err != nil { - return nil, err - } - return out.RiskFreeRate, nil + return c.postField(ctx, "/api/data/risk_free_rate", map[string]any{"index": index}, "risk_free_rate") } func (c *Client) DateLocation(ctx context.Context, strdate string) (any, error) { - var out struct { - Location any `json:"location"` - } - if err := c.post(ctx, "/api/data/date_location", map[string]any{"strdate": strdate}, &out); err != nil { - return nil, err - } - return out.Location, nil + return c.postField(ctx, "/api/data/date_location", map[string]any{"strdate": strdate}, "location") } func (c *Client) HistoryData(ctx context.Context, req HistoryDataRequest) (any, error) { if req.Len == 0 { req.Len = 10 } - if req.SkipPaused == "" { - req.SkipPaused = "true" + skip := "true" + if req.SkipPaused != nil { + skip = strconv.FormatBool(*req.SkipPaused) } - var out map[string]any - if err := c.post(ctx, "/api/data/history_data", req, &out); err != nil { - return nil, err + return c.postField(ctx, "/api/data/history_data", map[string]any{ + "len": req.Len, "period": req.Period, "field": req.Field, + "dividend_type": req.DividendType, "skip_paused": skip, + }, "data") +} + +func (c *Client) marketDataBody(req MarketDataRequest) map[string]any { + return map[string]any{ + "fields": csvJoin(req.Fields), + "stock_code": csvJoin(req.Stocks), + "start_time": req.StartTime, + "end_time": req.EndTime, + "period": req.Period, + "dividend_type": req.DividendType, + "count": req.Count, } - if msg, ok := out["error"].(string); ok && msg != "" { - return nil, &BusinessError{Message: msg} - } - return out["data"], nil } func (c *Client) MarketData(ctx context.Context, req MarketDataRequest) (any, error) { - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/data/market_data", req, &out); err != nil { - return nil, err - } - return out.Data, nil + return c.postField(ctx, "/api/data/market_data", c.marketDataBody(req), "data") } func (c *Client) MarketDataEx(ctx context.Context, req MarketDataRequest) (any, error) { - body := map[string]any{ - "fields": req.Fields, - "stock_code": req.StockCode, - "period": req.Period, - "start_time": req.StartTime, - "end_time": req.EndTime, - "count": req.Count, - "dividend_type": req.DividendType, - } - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/data/market_data_ex", body, &out); err != nil { - return nil, err - } - return out.Data, nil + return c.postField(ctx, "/api/data/market_data_ex", c.marketDataBody(req), "data") } func (c *Client) FullTick(ctx context.Context, stocks []string) (map[string]Tick, error) { - joined := ArrayJoin(stocks) - if joined == "" { - return nil, fmt.Errorf("full_tick: stocks empty") - } raw := map[string]any{} - if err := c.post(ctx, "/api/data/full_tick", map[string]any{"stocks": joined}, &raw); err != nil { + if err := c.post(ctx, "/api/data/full_tick", map[string]any{"stocks": stocks}, &raw); err != nil { return nil, err } out := make(map[string]Tick, len(raw)) @@ -228,23 +154,11 @@ func (c *Client) FullTick(ctx context.Context, stocks []string) (map[string]Tick } func (c *Client) DividFactors(ctx context.Context, stockcode string) (any, error) { - var out struct { - Factors any `json:"factors"` - } - if err := c.post(ctx, "/api/data/divid_factors", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.Factors, nil + return c.postField(ctx, "/api/data/divid_factors", map[string]any{"stockcode": stockcode}, "factors") } func (c *Client) MainContract(ctx context.Context, codemarket string) (any, error) { - var out struct { - MainContract any `json:"main_contract"` - } - if err := c.post(ctx, "/api/data/main_contract", map[string]any{"codemarket": codemarket}, &out); err != nil { - return nil, err - } - return out.MainContract, nil + return c.postField(ctx, "/api/data/main_contract", map[string]any{"codemarket": codemarket}, "main_contract") } func (c *Client) TimetagToDatetime(ctx context.Context, timetag int64, format string) (any, error) { @@ -252,23 +166,11 @@ func (c *Client) TimetagToDatetime(ctx context.Context, timetag int64, format st if format != "" { body["format"] = format } - var out struct { - Datetime any `json:"datetime"` - } - if err := c.post(ctx, "/api/data/timetag_to_datetime", body, &out); err != nil { - return nil, err - } - return out.Datetime, nil + return c.postField(ctx, "/api/data/timetag_to_datetime", body, "datetime") } func (c *Client) TotalShare(ctx context.Context, stockcode string) (any, error) { - var out struct { - TotalShare any `json:"total_share"` - } - if err := c.post(ctx, "/api/data/total_share", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.TotalShare, nil + return c.postField(ctx, "/api/data/total_share", map[string]any{"stockcode": stockcode}, "total_share") } func (c *Client) TradingDates(ctx context.Context, stockcode, startDate, endDate, period string, count int) ([]any, error) { @@ -286,242 +188,145 @@ func (c *Client) TradingDates(ctx context.Context, stockcode, startDate, endDate } func (c *Client) Svol(ctx context.Context, stockcode string) (any, error) { - var out struct { - Svol any `json:"svol"` - } - if err := c.post(ctx, "/api/data/svol", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.Svol, nil + return c.postField(ctx, "/api/data/svol", map[string]any{"stockcode": stockcode}, "svol") } func (c *Client) Bvol(ctx context.Context, stockcode string) (any, error) { - var out struct { - Bvol any `json:"bvol"` - } - if err := c.post(ctx, "/api/data/bvol", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.Bvol, nil + return c.postField(ctx, "/api/data/bvol", map[string]any{"stockcode": stockcode}, "bvol") } -func (c *Client) dataPayload(ctx context.Context, path string, body map[string]any) (any, error) { - var out map[string]any - if err := c.post(ctx, path, body, &out); err != nil { - return nil, err - } - if msg, ok := out["error"].(string); ok && msg != "" { - return nil, &BusinessError{Message: msg} - } - if v, ok := out["data"]; ok { - return v, nil - } - return out, nil +func (c *Client) Longhubang(ctx context.Context, stockList []string, startTime, endTime string) (any, error) { + return c.postField(ctx, "/api/data/longhubang", map[string]any{ + "stock_list": csvJoin(stockList), "startTime": startTime, "endTime": endTime, + }, "data") } -func (c *Client) Longhubang(ctx context.Context, stockList, startTime, endTime string) (any, error) { - return c.dataPayload(ctx, "/api/data/longhubang", map[string]any{ - "stock_list": stockList, "startTime": startTime, "endTime": endTime, - }) -} - -func (c *Client) Top10ShareHolder(ctx context.Context, stockList, dataName, startTime, endTime string) (any, error) { - return c.dataPayload(ctx, "/api/data/top10_share_holder", map[string]any{ - "stock_list": stockList, "data_name": dataName, "start_time": startTime, "end_time": endTime, - }) +func (c *Client) Top10ShareHolder(ctx context.Context, stockList []string, dataName, startTime, endTime string) (any, error) { + return c.postField(ctx, "/api/data/top10_share_holder", map[string]any{ + "stock_list": csvJoin(stockList), "data_name": dataName, "start_time": startTime, "end_time": endTime, + }, "data") } func (c *Client) OptionDetail(ctx context.Context, optioncode string) (any, error) { - var out struct { - Detail any `json:"detail"` - } - if err := c.post(ctx, "/api/data/option_detail", map[string]any{"optioncode": optioncode}, &out); err != nil { - return nil, err - } - return out.Detail, nil + return c.postField(ctx, "/api/data/option_detail", map[string]any{"optioncode": optioncode}, "detail") } -func (c *Client) TurnoverRate(ctx context.Context, stockList, startTime, endTime string) (any, error) { - return c.dataPayload(ctx, "/api/data/turnover_rate", map[string]any{ - "stock_list": stockList, "startTime": startTime, "endTime": endTime, - }) +func (c *Client) TurnoverRate(ctx context.Context, stockList []string, startTime, endTime string) (any, error) { + return c.postField(ctx, "/api/data/turnover_rate", map[string]any{ + "stock_list": csvJoin(stockList), "startTime": startTime, "endTime": endTime, + }, "data") } func (c *Client) ETFInfo(ctx context.Context, stockcode string) (any, error) { - var out struct { - Info any `json:"info"` - } - if err := c.post(ctx, "/api/data/etf_info", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.Info, nil + return c.postField(ctx, "/api/data/etf_info", map[string]any{"stockcode": stockcode}, "info") } func (c *Client) ETFIOPV(ctx context.Context, stockcode string) (any, error) { - var out struct { - IOPV any `json:"iopv"` - } - if err := c.post(ctx, "/api/data/etf_iopv", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.IOPV, nil + return c.postField(ctx, "/api/data/etf_iopv", map[string]any{"stockcode": stockcode}, "iopv") } func (c *Client) InstrumentDetail(ctx context.Context, stockcode string) (any, error) { - var out struct { - Detail any `json:"detail"` - } - if err := c.post(ctx, "/api/data/instrumentdetail", map[string]any{"stockcode": stockcode}, &out); err != nil { - return nil, err - } - return out.Detail, nil + return c.postField(ctx, "/api/data/instrumentdetail", map[string]any{"stockcode": stockcode}, "detail") } func (c *Client) ContractExpireDate(ctx context.Context, codemarket string) (any, error) { - var out struct { - ExpireDate any `json:"expire_date"` - } - if err := c.post(ctx, "/api/data/contract_expire_date", map[string]any{"codemarket": codemarket}, &out); err != nil { - return nil, err - } - return out.ExpireDate, nil + return c.postField(ctx, "/api/data/contract_expire_date", map[string]any{"codemarket": codemarket}, "expire_date") } func (c *Client) OptionUndlData(ctx context.Context, undlCodeRef string) (any, error) { - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/data/option_undl_data", map[string]any{"undl_code_ref": undlCodeRef}, &out); err != nil { - return nil, err - } - return out.Data, nil + return c.postField(ctx, "/api/data/option_undl_data", map[string]any{"undl_code_ref": undlCodeRef}, "data") } type FinancialDataRequest struct { - Tabname string `json:"tabname,omitempty"` - Colname string `json:"colname,omitempty"` - Market string `json:"market,omitempty"` - Code string `json:"code,omitempty"` - ReportType string `json:"report_type,omitempty"` - Barpos int `json:"barpos"` - FieldList string `json:"fieldList,omitempty"` - StockList string `json:"stockList,omitempty"` - StartDate string `json:"startDate,omitempty"` - EndDate string `json:"endDate,omitempty"` + Tabname string + Colname string + Market string + Code string + ReportType string + Barpos int + FieldList []string + StockList []string + StartDate string + EndDate string } func (c *Client) FinancialData(ctx context.Context, req FinancialDataRequest) (any, error) { - var out map[string]any - if err := c.post(ctx, "/api/data/financial_data", req, &out); err != nil { - return nil, err + body := map[string]any{ + "tabname": req.Tabname, "colname": req.Colname, "market": req.Market, "code": req.Code, + "report_type": req.ReportType, "barpos": req.Barpos, + "fieldList": csvJoin(req.FieldList), "stockList": csvJoin(req.StockList), + "startDate": req.StartDate, "endDate": req.EndDate, } - if msg, ok := out["error"].(string); ok && msg != "" { - return nil, &BusinessError{Message: msg} - } - return out["data"], nil + return c.postField(ctx, "/api/data/financial_data", body, "data") } type FactorDataRequest struct { - FieldList string `json:"fieldList,omitempty"` - StockList string `json:"stockList,omitempty"` - StockCode string `json:"stockCode,omitempty"` - StartDate string `json:"startDate,omitempty"` - EndDate string `json:"endDate,omitempty"` + FieldList []string + StockList []string + StockCode string + StartDate string + EndDate string } func (c *Client) FactorData(ctx context.Context, req FactorDataRequest) (any, error) { - var out map[string]any - if err := c.post(ctx, "/api/data/factor_data", req, &out); err != nil { - return nil, err + body := map[string]any{ + "fieldList": csvJoin(req.FieldList), "stockList": csvJoin(req.StockList), + "stockCode": req.StockCode, "startDate": req.StartDate, "endDate": req.EndDate, } - if msg, ok := out["error"].(string); ok && msg != "" { - return nil, &BusinessError{Message: msg} - } - return out["data"], nil + return c.postField(ctx, "/api/data/factor_data", body, "data") } func (c *Client) HisSTData(ctx context.Context, stockCode string) (any, error) { - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/data/his_st_data", map[string]any{"stockCode": stockCode}, &out); err != nil { - return nil, err - } - return out.Data, nil + return c.postField(ctx, "/api/data/his_st_data", map[string]any{"stockCode": stockCode}, "data") } func (c *Client) HisIndexData(ctx context.Context, index string) (any, error) { - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/data/his_index_data", map[string]any{"index": index}, &out); err != nil { - return nil, err - } - return out.Data, nil + return c.postField(ctx, "/api/data/his_index_data", map[string]any{"index": index}, "data") } func (c *Client) AllSubscription(ctx context.Context) (any, error) { - var out struct { - Subscriptions any `json:"subscriptions"` - } - if err := c.get(ctx, "/api/data/all_subscription", &out); err != nil { - return nil, err - } - return out.Subscriptions, nil + return c.getField(ctx, "/api/data/all_subscription", "subscriptions") } -func (c *Client) OptionList(ctx context.Context, undlCode, dedate, opttype, isavailable string) (any, error) { - body := map[string]any{"undl_code": undlCode, "dedate": dedate, "opttype": opttype} - if isavailable != "" { - body["isavailable"] = isavailable +func (c *Client) OptionList(ctx context.Context, undlCode, dedate, opttype string, isavailable bool) (any, error) { + body := map[string]any{ + "undl_code": undlCode, "dedate": dedate, "opttype": opttype, + "isavailable": strconv.FormatBool(isavailable), } - var out struct { - OptionList any `json:"option_list"` - } - if err := c.post(ctx, "/api/data/option_list", body, &out); err != nil { - return nil, err - } - return out.OptionList, nil + return c.postField(ctx, "/api/data/option_list", body, "option_list") } func (c *Client) HisContractList(ctx context.Context, market string) (any, error) { - var out struct { - Contracts any `json:"contracts"` - } - if err := c.post(ctx, "/api/data/his_contract_list", map[string]any{"market": market}, &out); err != nil { - return nil, err - } - return out.Contracts, nil + return c.postField(ctx, "/api/data/his_contract_list", map[string]any{"market": market}, "contracts") } func (c *Client) OptionIV(ctx context.Context, optioncode string) (any, error) { - var out struct { - IV any `json:"iv"` - } - if err := c.post(ctx, "/api/data/option_iv", map[string]any{"optioncode": optioncode}, &out); err != nil { - return nil, err - } - return out.IV, nil + return c.postField(ctx, "/api/data/option_iv", map[string]any{"optioncode": optioncode}, "iv") } type BSMPriceRequest struct { - OptionType string `json:"optionType"` - ObjectPrices string `json:"objectPrices"` - StrikePrice float64 `json:"strikePrice"` - RiskFree float64 `json:"riskFree"` - Sigma float64 `json:"sigma"` - Days int `json:"days"` - Dividend float64 `json:"dividend"` + OptionType string + ObjectPrices any // float64 或 []float64 + StrikePrice float64 + RiskFree float64 + Sigma float64 + Days int + Dividend float64 } func (c *Client) BSMPrice(ctx context.Context, req BSMPriceRequest) (any, error) { - var out struct { - Price any `json:"price"` + prices := req.ObjectPrices + if vals, ok := req.ObjectPrices.([]float64); ok { + parts := make([]string, len(vals)) + for i, v := range vals { + parts[i] = strconv.FormatFloat(v, 'f', -1, 64) + } + prices = strings.Join(parts, ",") } - if err := c.post(ctx, "/api/data/bsm_price", req, &out); err != nil { - return nil, err - } - return out.Price, nil + return c.postField(ctx, "/api/data/bsm_price", map[string]any{ + "optionType": req.OptionType, "objectPrices": prices, "strikePrice": req.StrikePrice, + "riskFree": req.RiskFree, "sigma": req.Sigma, "days": req.Days, "dividend": req.Dividend, + }, "price") } type BSMIVRequest struct { @@ -535,13 +340,7 @@ type BSMIVRequest struct { } func (c *Client) BSMIV(ctx context.Context, req BSMIVRequest) (any, error) { - var out struct { - IV any `json:"iv"` - } - if err := c.post(ctx, "/api/data/bsm_iv", req, &out); err != nil { - return nil, err - } - return out.IV, nil + return c.postField(ctx, "/api/data/bsm_iv", req, "iv") } type LocalDataRequest struct { @@ -554,18 +353,12 @@ type LocalDataRequest struct { } func (c *Client) LocalData(ctx context.Context, req LocalDataRequest) (any, error) { - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/data/local_data", req, &out); err != nil { - return nil, err - } - return out.Data, nil + return c.postField(ctx, "/api/data/local_data", req, "data") } func (c *Client) SubscribeQuote(ctx context.Context, stockCode, period, dividendType string) (*SubscribeResult, error) { - body := map[string]any{"stock_code": stockCode, "period": period, "dividend_type": dividendType} var out SubscribeResult + body := map[string]any{"stock_code": stockCode, "period": period, "dividend_type": dividendType} if err := c.post(ctx, "/api/data/subscribe_quote", body, &out); err != nil { return nil, err } diff --git a/go-client/sdk/doc.go b/go-client/sdk/doc.go index 8ca1231..9ac602a 100644 --- a/go-client/sdk/doc.go +++ b/go-client/sdk/doc.go @@ -1,4 +1,4 @@ // Package sdk 是 QMT_API.py HTTP 服务的 Go 客户端。 // -// 默认地址 http://127.0.0.1:10086,所有已注册接口都需要请求头 X-Token。 +// 用法:sdk.New(baseURL, token, timeout),账户类型默认 stock,资金账号由服务端环境变量决定。 package sdk diff --git a/go-client/sdk/ext.go b/go-client/sdk/ext.go index d7ef15f..b42e122 100644 --- a/go-client/sdk/ext.go +++ b/go-client/sdk/ext.go @@ -3,45 +3,25 @@ package sdk import "context" func (c *Client) ExtData(ctx context.Context, extdataname, stockcode string, deviation int) (any, error) { - var out struct { - Value any `json:"value"` - } - body := map[string]any{"extdataname": extdataname, "stockcode": stockcode, "deviation": deviation} - if err := c.post(ctx, "/api/ext/ext_data", body, &out); err != nil { - return nil, err - } - return out.Value, nil + return c.postField(ctx, "/api/ext/ext_data", map[string]any{ + "extdataname": extdataname, "stockcode": stockcode, "deviation": deviation, + }, "value") } func (c *Client) ExtDataRank(ctx context.Context, extdataname, stockcode string, deviation int) (any, error) { - var out struct { - Rank any `json:"rank"` - } - body := map[string]any{"extdataname": extdataname, "stockcode": stockcode, "deviation": deviation} - if err := c.post(ctx, "/api/ext/ext_data_rank", body, &out); err != nil { - return nil, err - } - return out.Rank, nil + return c.postField(ctx, "/api/ext/ext_data_rank", map[string]any{ + "extdataname": extdataname, "stockcode": stockcode, "deviation": deviation, + }, "rank") } func (c *Client) GetFactorValue(ctx context.Context, factorname, stockcode string, deviation int) (any, error) { - var out struct { - Value any `json:"value"` - } - body := map[string]any{"factorname": factorname, "stockcode": stockcode, "deviation": deviation} - if err := c.post(ctx, "/api/ext/get_factor_value", body, &out); err != nil { - return nil, err - } - return out.Value, nil + return c.postField(ctx, "/api/ext/get_factor_value", map[string]any{ + "factorname": factorname, "stockcode": stockcode, "deviation": deviation, + }, "value") } func (c *Client) GetFactorRank(ctx context.Context, factorname, stockcode string, deviation int) (any, error) { - var out struct { - Rank any `json:"rank"` - } - body := map[string]any{"factorname": factorname, "stockcode": stockcode, "deviation": deviation} - if err := c.post(ctx, "/api/ext/get_factor_rank", body, &out); err != nil { - return nil, err - } - return out.Rank, nil + return c.postField(ctx, "/api/ext/get_factor_rank", map[string]any{ + "factorname": factorname, "stockcode": stockcode, "deviation": deviation, + }, "rank") } diff --git a/go-client/sdk/trade.go b/go-client/sdk/trade.go index 7c88354..788072b 100644 --- a/go-client/sdk/trade.go +++ b/go-client/sdk/trade.go @@ -102,69 +102,47 @@ func (c *Client) styleOrder(ctx context.Context, path string, body map[string]an return &out, nil } -func (c *Client) OrderLots(ctx context.Context, stock string, lots int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.styleOrder(ctx, "/api/trade/order_lots", styleBody(stock, style, price, accID, "lots", lots)) +func (c *Client) OrderLots(ctx context.Context, stock string, lots int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/order_lots", map[string]any{"stock": stock, "lots": lots, "style": style, "price": price}) } -func (c *Client) OrderValue(ctx context.Context, stock string, value float64, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.styleOrder(ctx, "/api/trade/order_value", styleBody(stock, style, price, accID, "value", value)) +func (c *Client) OrderValue(ctx context.Context, stock string, value float64, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/order_value", map[string]any{"stock": stock, "value": value, "style": style, "price": price}) } -func (c *Client) OrderPercent(ctx context.Context, stock string, percent float64, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.styleOrder(ctx, "/api/trade/order_percent", styleBody(stock, style, price, accID, "percent", percent)) +func (c *Client) OrderPercent(ctx context.Context, stock string, percent float64, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/order_percent", map[string]any{"stock": stock, "percent": percent, "style": style, "price": price}) } -func (c *Client) OrderTargetValue(ctx context.Context, stock string, tarValue float64, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.styleOrder(ctx, "/api/trade/order_target_value", styleBody(stock, style, price, accID, "tar_value", tarValue)) +func (c *Client) OrderTargetValue(ctx context.Context, stock string, tarValue float64, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/order_target_value", map[string]any{"stock": stock, "tar_value": tarValue, "style": style, "price": price}) } -func (c *Client) OrderTargetPercent(ctx context.Context, stock string, tarPercent float64, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.styleOrder(ctx, "/api/trade/order_target_percent", styleBody(stock, style, price, accID, "tar_percent", tarPercent)) +func (c *Client) OrderTargetPercent(ctx context.Context, stock string, tarPercent float64, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/order_target_percent", map[string]any{"stock": stock, "tar_percent": tarPercent, "style": style, "price": price}) } -func (c *Client) OrderShares(ctx context.Context, stock string, shares int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.styleOrder(ctx, "/api/trade/order_shares", styleBody(stock, style, price, accID, "shares", shares)) +func (c *Client) OrderShares(ctx context.Context, stock string, shares int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/order_shares", map[string]any{"stock": stock, "shares": shares, "style": style, "price": price}) } -func styleBody(stock, style string, price float64, accID, key string, val any) map[string]any { - body := map[string]any{"stock": stock, key: val, "price": price} - if style != "" { - body["style"] = style - } - if accID != "" { - body["accId"] = accID - } - return body +func (c *Client) FuturesBuyOpen(ctx context.Context, stock string, amount int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/futures/buy_open", map[string]any{"stock": stock, "amount": amount, "style": style, "price": price}) } - -func (c *Client) futures(ctx context.Context, path, stock string, amount int, style string, price float64, accID string) (*StyleOrderResult, error) { - body := map[string]any{"stock": stock, "amount": amount, "price": price} - if style != "" { - body["style"] = style - } - if accID != "" { - body["accId"] = accID - } - return c.styleOrder(ctx, path, body) +func (c *Client) FuturesBuyCloseTdayFirst(ctx context.Context, stock string, amount int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/futures/buy_close_tdayfirst", map[string]any{"stock": stock, "amount": amount, "style": style, "price": price}) } - -func (c *Client) FuturesBuyOpen(ctx context.Context, stock string, amount int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.futures(ctx, "/api/trade/futures/buy_open", stock, amount, style, price, accID) +func (c *Client) FuturesBuyCloseYdayFirst(ctx context.Context, stock string, amount int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/futures/buy_close_ydayfirst", map[string]any{"stock": stock, "amount": amount, "style": style, "price": price}) } -func (c *Client) FuturesBuyCloseTdayFirst(ctx context.Context, stock string, amount int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.futures(ctx, "/api/trade/futures/buy_close_tdayfirst", stock, amount, style, price, accID) +func (c *Client) FuturesSellOpen(ctx context.Context, stock string, amount int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/futures/sell_open", map[string]any{"stock": stock, "amount": amount, "style": style, "price": price}) } -func (c *Client) FuturesBuyCloseYdayFirst(ctx context.Context, stock string, amount int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.futures(ctx, "/api/trade/futures/buy_close_ydayfirst", stock, amount, style, price, accID) +func (c *Client) FuturesSellCloseTdayFirst(ctx context.Context, stock string, amount int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/futures/sell_close_tdayfirst", map[string]any{"stock": stock, "amount": amount, "style": style, "price": price}) } -func (c *Client) FuturesSellOpen(ctx context.Context, stock string, amount int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.futures(ctx, "/api/trade/futures/sell_open", stock, amount, style, price, accID) -} -func (c *Client) FuturesSellCloseTdayFirst(ctx context.Context, stock string, amount int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.futures(ctx, "/api/trade/futures/sell_close_tdayfirst", stock, amount, style, price, accID) -} -func (c *Client) FuturesSellCloseYdayFirst(ctx context.Context, stock string, amount int, style string, price float64, accID string) (*StyleOrderResult, error) { - return c.futures(ctx, "/api/trade/futures/sell_close_ydayfirst", stock, amount, style, price, accID) +func (c *Client) FuturesSellCloseYdayFirst(ctx context.Context, stock string, amount int, style string, price float64) (*StyleOrderResult, error) { + return c.styleOrder(ctx, "/api/trade/futures/sell_close_ydayfirst", map[string]any{"stock": stock, "amount": amount, "style": style, "price": price}) } type TaskResult struct { @@ -172,45 +150,37 @@ type TaskResult struct { TaskID any `json:"taskId"` } -func (c *Client) task(ctx context.Context, path, taskID, accountType string) (*TaskResult, error) { - body := map[string]any{"taskId": taskID} - if accountType != "" { - body["accountType"] = accountType - } +func (c *Client) CancelTask(ctx context.Context, taskID string) (*TaskResult, error) { + return c.task(ctx, "/api/trade/cancel_task", taskID) +} +func (c *Client) PauseTask(ctx context.Context, taskID string) (*TaskResult, error) { + return c.task(ctx, "/api/trade/pause_task", taskID) +} +func (c *Client) ResumeTask(ctx context.Context, taskID string) (*TaskResult, error) { + return c.task(ctx, "/api/trade/resume_task", taskID) +} + +func (c *Client) task(ctx context.Context, path, taskID string) (*TaskResult, error) { var out TaskResult - if err := c.post(ctx, path, body, &out); err != nil { + if err := c.post(ctx, path, map[string]any{"taskId": taskID, "accountType": c.accountType}, &out); err != nil { return nil, err } return &out, nil } -func (c *Client) CancelTask(ctx context.Context, taskID, accountType string) (*TaskResult, error) { - return c.task(ctx, "/api/trade/cancel_task", taskID, accountType) -} -func (c *Client) PauseTask(ctx context.Context, taskID, accountType string) (*TaskResult, error) { - return c.task(ctx, "/api/trade/pause_task", taskID, accountType) -} -func (c *Client) ResumeTask(ctx context.Context, taskID, accountType string) (*TaskResult, error) { - return c.task(ctx, "/api/trade/resume_task", taskID, accountType) -} - func (c *Client) DoOrder(ctx context.Context) (map[string]any, error) { var out map[string]any - if err := c.post(ctx, "/api/trade/do_order", map[string]any{}, &out); err != nil { + if err := c.post(ctx, "/api/trade/do_order", nil, &out); err != nil { return nil, err } return out, nil } -func (c *Client) TradeDetailData(ctx context.Context, account, datatype string) ([]map[string]string, error) { - body := map[string]any{ - "account": c.Account(account), - "datatype": datatype, - } +func (c *Client) TradeDetailData(ctx context.Context, datatype string) ([]map[string]string, error) { var out struct { Data []map[string]string `json:"data"` } - if err := c.post(ctx, "/api/trade/trade_detail_data", body, &out); err != nil { + if err := c.post(ctx, "/api/trade/trade_detail_data", map[string]any{"account": c.accountType, "datatype": datatype}, &out); err != nil { return nil, err } if out.Data == nil { @@ -219,84 +189,61 @@ func (c *Client) TradeDetailData(ctx context.Context, account, datatype string) return out.Data, nil } -func (c *Client) ValueByOrderID(ctx context.Context, orderID, accountType, datatype string) (map[string]string, error) { - body := map[string]any{"orderId": orderID, "accountType": accountType, "datatype": datatype} +func (c *Client) ValueByOrderID(ctx context.Context, orderID, datatype string) (map[string]string, error) { var out struct { - OrderID string `json:"orderId"` - Data map[string]string `json:"data"` + Data map[string]string `json:"data"` } + body := map[string]any{"orderId": orderID, "accountType": c.accountType, "datatype": datatype} if err := c.post(ctx, "/api/trade/value_by_order_id", body, &out); err != nil { return nil, err } return out.Data, nil } -func (c *Client) LastOrderID(ctx context.Context, account, datatype string) (any, error) { - body := map[string]any{"account": c.Account(account), "datatype": datatype} +func (c *Client) LastOrderID(ctx context.Context, datatype string) (any, error) { var out struct { LastOrderID any `json:"last_order_id"` } - if err := c.post(ctx, "/api/trade/last_order_id", body, &out); err != nil { + if err := c.post(ctx, "/api/trade/last_order_id", map[string]any{"account": c.accountType, "datatype": datatype}, &out); err != nil { return nil, err } return out.LastOrderID, nil } -func (c *Client) CanCancelOrder(ctx context.Context, orderID, accountType string) (any, error) { - body := map[string]any{"orderId": orderID, "accountType": accountType} +func (c *Client) CanCancelOrder(ctx context.Context, orderID string) (any, error) { var out struct { CanCancel any `json:"can_cancel"` } - if err := c.post(ctx, "/api/trade/can_cancel_order", body, &out); err != nil { + if err := c.post(ctx, "/api/trade/can_cancel_order", map[string]any{"orderId": orderID, "accountType": c.accountType}, &out); err != nil { return nil, err } return out.CanCancel, nil } -func (c *Client) contractList(ctx context.Context, path, accID string) ([]map[string]string, error) { - body := map[string]any{} - if accID != "" { - body["accId"] = accID - } +func (c *Client) contractList(ctx context.Context, path string) ([]map[string]string, error) { var out struct { Data []map[string]string `json:"data"` } - if err := c.post(ctx, path, body, &out); err != nil { + if err := c.post(ctx, path, nil, &out); err != nil { return nil, err } return out.Data, nil } -func (c *Client) DebtContract(ctx context.Context, accID string) ([]map[string]string, error) { - return c.contractList(ctx, "/api/trade/debt_contract", accID) +func (c *Client) DebtContract(ctx context.Context) ([]map[string]string, error) { + return c.contractList(ctx, "/api/trade/debt_contract") } -func (c *Client) AssureContract(ctx context.Context, accID string) ([]map[string]string, error) { - return c.contractList(ctx, "/api/trade/assure_contract", accID) +func (c *Client) AssureContract(ctx context.Context) ([]map[string]string, error) { + return c.contractList(ctx, "/api/trade/assure_contract") } -func (c *Client) EnableShortContract(ctx context.Context, accID string) ([]map[string]string, error) { - return c.contractList(ctx, "/api/trade/enable_short_contract", accID) +func (c *Client) EnableShortContract(ctx context.Context) ([]map[string]string, error) { + return c.contractList(ctx, "/api/trade/enable_short_contract") } func (c *Client) IPOData(ctx context.Context, typ string) (any, error) { - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/trade/ipo_data", map[string]any{"type": typ}, &out); err != nil { - return nil, err - } - return out.Data, nil + return c.postField(ctx, "/api/trade/ipo_data", map[string]any{"type": typ}, "data") } -func (c *Client) NewPurchaseLimit(ctx context.Context, accid string) (any, error) { - body := map[string]any{} - if accid != "" { - body["accid"] = accid - } - var out struct { - Data any `json:"data"` - } - if err := c.post(ctx, "/api/trade/new_purchase_limit", body, &out); err != nil { - return nil, err - } - return out.Data, nil +func (c *Client) NewPurchaseLimit(ctx context.Context) (any, error) { + return c.postField(ctx, "/api/trade/new_purchase_limit", nil, "data") }