This commit is contained in:
2026-08-25 22:35:44 +08:00
parent ec58641d09
commit f14423418a
23 changed files with 738 additions and 680 deletions

View File

@@ -6,6 +6,8 @@ import (
"sync"
"time"
"big-qmt/go-client/config"
"big-qmt/go-client/libs"
"big-qmt/go-client/sdk"
)
@@ -19,30 +21,21 @@ 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, ticks map[string]sdk.Tick, openSignals map[string]libs.SignalItem, marketOK bool, buyBudget *float64) {
if !marketOK {
return
}
if assets == nil {
if buyBudget == nil || *buyBudget <= 0 {
return
}
if assets.Available < assets.Total*cfg.MinCashRatio {
return
}
state := getState(cfg)
state := getState()
if state.LoadError != "" {
logf("ERROR", "[ZT][开仓] 状态文件异常,禁止新开仓: %s", state.LoadError)
return
}
for signalCode, signal := range openSignals {
code := normalizeCode(signalCode, "")
for code := range openSignals {
if code == "" {
if c, ok := signal["code"].(string); ok {
code = normalizeCode(c, "")
}
}
if code == "" {
logf("ERROR", "[ZT][开仓] 无效股票代码=%s", signalCode)
logf("ERROR", "[ZT][开仓] 无效股票代码")
continue
}
if state.Get(code) != nil {
@@ -52,35 +45,42 @@ func openSignal(ctx context.Context, client *sdk.Client, books *OrderBook, cfg C
if price <= 0 {
continue
}
if !dipTriggered(&openDip.mu, openDip.store, cfg, "开仓", code, price) {
if !dipTriggered(&openDip.mu, openDip.store, "开仓", code, price) {
continue
}
volume := calcOpenVolume(price, cfg.OpenMoney)
volume := calcBuyVolume(price, config.Account.BuyValue)
if volume <= 0 {
continue
}
if !books.place(ctx, client, cfg, "buy", code, volume, newOrderTag("base")) {
estimated := price * float64(volume)
if estimated > *buyBudget {
logf("INFO", "[ZT][开仓] %s 可用买入预算不足,需要=%.2f 剩余=%.2f", code, estimated, *buyBudget)
continue
}
state.Ensure(code).Pending = "base_opening"
orderID := newOrderTag("base")
if !books.place(ctx, client, sideBuy, code, volume, orderID) {
continue
}
setPending(state.Ensure(code), pendingBaseOpening, orderID)
*buyBudget -= estimated
state.Save()
logf("INFO", "[ZT][开仓] %s 买入 %d 股", code, volume)
}
state.Save()
}
func calcOpenVolume(price, openMoney float64) int {
if price <= 0 || openMoney <= 0 {
func calcBuyVolume(price, buyValue float64) int {
if price <= 0 || buyValue <= 0 {
return 0
}
hands := int(math.Floor(openMoney / (price * 100)))
// 不足一手时仍按最低一手委托。
hands := int(math.Floor(buyValue / (price * 100)))
if hands == 0 {
hands = 1
}
return hands * 100
}
func dipTriggered(mu *sync.Mutex, store map[string]dipWatch, cfg Config, tag, code string, price float64) bool {
func dipTriggered(mu *sync.Mutex, store map[string]dipWatch, tag, code string, price float64) bool {
if price <= 0 {
return false
}
@@ -89,13 +89,13 @@ func dipTriggered(mu *sync.Mutex, store map[string]dipWatch, cfg Config, tag, co
now := time.Now()
watch, ok := store[code]
if !ok || now.After(watch.ExpiresAt) || now.Equal(watch.ExpiresAt) {
store[code] = dipWatch{LastClose: price, ExpiresAt: now.Add(cfg.WatchTimeout)}
store[code] = dipWatch{LastClose: price, ExpiresAt: now.Add(time.Duration(config.Account.WatchTimeoutSec) * time.Second)}
logf("INFO", "[%s-观察] %s 现价=%.2f", tag, code, price)
return false
}
if price < watch.LastClose {
watch.LastClose = price
watch.ExpiresAt = now.Add(cfg.WatchTimeout)
watch.ExpiresAt = now.Add(time.Duration(config.Account.WatchTimeoutSec) * time.Second)
store[code] = watch
logf("INFO", "[%s-下跌] %s 刷新低点=%.2f", tag, code, price)
return false
@@ -104,8 +104,8 @@ func dipTriggered(mu *sync.Mutex, store map[string]dipWatch, cfg Config, tag, co
if rebound <= 0 {
return false
}
if rebound < cfg.ReboundThreshold {
logf("INFO", "[%s-等待] %s 反弹=%.2f%% 阈值=%.2f%%", tag, code, rebound, cfg.ReboundThreshold)
if rebound < config.Account.ReboundThreshold {
logf("INFO", "[%s-等待] %s 反弹=%.2f%% 阈值=%.2f%%", tag, code, rebound, config.Account.ReboundThreshold)
return false
}
delete(store, code)