dev 2
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user