refactor: extract ResolveClaw402WalletKey to store layer and expand OKX margin mode tests

- Move duplicated claw402 wallet resolution logic into store.AIModelStore.ResolveClaw402WalletKey
- api/strategy.go and manager/trader_manager.go now delegate to the shared method
- Add detailed doc comment on OKX SetMarginMode explaining the local-state-only approach
  and why the legacy /api/v5/account/set-isolated-mode endpoint is not called
- Add 3 new test cases: cross mode leverage, OpenShort tdMode, SetTakeProfit tdMode
This commit is contained in:
root
2026-04-17 10:57:42 +08:00
parent b9b0a52137
commit 802590c2b9
5 changed files with 115 additions and 45 deletions
+13 -1
View File
@@ -80,7 +80,19 @@ func (t *OKXTrader) GetBalance() (map[string]interface{}, error) {
return result, nil
}
// SetMarginMode sets margin mode
// SetMarginMode configures the margin mode (cross/isolated) that will be applied
// to all subsequent leverage and order requests for this trader instance.
//
// OKX V5 unified accounts do not expose a per-symbol mode-switch endpoint that
// works reliably — the legacy /api/v5/account/set-isolated-mode endpoint returns
// error 51000 ("Parameter isoMode error") when called on a unified account.
// Instead, OKX applies the mode per-request via the mgnMode field on
// /api/v5/account/set-leverage and via the tdMode field on order placement.
//
// This implementation therefore stores the configured mode locally and injects it
// into each subsequent API request, rather than making an API call here.
// NOTE: unlike Binance/Bybit implementations of this interface, no network call
// is made — the method only updates local state.
func (t *OKXTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
t.isCrossMargin = isCrossMargin
mgnMode := t.marginMode()
+57
View File
@@ -188,3 +188,60 @@ func TestOKXPlaceLimitOrderUsesConfiguredMarginMode(t *testing.T) {
t.Fatalf("expected isolated tdMode, got %#v", orderRequests[0].Body["tdMode"])
}
}
func TestOKXCrossMarginModeUsedInLeverage(t *testing.T) {
rt := &recordingTransport{}
trader := newTestOKXTrader(rt, true) // cross margin
if err := trader.SetLeverage("BTCUSDT", 10); err != nil {
t.Fatalf("SetLeverage failed: %v", err)
}
leverageRequests := rt.requestsForPath(okxLeveragePath)
if len(leverageRequests) != 2 {
t.Fatalf("expected 2 leverage requests, got %d", len(leverageRequests))
}
for _, req := range leverageRequests {
if req.Body["mgnMode"] != "cross" {
t.Fatalf("expected cross leverage mode, got %#v", req.Body["mgnMode"])
}
}
}
func TestOKXOpenShortUsesConfiguredMarginMode(t *testing.T) {
rt := &recordingTransport{}
trader := newTestOKXTrader(rt, false) // isolated
if _, err := trader.OpenShort("BTCUSDT", 0.1, 5); err != nil {
t.Fatalf("OpenShort failed: %v", err)
}
orderRequests := rt.requestsForPath(okxOrderPath)
if len(orderRequests) == 0 {
t.Fatal("expected at least one order request")
}
lastOrder := orderRequests[len(orderRequests)-1]
if lastOrder.Body["tdMode"] != "isolated" {
t.Fatalf("expected isolated tdMode for OpenShort, got %#v", lastOrder.Body["tdMode"])
}
}
func TestOKXSetTakeProfitUsesConfiguredMarginMode(t *testing.T) {
rt := &recordingTransport{}
trader := newTestOKXTrader(rt, false) // isolated
if err := trader.SetTakeProfit("BTCUSDT", "LONG", 0.1, 100000); err != nil {
t.Fatalf("SetTakeProfit failed: %v", err)
}
algoRequests := rt.requestsForPath(okxAlgoOrderPath)
if len(algoRequests) != 1 {
t.Fatalf("expected 1 algo order request, got %d", len(algoRequests))
}
if algoRequests[0].Body["tdMode"] != "isolated" {
t.Fatalf("expected isolated tdMode for SetTakeProfit, got %#v", algoRequests[0].Body["tdMode"])
}
}