dev 1
This commit is contained in:
193
go-client/sdk/account.go
Normal file
193
go-client/sdk/account.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Position 对应 HoldingHandler 封装后的持仓。
|
||||
type Position struct {
|
||||
StockCode string `json:"StockCode"`
|
||||
StockName string `json:"StockName"`
|
||||
Direction any `json:"Direction"`
|
||||
Volume int `json:"Volume"`
|
||||
OpenPrice float64 `json:"OpenPrice"`
|
||||
FloatProfit float64 `json:"FloatProfit"`
|
||||
MarketValue float64 `json:"MarketValue"`
|
||||
StockHolder string `json:"StockHolder"`
|
||||
FrozenVolume int `json:"FrozenVolume"`
|
||||
CanUseVolume int `json:"CanUseVolume"`
|
||||
OnRoadVolume int `json:"OnRoadVolume"`
|
||||
YesterdayVolume int `json:"YesterdayVolume"`
|
||||
LastPrice float64 `json:"LastPrice"`
|
||||
ProfitRate float64 `json:"ProfitRate"`
|
||||
FutureTradeType any `json:"FutureTradeType"`
|
||||
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, account string) ([]Position, error) {
|
||||
raw := map[string]json.RawMessage{}
|
||||
if err := c.post(ctx, "/api/v2/positions", 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("position %s: %w", code, err)
|
||||
}
|
||||
if p.StockCode == "" {
|
||||
p.StockCode = code
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
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) {
|
||||
var out Assets
|
||||
if err := c.post(ctx, "/api/v2/assets", accountBody{Account: c.Account(account)}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (c *Client) TotalMoney(ctx context.Context, account string) (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 {
|
||||
return 0, err
|
||||
}
|
||||
return out.TotalMoney, nil
|
||||
}
|
||||
|
||||
func (c *Client) AvailableMoney(ctx context.Context, account string) (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 {
|
||||
return 0, err
|
||||
}
|
||||
return out.AvailableMoney, nil
|
||||
}
|
||||
|
||||
type OrderRefResult struct {
|
||||
Status string `json:"status"`
|
||||
Action string `json:"action"`
|
||||
Stock string `json:"stock"`
|
||||
OpType int `json:"opType"`
|
||||
OrderRef string `json:"order_ref"`
|
||||
}
|
||||
|
||||
func (c *Client) Buy(ctx context.Context, stock string, price float64, volume int, prType int) (*OrderRefResult, error) {
|
||||
body := map[string]any{"stock": stock, "price": price, "volume": volume}
|
||||
if prType != 0 {
|
||||
body["prType"] = prType
|
||||
}
|
||||
var out OrderRefResult
|
||||
if err := c.post(ctx, "/api/order/buy", body, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (c *Client) Sell(ctx context.Context, stock string, price float64, volume int, prType int) (*OrderRefResult, error) {
|
||||
body := map[string]any{"stock": stock, "price": price, "volume": volume}
|
||||
if prType != 0 {
|
||||
body["prType"] = prType
|
||||
}
|
||||
var out OrderRefResult
|
||||
if err := c.post(ctx, "/api/order/sell", body, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
type OrderStatus struct {
|
||||
OrderSysID string `json:"order_sys_id"`
|
||||
Status int `json:"status"`
|
||||
VolumeLeft int `json:"volume_left"`
|
||||
VolumeTraded int `json:"volume_traded"`
|
||||
}
|
||||
|
||||
func (c *Client) OrderStatusList(ctx context.Context, account string) ([]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 {
|
||||
return nil, err
|
||||
}
|
||||
return out.Orders, nil
|
||||
}
|
||||
|
||||
type CanceledOrder struct {
|
||||
OrderSysID string `json:"order_sys_id"`
|
||||
Stock string `json:"stock"`
|
||||
VolumeLeft int `json:"volume_left"`
|
||||
}
|
||||
|
||||
type CancelAllResult struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
CanceledOrders []CanceledOrder `json:"canceled_orders"`
|
||||
CanceledSysIDs []string `json:"canceled_sys_ids"`
|
||||
}
|
||||
|
||||
func (c *Client) CancelAll(ctx context.Context, account string) (*CancelAllResult, error) {
|
||||
var out CancelAllResult
|
||||
if err := c.post(ctx, "/api/order/cancel_all", accountBody{Account: c.Account(account)}, &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) {
|
||||
var out CancelAllResult
|
||||
body := map[string]any{"stock": stock, "volume": volume, "account": c.Account(account)}
|
||||
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) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return out.Deals, nil
|
||||
}
|
||||
67
go-client/sdk/check.go
Normal file
67
go-client/sdk/check.go
Normal file
@@ -0,0 +1,67 @@
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
111
go-client/sdk/client.go
Normal file
111
go-client/sdk/client.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
// Client 调用 QMT HTTP API。
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
accountType string
|
||||
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
|
||||
}
|
||||
return c.accountType
|
||||
}
|
||||
|
||||
func (c *Client) get(ctx context.Context, path string, dest any) error {
|
||||
return c.do(ctx, http.MethodGet, path, nil, dest)
|
||||
}
|
||||
|
||||
func (c *Client) post(ctx context.Context, path string, body any, dest any) error {
|
||||
if body == nil {
|
||||
body = map[string]any{}
|
||||
}
|
||||
return c.do(ctx, http.MethodPost, path, body, dest)
|
||||
}
|
||||
|
||||
func (c *Client) do(ctx context.Context, method, path string, body any, dest any) error {
|
||||
var rdr io.Reader
|
||||
if body != nil && method != http.MethodGet {
|
||||
raw, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal request: %w", err)
|
||||
}
|
||||
rdr = bytes.NewReader(raw)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, rdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-Token", c.token)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if rdr != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
apiErr := &APIError{StatusCode: resp.StatusCode, Message: strings.TrimSpace(string(raw))}
|
||||
var parsed APIError
|
||||
if json.Unmarshal(raw, &parsed) == nil {
|
||||
if parsed.StatusCode == 0 {
|
||||
parsed.StatusCode = resp.StatusCode
|
||||
}
|
||||
if parsed.Message != "" {
|
||||
apiErr = &parsed
|
||||
}
|
||||
}
|
||||
return apiErr
|
||||
}
|
||||
if dest == nil || len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := json.Unmarshal(raw, dest); err != nil {
|
||||
return fmt.Errorf("unmarshal %s: %w; body=%s", path, err, truncate(raw, 512))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func truncate(b []byte, n int) string {
|
||||
if len(b) <= n {
|
||||
return string(b)
|
||||
}
|
||||
return string(b[:n]) + "..."
|
||||
}
|
||||
|
||||
func ArrayJoin(items []string) string {
|
||||
parts := make([]string, 0, len(items))
|
||||
for _, s := range items {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
parts = append(parts, s)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
94
go-client/sdk/coerce.go
Normal file
94
go-client/sdk/coerce.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func asString(v any) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
return x
|
||||
case json.Number:
|
||||
return x.String()
|
||||
case []byte:
|
||||
return string(x)
|
||||
default:
|
||||
return strings.TrimSpace(fmtAny(v))
|
||||
}
|
||||
}
|
||||
|
||||
func fmtAny(v any) string {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
s := strings.Trim(string(b), `"`)
|
||||
return s
|
||||
}
|
||||
|
||||
func asFloat(v any) float64 {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
return x
|
||||
case float32:
|
||||
return float64(x)
|
||||
case int:
|
||||
return float64(x)
|
||||
case int64:
|
||||
return float64(x)
|
||||
case json.Number:
|
||||
f, _ := x.Float64()
|
||||
return f
|
||||
case string:
|
||||
f, _ := strconv.ParseFloat(strings.TrimSpace(x), 64)
|
||||
return f
|
||||
default:
|
||||
f, _ := strconv.ParseFloat(asString(v), 64)
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
||||
136
go-client/sdk/context.go
Normal file
136
go-client/sdk/context.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package sdk
|
||||
|
||||
import "context"
|
||||
|
||||
type ContextInfo struct {
|
||||
Period any `json:"period"`
|
||||
Barpos any `json:"barpos"`
|
||||
TimeTickSize any `json:"time_tick_size"`
|
||||
Stockcode any `json:"stockcode"`
|
||||
DividendType any `json:"dividend_type"`
|
||||
Market any `json:"market"`
|
||||
DoBackTest any `json:"do_back_test"`
|
||||
Benchmark any `json:"benchmark"`
|
||||
Capital any `json:"capital"`
|
||||
Universe any `json:"universe"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
switch v := out.Universe.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case []any:
|
||||
codes := make([]string, 0, len(v))
|
||||
for _, item := range v {
|
||||
s := asString(item)
|
||||
if s != "" {
|
||||
codes = append(codes, s)
|
||||
}
|
||||
}
|
||||
return codes, nil
|
||||
case []string:
|
||||
return v, nil
|
||||
default:
|
||||
s := asString(v)
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return []string{s}, nil
|
||||
}
|
||||
}
|
||||
581
go-client/sdk/data.go
Normal file
581
go-client/sdk/data.go
Normal file
@@ -0,0 +1,581 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Tick struct {
|
||||
LastPrice float64
|
||||
LastClose float64
|
||||
Raw map[string]any
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type SubscribeResult struct {
|
||||
Status string `json:"status"`
|
||||
SubID any `json:"sub_id"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Client) Sector(ctx context.Context, sector string, realtime string) ([]any, error) {
|
||||
body := map[string]any{"sector": sector}
|
||||
if realtime != "" {
|
||||
body["realtime"] = realtime
|
||||
}
|
||||
var out struct {
|
||||
Stocks []any `json:"stocks"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/data/sector", body, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Stocks, nil
|
||||
}
|
||||
|
||||
func (c *Client) Industry(ctx context.Context, industry string) ([]any, error) {
|
||||
var out struct {
|
||||
Stocks []any `json:"stocks"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/data/industry", map[string]any{"industry": industry}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Stocks, nil
|
||||
}
|
||||
|
||||
func (c *Client) StockListInSector(ctx context.Context, sectorname string) ([]any, error) {
|
||||
var out struct {
|
||||
Stocks []any `json:"stocks"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/data/stock_list_in_sector", map[string]any{"sectorname": sectorname}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Stocks, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Client) HistoryData(ctx context.Context, req HistoryDataRequest) (any, error) {
|
||||
if req.Len == 0 {
|
||||
req.Len = 10
|
||||
}
|
||||
if req.SkipPaused == "" {
|
||||
req.SkipPaused = "true"
|
||||
}
|
||||
var out map[string]any
|
||||
if err := c.post(ctx, "/api/data/history_data", req, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
out := make(map[string]Tick, len(raw))
|
||||
for code, v := range raw {
|
||||
tick := Tick{Raw: map[string]any{}}
|
||||
if m, ok := v.(map[string]any); ok {
|
||||
tick.Raw = m
|
||||
tick.LastPrice = asFloat(mapField(m, "lastPrice", "last_price", "LastPrice"))
|
||||
tick.LastClose = asFloat(mapField(m, "lastClose", "last_close", "LastClose"))
|
||||
}
|
||||
out[code] = tick
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Client) TimetagToDatetime(ctx context.Context, timetag int64, format string) (any, error) {
|
||||
body := map[string]any{"timetag": timetag}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Client) TradingDates(ctx context.Context, stockcode, startDate, endDate, period string, count int) ([]any, error) {
|
||||
body := map[string]any{"stockcode": stockcode, "start_date": startDate, "end_date": endDate, "period": period}
|
||||
if count != 0 {
|
||||
body["count"] = count
|
||||
}
|
||||
var out struct {
|
||||
Dates []any `json:"dates"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/data/trading_dates", body, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Dates, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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, 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) 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
|
||||
}
|
||||
|
||||
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) 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if msg, ok := out["error"].(string); ok && msg != "" {
|
||||
return nil, &BusinessError{Message: msg}
|
||||
}
|
||||
return out["data"], nil
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if msg, ok := out["error"].(string); ok && msg != "" {
|
||||
return nil, &BusinessError{Message: msg}
|
||||
}
|
||||
return out["data"], nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func (c *Client) BSMPrice(ctx context.Context, req BSMPriceRequest) (any, error) {
|
||||
var out struct {
|
||||
Price any `json:"price"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/data/bsm_price", req, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Price, nil
|
||||
}
|
||||
|
||||
type BSMIVRequest struct {
|
||||
OptionType string `json:"optionType"`
|
||||
ObjectPrices float64 `json:"objectPrices"`
|
||||
StrikePrice float64 `json:"strikePrice"`
|
||||
OptionPrice float64 `json:"optionPrice"`
|
||||
RiskFree float64 `json:"riskFree"`
|
||||
Days int `json:"days"`
|
||||
Dividend float64 `json:"dividend"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type LocalDataRequest struct {
|
||||
StockCode string `json:"stock_code"`
|
||||
StartTime string `json:"start_time,omitempty"`
|
||||
EndTime string `json:"end_time,omitempty"`
|
||||
Period string `json:"period,omitempty"`
|
||||
DividType string `json:"divid_type,omitempty"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
if err := c.post(ctx, "/api/data/subscribe_quote", body, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (c *Client) UnsubscribeQuote(ctx context.Context, subID int) (*SubscribeResult, error) {
|
||||
var out SubscribeResult
|
||||
if err := c.post(ctx, "/api/data/unsubscribe_quote", map[string]any{"sub_id": subID}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
4
go-client/sdk/doc.go
Normal file
4
go-client/sdk/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package sdk 是 QMT_API.py HTTP 服务的 Go 客户端。
|
||||
//
|
||||
// 默认地址 http://127.0.0.1:10086,所有已注册接口都需要请求头 X-Token。
|
||||
package sdk
|
||||
38
go-client/sdk/error.go
Normal file
38
go-client/sdk/error.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// APIError 表示服务端返回的 HTTP 错误(write_error 格式)。
|
||||
type APIError struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Message string `json:"error"`
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
if e == nil {
|
||||
return "qmt api error"
|
||||
}
|
||||
if e.Message == "" {
|
||||
return fmt.Sprintf("qmt api: http %d", e.StatusCode)
|
||||
}
|
||||
return fmt.Sprintf("qmt api: http %d: %s", e.StatusCode, e.Message)
|
||||
}
|
||||
|
||||
func (e *APIError) Unauthorized() bool {
|
||||
return e != nil && e.StatusCode == http.StatusUnauthorized
|
||||
}
|
||||
|
||||
// BusinessError 表示 HTTP 200 但业务 JSON 带 error 字段。
|
||||
type BusinessError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *BusinessError) Error() string {
|
||||
if e == nil || e.Message == "" {
|
||||
return "qmt api business error"
|
||||
}
|
||||
return e.Message
|
||||
}
|
||||
47
go-client/sdk/ext.go
Normal file
47
go-client/sdk/ext.go
Normal file
@@ -0,0 +1,47 @@
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
30
go-client/sdk/sys.go
Normal file
30
go-client/sdk/sys.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package sdk
|
||||
|
||||
import "context"
|
||||
|
||||
type PythonVersion struct {
|
||||
PythonVersion string `json:"python_version"`
|
||||
PythonVersionInfo struct {
|
||||
Major int `json:"major"`
|
||||
Minor int `json:"minor"`
|
||||
Micro int `json:"micro"`
|
||||
ReleaseLevel string `json:"releaselevel"`
|
||||
Serial int `json:"serial"`
|
||||
} `json:"python_version_info"`
|
||||
}
|
||||
|
||||
func (c *Client) PythonVersion(ctx context.Context) (*PythonVersion, error) {
|
||||
var out PythonVersion
|
||||
if err := c.get(ctx, "/api/sys/python_version", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (c *Client) Shutdown(ctx context.Context) (map[string]any, error) {
|
||||
var out map[string]any
|
||||
if err := c.post(ctx, "/api/sys/shutdown", map[string]any{}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
302
go-client/sdk/trade.go
Normal file
302
go-client/sdk/trade.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package sdk
|
||||
|
||||
import "context"
|
||||
|
||||
const (
|
||||
OpBuy = 23
|
||||
OpSell = 24
|
||||
OrderTypeVolume = 1101
|
||||
PrTypeLatest = 5
|
||||
QuickTradeNow = 2
|
||||
)
|
||||
|
||||
type PassorderRequest struct {
|
||||
OpType int `json:"opType"`
|
||||
OrderType int `json:"orderType,omitempty"`
|
||||
Stock string `json:"stock"`
|
||||
PrType int `json:"prType,omitempty"`
|
||||
Price float64 `json:"price"`
|
||||
Volume int `json:"volume"`
|
||||
QuickTrade int `json:"quickTrade,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) Passorder(ctx context.Context, req PassorderRequest) (*OrderRefResult, error) {
|
||||
var out OrderRefResult
|
||||
if err := c.post(ctx, "/api/trade/passorder", req, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// PassorderLatest 按最新价下单。服务端策略名写死为 qmt,无法传投资备注。
|
||||
func (c *Client) PassorderLatest(ctx context.Context, buy bool, stock string, volume int) (*OrderRefResult, error) {
|
||||
op := OpSell
|
||||
if buy {
|
||||
op = OpBuy
|
||||
}
|
||||
return c.Passorder(ctx, PassorderRequest{
|
||||
OpType: op,
|
||||
OrderType: OrderTypeVolume,
|
||||
Stock: stock,
|
||||
PrType: PrTypeLatest,
|
||||
Price: -1,
|
||||
Volume: volume,
|
||||
QuickTrade: QuickTradeNow,
|
||||
})
|
||||
}
|
||||
|
||||
type AlgoPassorderRequest struct {
|
||||
OpType int `json:"opType"`
|
||||
OrderType int `json:"orderType,omitempty"`
|
||||
Stock string `json:"stock"`
|
||||
PrType int `json:"prType"`
|
||||
Price float64 `json:"price"`
|
||||
Volume int `json:"volume"`
|
||||
StrategyName string `json:"strategyName,omitempty"`
|
||||
QuickTrade int `json:"quickTrade,omitempty"`
|
||||
UserOrderID string `json:"userOrderId,omitempty"`
|
||||
UserOrderParam map[string]any `json:"userOrderParam,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) AlgoPassorder(ctx context.Context, req AlgoPassorderRequest) (*OrderRefResult, error) {
|
||||
var out OrderRefResult
|
||||
if err := c.post(ctx, "/api/trade/algo_passorder", req, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
type SmartAlgoPassorderRequest struct {
|
||||
OpType int `json:"opType"`
|
||||
OrderType int `json:"orderType,omitempty"`
|
||||
Stock string `json:"stock"`
|
||||
PrType int `json:"prType"`
|
||||
Price float64 `json:"price"`
|
||||
Volume int `json:"volume"`
|
||||
SmartAlgoType string `json:"smartAlgoType"`
|
||||
LimitOverRate int `json:"limitOverRate"`
|
||||
MinAmountPerOrder int `json:"minAmountPerOrder"`
|
||||
StartTime string `json:"startTime,omitempty"`
|
||||
EndTime string `json:"endTime,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) SmartAlgoPassorder(ctx context.Context, req SmartAlgoPassorderRequest) (*OrderRefResult, error) {
|
||||
var out OrderRefResult
|
||||
if err := c.post(ctx, "/api/trade/smart_algo_passorder", req, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
type StyleOrderResult struct {
|
||||
Status string `json:"status"`
|
||||
Action string `json:"action"`
|
||||
Stock string `json:"stock"`
|
||||
}
|
||||
|
||||
func (c *Client) styleOrder(ctx context.Context, path string, body map[string]any) (*StyleOrderResult, error) {
|
||||
var out StyleOrderResult
|
||||
if err := c.post(ctx, path, body, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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) 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) 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) 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) 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) 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 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) 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) 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) 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) 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) 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)
|
||||
}
|
||||
|
||||
type TaskResult struct {
|
||||
Status string `json:"status"`
|
||||
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
|
||||
}
|
||||
var out TaskResult
|
||||
if err := c.post(ctx, path, body, &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 {
|
||||
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,
|
||||
}
|
||||
var out struct {
|
||||
Data []map[string]string `json:"data"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/trade/trade_detail_data", body, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if out.Data == nil {
|
||||
return []map[string]string{}, nil
|
||||
}
|
||||
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}
|
||||
var out struct {
|
||||
OrderID string `json:"orderId"`
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
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}
|
||||
var out struct {
|
||||
LastOrderID any `json:"last_order_id"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/trade/last_order_id", body, &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}
|
||||
var out struct {
|
||||
CanCancel any `json:"can_cancel"`
|
||||
}
|
||||
if err := c.post(ctx, "/api/trade/can_cancel_order", body, &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
|
||||
}
|
||||
var out struct {
|
||||
Data []map[string]string `json:"data"`
|
||||
}
|
||||
if err := c.post(ctx, path, body, &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) AssureContract(ctx context.Context, accID string) ([]map[string]string, error) {
|
||||
return c.contractList(ctx, "/api/trade/assure_contract", accID)
|
||||
}
|
||||
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) 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user