diff --git a/go-client/apps/zt/logic/boot.go b/go-client/apps/trend/logic/boot.go similarity index 77% rename from go-client/apps/zt/logic/boot.go rename to go-client/apps/trend/logic/boot.go index 04f2501..9c12bb7 100644 --- a/go-client/apps/zt/logic/boot.go +++ b/go-client/apps/trend/logic/boot.go @@ -39,7 +39,7 @@ func Overview(assets *sdk.Assets, positions []sdk.Position) { } } -func RunOnce(ctx context.Context, client *sdk.Client, signals *libs.SignalResult) { +func RunOnce(ctx context.Context, client *sdk.Client, signals []*libs.SignalItem) { if !libs.TradingTime(time.Now()) { return } @@ -71,12 +71,13 @@ func RunOnce(ctx context.Context, client *sdk.Client, signals *libs.SignalResult allCodes = append(allCodes, pos_codes...) // 5 验证有效开仓信号 - allowOpen := make([]libs.SignalItem, 0) - for code, item := range signals.Data { - if !slices.Contains(pos_codes, code) { + allowOpen := make([]*libs.SignalItem, 0) + for _, item := range signals { + if !slices.Contains(pos_codes, item.Code) { allowOpen = append(allowOpen, item) } } + allowOpen = SignalFilter(allowOpen, config.Account.SignalAllow) // 6 获取行情tick ticks, err := client.FullTick(ctx, allCodes) @@ -87,10 +88,24 @@ func RunOnce(ctx context.Context, client *sdk.Client, signals *libs.SignalResult // 7 执行开仓:有开仓信号 && 大盘指数允许开仓 if len(allowOpen) > 0 && IsAllow { - openSignal(ctx, client, ticks, allowOpen) + openSignal(client, ticks, allowOpen) } // 8 持仓计算 - buyBudget := assets.Available - managePositions(ctx, client, ticks, positions, IsAllow, &buyBudget) + managePositions(client, ticks, positions, IsAllow) +} + +func SignalFilter(in []*libs.SignalItem, name []string) []*libs.SignalItem { + newSignalItem := make([]*libs.SignalItem, 0) + if len(name) == 0 { + return newSignalItem + } + for _, i := range in { + for _, n := range name { + if i.SignalKey == n { + newSignalItem = append(newSignalItem, i) + } + } + } + return newSignalItem } diff --git a/go-client/apps/zt/logic/open.go b/go-client/apps/trend/logic/open.go similarity index 87% rename from go-client/apps/zt/logic/open.go rename to go-client/apps/trend/logic/open.go index 3cc0680..954a707 100644 --- a/go-client/apps/zt/logic/open.go +++ b/go-client/apps/trend/logic/open.go @@ -1,14 +1,12 @@ package logic import ( - "context" - "big-qmt/go-client/config" "big-qmt/go-client/libs" "big-qmt/go-client/sdk" ) -func openSignal(ctx context.Context, client *sdk.Client, ticks map[string]sdk.Tick, openSignals []libs.SignalItem) { +func openSignal(client *sdk.Client, ticks map[string]sdk.Tick, openSignals []*libs.SignalItem) { for _, item := range openSignals { // 是否有锁 if OrderBook.IsLock("BUY", item.Code) { diff --git a/go-client/apps/zt/logic/order.go b/go-client/apps/trend/logic/order.go similarity index 100% rename from go-client/apps/zt/logic/order.go rename to go-client/apps/trend/logic/order.go diff --git a/go-client/apps/trend/logic/positions.go b/go-client/apps/trend/logic/positions.go new file mode 100644 index 0000000..885417a --- /dev/null +++ b/go-client/apps/trend/logic/positions.go @@ -0,0 +1,155 @@ +package logic + +import ( + "math" + "sync" + + "big-qmt/go-client/config" + "big-qmt/go-client/libs" + "big-qmt/go-client/sdk" +) + +const ( + legBase = "base" + legAdded = "add" +) + +var ( + peakMu sync.Mutex + peakGrids = make(map[string]int) +) + +func managePositions(client *sdk.Client, ticks map[string]sdk.Tick, positions []sdk.Position, marketOK bool) { + for _, pos := range positions { + item, err := QuantState.Get(code) + if err != nil || item.BaseStatus == StatusIng || item.AddedStatus == StatusIng || position.Volume != item.BaseQty+item.AddedQty { + continue + } + price := ticks[code].LastPrice + if price <= 0 { + continue + } + if item.AddedQty > 0 { + pnl := (price - item.AddedCost) / item.AddedCost * 100 + if shouldSell(code, legAdded, pnl) { + sell(client, item, position.CanUseVolume, item.AddedQty, legAdded, pnl) + } + continue + } + + pnl := (price - item.BaseCost) / item.BaseCost * 100 + if shouldSell(code, legBase, pnl) { + sell(client, item, position.CanUseVolume, item.BaseQty, legBase, pnl) + } else if pnl <= config.Account.LossTriggerPct { + buyAdded(client, item, price, marketOK, budget) + } + } +} + +func syncAdded(item *StateItem, position sdk.Position) { + addedQty := position.Volume - item.BaseQty + if addedQty <= 0 { + item.BaseQty = position.Volume + item.BaseCost = position.OpenPrice + item.AddedQty = 0 + item.AddedCost = 0 + item.AddedStatus = StatusNone + peakMu.Lock() + delete(peakGrids, item.Code+"|"+legAdded) + peakMu.Unlock() + return + } + + item.AddedQty = addedQty + totalCost := position.OpenPrice * float64(position.Volume) + baseCost := item.BaseCost * float64(item.BaseQty) + item.AddedCost = math.Max(0, (totalCost-baseCost)/float64(addedQty)) + item.AddedStatus = StatusOk +} + +func buyAdded(client *sdk.Client, item *StateItem, price float64, marketOK bool, budget *float64) { + if !marketOK || !PosbuyWatch.Triggered("补仓", item.Code, price) { + return + } + volume := libs.CalcBuyVolume(price, config.Account.BuyValue) + amount := price * float64(volume) + if amount > *budget || orderBusy(item.Code, "BUY") { + return + } + + orderID := NewOrderID(legAdded) + if !OrderBook.Place(client, sdk.OpBuy, item.Code, volume, orderID) { + return + } + item.AddedOrderId = orderID + item.AddedNum++ + item.AddedQty = volume + item.AddedCost = price + item.AddedStatus = StatusIng + QuantState.Set(item) + *budget -= amount +} + +func sell(client *sdk.Client, item *StateItem, usable, volume int, leg string, pnl float64) { + volume -= volume % 100 + if volume <= 0 || usable < volume || orderBusy(item.Code, "SELL") { + return + } + orderID := NewOrderID(leg) + if !OrderBook.Place(client, sdk.OpSell, item.Code, volume, orderID) { + return + } + if leg == legAdded { + item.AddedOrderId = orderID + item.AddedStatus = StatusIng + } else { + item.BaseOrderId = orderID + item.BaseStatus = StatusIng + } + QuantState.Set(item) + logf("INFO", "[止盈] %s 卖出%d股,盈利=%.2f%%", item.Code, volume, pnl) +} + +func orderBusy(code, side string) bool { + OrderBook.mu.Lock() + defer OrderBook.mu.Unlock() + order := OrderBook.Data[side+"-"+code] + if order == nil { + return false + } + switch order.Status { + case "48", "49", "50", "51", "52", "55": + return true + default: + return false + } +} + +func shouldSell(code, leg string, pnl float64) bool { + if pnl < config.Account.MinProfitPct { + return false + } + grid := int(math.Floor(pnl / config.Account.GridStepPct)) + key := code + "|" + leg + peakMu.Lock() + defer peakMu.Unlock() + peak, tracked := peakGrids[key] + if !tracked || grid > peak { + peakGrids[key] = grid + return false + } + return grid < peak +} + +func forget(code string) { + OpenWatch.mu.Lock() + delete(OpenWatch.Data, code) + OpenWatch.mu.Unlock() + PosbuyWatch.mu.Lock() + delete(PosbuyWatch.Data, code) + PosbuyWatch.mu.Unlock() + peakMu.Lock() + delete(peakGrids, code+"|"+legBase) + delete(peakGrids, code+"|"+legAdded) + peakMu.Unlock() +} diff --git a/go-client/apps/zt/logic/state.go b/go-client/apps/trend/logic/state.go similarity index 100% rename from go-client/apps/zt/logic/state.go rename to go-client/apps/trend/logic/state.go diff --git a/go-client/apps/zt/logic/watch.go b/go-client/apps/trend/logic/watch.go similarity index 100% rename from go-client/apps/zt/logic/watch.go rename to go-client/apps/trend/logic/watch.go diff --git a/go-client/apps/zt/main.go b/go-client/apps/trend/main.go similarity index 74% rename from go-client/apps/zt/main.go rename to go-client/apps/trend/main.go index be6e728..5fdcfd3 100644 --- a/go-client/apps/zt/main.go +++ b/go-client/apps/trend/main.go @@ -8,7 +8,7 @@ import ( "syscall" "time" - "big-qmt/go-client/apps/zt/logic" + "big-qmt/go-client/apps/trend/logic" "big-qmt/go-client/config" "big-qmt/go-client/libs" "big-qmt/go-client/sdk" @@ -17,7 +17,7 @@ import ( ) var ( - StrategyName = "zt" + StrategyName = "trend" ) func main() { @@ -38,7 +38,7 @@ func main() { return } - // 初始化 + // 第三步 初始化 logic.NewOrderBook() logic.InitWatch() if err := logic.InitState(StrategyName); err != nil { @@ -47,15 +47,14 @@ func main() { if err := logic.SyncPositions(positions); err != nil { log.Panicln("ERROR", err.Error()) } - - // 第三步:连接成功后接管首次持仓并打印账户概览。 - logic.Overview(assets, positions) - signals, err := libs.FetchSignal(libs.Dcm_Signal, config.Account.HostKey) + signals, err := libs.InitSignals() if err != nil { - log.Printf("[ERROR] [ZT] 获取开仓信号失败: %v", err) - signals = &libs.SignalResult{Data: map[string]libs.SignalItem{}} + log.Panicln("ERROR", err.Error()) } - log.Printf("[INFO] [ZT] 已加载 %d 个开仓信号", len(signals.Data)) + + // 打印启动信息 + logic.Overview(assets, positions) + log.Printf("[INFO] 已加载 %d 个开仓信号", len(signals)) // 第四步:工作日每 30 秒触发,交易时段由 RunOnce 统一判断。 scheduler := cron.New( @@ -68,11 +67,11 @@ func main() { log.Fatalf("[ERROR] 创建计划任务失败: %v", err) } scheduler.Start() - log.Printf("[INFO] [ZT] 计划任务已启动") + log.Printf("[INFO] 计划任务已启动") <-ctx.Done() <-scheduler.Stop().Done() - log.Printf("[INFO] [ZT] 停止") + log.Printf("[INFO] 停止") } func waitForQMT(ctx context.Context, client *sdk.Client) (*sdk.Assets, []sdk.Position, bool) { @@ -82,10 +81,10 @@ func waitForQMT(ctx context.Context, client *sdk.Client) (*sdk.Assets, []sdk.Pos _, positions, positionsErr := client.Positions(attempt) cancel() if assetsErr == nil && positionsErr == nil { - log.Printf("[INFO] [ZT] QMT连接成功: %s", config.Global.QMTBaseURL) + log.Printf("[INFO] QMT连接成功: %s", config.Global.QMTBaseURL) return assets, positions, true } - log.Printf("[WARNING] [ZT] QMT未就绪,5秒后重试: assets=%v positions=%v", assetsErr, positionsErr) + log.Printf("[WARNING] QMT未就绪,5秒后重试: assets=%v positions=%v", assetsErr, positionsErr) select { case <-ctx.Done(): return nil, nil, false diff --git a/go-client/apps/zt/logic/positions.go b/go-client/apps/zt/logic/positions.go deleted file mode 100644 index f250953..0000000 --- a/go-client/apps/zt/logic/positions.go +++ /dev/null @@ -1,237 +0,0 @@ -package logic - -import ( - "context" - "math" - "sync" - - "big-qmt/go-client/config" - "big-qmt/go-client/libs" - "big-qmt/go-client/sdk" -) - -const ( - legBase = "base" - legAdded = "add" -) - -var ( - peakMu sync.Mutex - peakGrids = make(map[string]int) -) - -func managePositions(_ context.Context, client *sdk.Client, ticks map[string]sdk.Tick, positions []sdk.Position, marketOK bool, budget *float64) { - if QuantState == nil || OrderBook == nil || positions == nil { - return - } - if err := OrderBook.Refresh(client); err != nil { - logf("ERROR", "[持仓] 刷新委托失败: %v", err) - return - } - - current := make(map[string]sdk.Position, len(positions)) - for _, position := range positions { - if position.StockCode != "" { - current[position.StockCode] = position - } - } - - for _, code := range stateCodes() { - syncPosition(code, current[code]) - } - for code, position := range current { - managePosition(client, ticks[code], position, marketOK, budget) - } - - saveState() -} - -func syncPosition(code string, position sdk.Position) { - item, err := QuantState.Get(code) - if err != nil || orderBusy(code, "BUY") || orderBusy(code, "SELL") { - return - } - if position.Volume <= 0 { - QuantState.Delete(code) - forget(code) - return - } - - if item.BaseStatus == StatusIng { - item.BaseQty = max(0, position.Volume-item.AddedQty) - item.BaseCost = position.OpenPrice - item.BaseStatus = StatusOk - } - if item.AddedStatus == StatusIng { - syncAdded(item, position) - } - QuantState.Set(item) -} - -func syncAdded(item *StateItem, position sdk.Position) { - addedQty := position.Volume - item.BaseQty - if addedQty <= 0 { - item.BaseQty = position.Volume - item.BaseCost = position.OpenPrice - item.AddedQty = 0 - item.AddedCost = 0 - item.AddedStatus = StatusNone - clearPeak(item.Code, legAdded) - return - } - - item.AddedQty = addedQty - totalCost := position.OpenPrice * float64(position.Volume) - baseCost := item.BaseCost * float64(item.BaseQty) - item.AddedCost = math.Max(0, (totalCost-baseCost)/float64(addedQty)) - item.AddedStatus = StatusOk -} - -func managePosition(client *sdk.Client, tick sdk.Tick, position sdk.Position, marketOK bool, budget *float64) { - item, err := QuantState.Get(position.StockCode) - if err != nil || tick.LastPrice <= 0 || !positionReady(item, position) { - return - } - if item.AddedQty > 0 { - pnl := profit(tick.LastPrice, item.AddedCost) - if shouldSell(item.Code, legAdded, pnl) { - sell(client, item, position.CanUseVolume, item.AddedQty, legAdded, pnl) - } - return - } - - pnl := profit(tick.LastPrice, item.BaseCost) - if shouldSell(item.Code, legBase, pnl) { - sell(client, item, position.CanUseVolume, item.BaseQty, legBase, pnl) - } else if pnl <= config.Account.LossTriggerPct { - buyAdded(client, item, tick.LastPrice, marketOK, budget) - } -} - -func positionReady(item *StateItem, position sdk.Position) bool { - return item.BaseStatus != StatusIng && item.AddedStatus != StatusIng && - position.Volume > 0 && position.Volume%100 == 0 && - position.Volume == item.BaseQty+item.AddedQty -} - -func buyAdded(client *sdk.Client, item *StateItem, price float64, marketOK bool, budget *float64) { - if !marketOK || budget == nil || PosbuyWatch == nil || !PosbuyWatch.Triggered("补仓", item.Code, price) { - return - } - volume := calcBuyVolume(price, config.Account.BuyValue) - amount := price * float64(volume) - if volume <= 0 || amount > *budget || orderBusy(item.Code, "BUY") { - return - } - - orderID := NewOrderID(legAdded) - if !OrderBook.Place(client, sdk.OpBuy, item.Code, volume, orderID) { - return - } - item.AddedOrderId = orderID - item.AddedNum++ - item.AddedQty = volume - item.AddedCost = price - item.AddedStatus = StatusIng - QuantState.Set(item) - *budget -= amount - saveState() -} - -func sell(client *sdk.Client, item *StateItem, usable, volume int, leg string, pnl float64) { - volume -= volume % 100 - if volume <= 0 || usable < volume || orderBusy(item.Code, "SELL") { - return - } - orderID := NewOrderID(leg) - if !OrderBook.Place(client, sdk.OpSell, item.Code, volume, orderID) { - return - } - if leg == legAdded { - item.AddedOrderId = orderID - item.AddedStatus = StatusIng - } else { - item.BaseOrderId = orderID - item.BaseStatus = StatusIng - } - QuantState.Set(item) - saveState() - logf("INFO", "[止盈] %s 卖出%d股,盈利=%.2f%%", item.Code, volume, pnl) -} - -func orderBusy(code, side string) bool { - OrderBook.mu.Lock() - defer OrderBook.mu.Unlock() - order := OrderBook.Data[side+"-"+code] - if order == nil { - return false - } - switch order.Status { - case "48", "49", "50", "51", "52", "55": - return true - default: - return false - } -} - -func shouldSell(code, leg string, pnl float64) bool { - if pnl < config.Account.MinProfitPct || config.Account.GridStepPct <= 0 { - return false - } - grid := int(math.Floor(pnl / config.Account.GridStepPct)) - key := peakKey(code, leg) - peakMu.Lock() - defer peakMu.Unlock() - peak, tracked := peakGrids[key] - if !tracked || grid > peak { - peakGrids[key] = grid - return false - } - return grid < peak -} - -func stateCodes() []string { - QuantState.mu.Lock() - defer QuantState.mu.Unlock() - return append([]string(nil), QuantState.Codes...) -} - -func saveState() { - if err := QuantState.Save(); err != nil { - logf("ERROR", "%v", err) - } -} - -func profit(price, cost float64) float64 { - if cost <= 0 { - return math.Inf(-1) - } - return (price - cost) / cost * 100 -} - -func calcBuyVolume(price, value float64) int { - return libs.CalcBuyVolume(price, value) -} - -func peakKey(code, leg string) string { return code + "|" + leg } - -func clearPeak(code, leg string) { - peakMu.Lock() - delete(peakGrids, peakKey(code, leg)) - peakMu.Unlock() -} - -func forget(code string) { - if OpenWatch != nil { - OpenWatch.mu.Lock() - delete(OpenWatch.Data, code) - OpenWatch.mu.Unlock() - } - if PosbuyWatch != nil { - PosbuyWatch.mu.Lock() - delete(PosbuyWatch.Data, code) - PosbuyWatch.mu.Unlock() - } - clearPeak(code, legBase) - clearPeak(code, legAdded) -} diff --git a/go-client/apps/zt/策略说明.md b/go-client/apps/zt/策略说明.md deleted file mode 100644 index 1a5b63c..0000000 --- a/go-client/apps/zt/策略说明.md +++ /dev/null @@ -1,67 +0,0 @@ -# 做 T 策略说明 - -## 启动准备 - -策略启动后,根据当前计算机选择对应的交易账户。账户连接成功后,展示总资产、可用资金和当前持仓,并读取一次当日开仓信号。运行期间一直使用这份内存信号,不再重复请求。 - -首次运行且没有历史策略状态时,账户中已有的全部持仓都作为底仓接管。后续运行以已保存的策略状态为准。 - -## 运行时间 - -策略仅在周一至周五运行,周末不执行交易计算。每天运行时段为: - -- 09:30 至 11:30; -- 13:00 至 15:00。 - -交易时段内每 30 秒计算一次。午间休市和收盘后不执行交易计算。 - -## 每轮计算流程 - -每轮读取账户资产、当前持仓、最新行情和委托情况,并处理已经超过等待时间的委托。 - -随后判断开仓信号中的股票是否已经持仓。未持仓信号和已有持仓可以在同一轮中分别处理,不会因为存在未开仓信号而停止管理已有持仓。 - -大盘信号只控制买入行为。大盘不允许开仓时,不新建底仓,也不补仓;止盈卖出、委托清理和持仓状态同步仍然正常进行。 - -## 底仓开仓 - -开仓信号对应的股票尚未持仓,且大盘允许开仓时,进入价格观察阶段。 - -观察期间持续记录最低价格。当价格从观察低点反弹达到设定幅度后,触发底仓买入。 - -买入数量根据配置的 `buy_value` 和当前股价计算,向下取整为整手。不足一手时按一手买入。 - -提交底仓买入后,策略记录正在开仓的状态,等待委托和持仓结果确认。 - -## 补仓 - -底仓亏损达到配置的补仓触发比例后,进入补仓价格观察阶段。 - -观察期间持续记录新的最低价格。当价格从低点反弹达到设定幅度,且大盘允许买入时,触发补仓。 - -补仓数量同样根据 `buy_value` 和补仓时的股价独立计算,因此补仓数量不要求与底仓数量相同。部分成交的数量按实际补仓数量接管。 - -## 网格止盈 - -底仓和补仓分别计算盈利比例,并分别记录本次运行期间达到的最高盈利网格。 - -盈利达到最低止盈比例后,策略开始跟踪最高网格。当盈利从最高网格回落时,触发对应仓位的卖出: - -- 补仓达到回撤条件时,只卖出补仓部分; -- 底仓达到回撤条件时,可以卖出全部底仓。 - -最高盈利网格只在本次程序运行期间保留,程序重新启动后重新开始记录。 - -## 委托确认 - -每笔委托生成一个不超过 24 个字符的唯一订单号。策略根据账户回写的委托结果确认订单状态,并将结果同步到策略状态。 - -回写状态包括已提交、部分成交、全部成交、已撤销和已拒绝。没有回写结果的订单不会直接重复下单。 - -委托超过等待时间后,策略先检查当前委托和持仓。如果委托已经不存在,则释放该股票的等待状态,允许后续交易轮次重新判断,但不会在释放状态的同一轮自动重复下单。 - -## 状态保护 - -策略持续保存底仓数量与成本、补仓数量与成本、当前待确认动作和最近委托状态。 - -状态文件不存在时,启动前持仓全部作为底仓接管。状态文件内容异常时,策略停止交易处理,不会自动删除或重建异常文件。 diff --git a/go-client/config/config.go b/go-client/config/config.go index 304f48d..7e784c1 100644 --- a/go-client/config/config.go +++ b/go-client/config/config.go @@ -17,21 +17,29 @@ var ( ) type GlobalConfig struct { - QMTBaseURL string `yaml:"qmt_base_url"` - QMTToken string `yaml:"qmt_token"` - APIHost string `yaml:"api_host"` - QMTDataDir string `yaml:"qmt_data_dir"` - Hosts map[string]string `yaml:"hosts"` + QMTBaseURL string `yaml:"qmt_base_url"` + QMTToken string `yaml:"qmt_token"` + APIHost string `yaml:"api_host"` + QMTDataDir string `yaml:"qmt_data_dir"` + Hosts map[string]string `yaml:"hosts"` + Signals map[string]SignalConfig `yaml:"signals"` +} + +type SignalConfig struct { + Url string `yaml:"url"` + Timezone string `yaml:"timezone"` + GtLastPriceIsOpen bool `yaml:"gt_last_price_is_open"` } type AccountConfig struct { - AccountID string `yaml:"account_id"` - HostKey string `yaml:"host_key"` - BuyValue float64 `yaml:"buy_value"` - MinCashRatio float64 `yaml:"min_cash_ratio"` - LossTriggerPct float64 `yaml:"loss_trigger_pct"` - GridStepPct float64 `yaml:"grid_step_pct"` - MinProfitPct float64 `yaml:"min_profit_pct"` + AccountID string `yaml:"account_id"` + HostKey string `yaml:"host_key"` + BuyValue float64 `yaml:"buy_value"` + MinCashRatio float64 `yaml:"min_cash_ratio"` + LossTriggerPct float64 `yaml:"loss_trigger_pct"` + GridStepPct float64 `yaml:"grid_step_pct"` + MinProfitPct float64 `yaml:"min_profit_pct"` + SignalAllow []string `yaml:"signal_allow"` } // Load 根据 global.yaml 中的 hosts 映射加载当前计算机的账户配置。 @@ -68,6 +76,7 @@ func Load(etcDir string) error { return fmt.Errorf("buy_value、grid_step_pct 和超时时间必须大于 0") } + account.HostKey = strings.ToLower(account.HostKey) Global = &global Account = &account diff --git a/go-client/etc/dev.yaml b/go-client/etc/dev.yaml index da6c960..ed1763f 100644 --- a/go-client/etc/dev.yaml +++ b/go-client/etc/dev.yaml @@ -1,11 +1,11 @@ # 当前计算机使用的账户和策略参数。 account_id: CHANGE_ME -host_key: "" +host_key: "dev" buy_value: 5000 min_cash_ratio: 0.10 loss_trigger_pct: -30 grid_step_pct: 1 min_profit_pct: 2 rebound_threshold: 0.61 -order_timeout_seconds: 60 -watch_timeout_seconds: 300 +signal_allow: + - dcm diff --git a/go-client/etc/global.yaml b/go-client/etc/global.yaml index c9698b1..fec6d29 100644 --- a/go-client/etc/global.yaml +++ b/go-client/etc/global.yaml @@ -1,9 +1,27 @@ # 系统公共参数。hosts 将 Windows 计算机名映射到账户配置文件。 qmt_base_url: http://127.0.0.1:10086 qmt_token: QMTbyYanweidong -api_host: http://go.apinb.com +api_host: http://139.224.247.176:13499 qmt_data_dir: D:/qmt_strategy_data -state_dir: D:/qmt_strategy_state hosts: DESKTOP-39H91QV: dev.yaml + +signals: + dcm: + url: /a/dcm_signal + timezone: * # *代表全时间段,9:30-10:30,13:30-14:30 代表2个时间段 + gt_last_price_is_open: false # 大于信号的昨收价是否开仓 + morning: + url: /a/morning_signal + timezone: 9:30-10:30 # *代表全时间段,9:30-10:30,13:30-14:30 代表2个时间段 + gt_last_price_is_open: true # 大于信号的昨收价是否开仓 + tail: + url: /a/tail_signal + timezone: 14:30-14:55 # *代表全时间段,9:30-10:30,13:30-14:30 代表2个时间段 + gt_last_price_is_open: false # 大于信号的昨收价是否开仓 + arbitrage: + url: /a/arbitrage_signal + timezone: * # *代表全时间段,9:30-10:30,13:30-14:30 代表2个时间段 + gt_last_price_is_open: false # 大于信号的昨收价是否开仓 + diff --git a/go-client/libs/signal.go b/go-client/libs/signal.go index 038d022..84e0547 100644 --- a/go-client/libs/signal.go +++ b/go-client/libs/signal.go @@ -1,13 +1,9 @@ package libs import ( + "big-qmt/go-client/config" "encoding/json" "fmt" - "log" -) - -var ( - Dcm_Signal = "/a/dcm_signal" ) type SignalResult struct { @@ -19,6 +15,7 @@ type SignalResult struct { } type SignalItem struct { + SignalKey string `json:"signal_key"` Code string `json:"code"` Name string `json:"name"` Desc string `json:"desc"` @@ -26,21 +23,35 @@ type SignalItem struct { TechIndicator map[string]float64 `json:"tech_indicator"` } +func InitSignals() ([]*SignalItem, error) { + CacheSignals := make([]*SignalItem, 0) + for key, s := range config.Global.Signals { + result, err := FetchSignal(s.Url) + if err != nil { + return nil, fmt.Errorf("[信号] %s, 获取错误:%v", key, err) + } + for _, item := range result.Data { + item.SignalKey = key + CacheSignals = append(CacheSignals, &item) + } + } + return CacheSignals, nil +} + // FetchSignals 启动时读取信号,运行期间直接使用内存数据。 -func FetchSignal(subUrl, host_key string) (*SignalResult, error) { +func FetchSignal(subUrl string) (*SignalResult, error) { // gen url. - fullUrl := fmt.Sprintf("%s%s?host_key=%s&t=%s", API_HOST, subUrl, host_key, randStr(16)) + fullUrl := fmt.Sprintf("%s%s?t=%s", API_HOST, subUrl, randStr(16)) // doing payload, err := GetJSON(fullUrl, HTTPTimeout) if err != nil { - log.Printf("[ERROR] 获取60m大盘信号失败: %s %v", fullUrl, err) - return nil, err + return nil, fmt.Errorf("开仓信号获取失败: %s %v", fullUrl, err) } var result SignalResult err = json.Unmarshal(payload, &result) if err != nil { - return nil, err + return nil, fmt.Errorf("开仓信号解析失败: %s %v", fullUrl, err) } return &result, nil }