dev 2
This commit is contained in:
@@ -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=''):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package logic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 != "<nil>" {
|
||||
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 != "<nil>" {
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package logic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
39
go-client/libs/http.go
Normal file
39
go-client/libs/http.go
Normal file
@@ -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
|
||||
}
|
||||
58
go-client/libs/market.go
Normal file
58
go-client/libs/market.go
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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]) + "..."
|
||||
}
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user