feat: dynamic crypto detection, trade history tool, fix 9 ticker leaks

- Agent gatherContext: expand from 10 hardcoded symbols to 38+ known symbols
  plus dynamic XXXUSDT pattern extraction (caps at 5 to avoid slow context)
- Add get_trade_history tool: LLM can now query closed trades with PnL summary
  (win rate, total PnL, recent N trades across all traders)
- Fix ticker.Stop() leak in ALL 9 exchange order_sync goroutines:
  OKX, Hyperliquid, Aster, Bybit, KuCoin, Gate, Bitget, Lighter, Binance
  — tickers were never stopped when traders were stopped, leaking goroutines
This commit is contained in:
shinchan-zhai
2026-03-23 12:44:40 +08:00
parent 809b95d316
commit 54dce726cb
11 changed files with 156 additions and 7 deletions

View File

@@ -183,6 +183,7 @@ func deriveAsterOrderAction(side, positionSide string, realizedPnL float64) stri
// StartOrderSync starts background order sync task for Aster
func (t *AsterTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("aster-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromAster(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -361,6 +361,7 @@ func (t *FuturesTrader) StartOrderSync(traderID string, exchangeID string, excha
// Then run periodically
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("binance-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromBinance(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -282,6 +282,7 @@ func (t *BitgetTrader) SyncOrdersFromBitget(traderID string, exchangeID string,
// StartOrderSync starts background order sync task for Bitget
func (t *BitgetTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("bitget-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromBitget(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -299,6 +299,7 @@ func (t *BybitTrader) SyncOrdersFromBybit(traderID string, exchangeID string, ex
// StartOrderSync starts background order sync task for Bybit
func (t *BybitTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("bybit-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromBybit(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -294,6 +294,7 @@ func (t *GateTrader) SyncOrdersFromGate(traderID string, exchangeID string, exch
// StartOrderSync starts background order sync task for Gate
func (t *GateTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("gate-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromGate(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -139,6 +139,7 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
// StartOrderSync starts background order sync task
func (t *HyperliquidTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("hyperliquid-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromHyperliquid(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -402,6 +402,7 @@ func (t *KuCoinTrader) SyncOrdersFromKuCoin(traderID string, exchangeID string,
// StartOrderSync starts background order sync task for KuCoin
func (t *KuCoinTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("kucoin-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromKuCoin(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -148,6 +148,7 @@ func (t *LighterTraderV2) SyncOrdersFromLighter(traderID string, exchangeID stri
// StartOrderSync starts background order sync task
func (t *LighterTraderV2) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("lighter-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromLighter(traderID, exchangeID, exchangeType, st); err != nil {

View File

@@ -275,6 +275,7 @@ func (t *OKXTrader) SyncOrdersFromOKX(traderID string, exchangeID string, exchan
// StartOrderSync starts background order sync task for OKX
func (t *OKXTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
safe.GoNamed("okx-order-sync", func() {
for range ticker.C {
if err := t.SyncOrdersFromOKX(traderID, exchangeID, exchangeType, st); err != nil {