|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
|
|
|
|
+from src.trader_mcp.strategy_sizing import cap_amount_to_balance_target, suggest_quote_sized_amount
|
|
|
from src.trader_mcp.strategy_sdk import Strategy
|
|
from src.trader_mcp.strategy_sdk import Strategy
|
|
|
from src.trader_mcp.logging_utils import log_event
|
|
from src.trader_mcp.logging_utils import log_event
|
|
|
|
|
|
|
@@ -38,6 +39,7 @@ class Strategy(Strategy):
|
|
|
"exit_offset_pct": {"type": "float", "default": 0.002, "min": 0.0, "max": 1.0},
|
|
"exit_offset_pct": {"type": "float", "default": 0.002, "min": 0.0, "max": 1.0},
|
|
|
"order_notional_quote": {"type": "float", "default": 0.0, "min": 0.0},
|
|
"order_notional_quote": {"type": "float", "default": 0.0, "min": 0.0},
|
|
|
"max_order_notional_quote": {"type": "float", "default": 0.0, "min": 0.0},
|
|
"max_order_notional_quote": {"type": "float", "default": 0.0, "min": 0.0},
|
|
|
|
|
+ "dust_collect": {"type": "bool", "default": False},
|
|
|
"cooldown_ticks": {"type": "int", "default": 2, "min": 0, "max": 1000},
|
|
"cooldown_ticks": {"type": "int", "default": 2, "min": 0, "max": 1000},
|
|
|
"debug_orders": {"type": "bool", "default": True},
|
|
"debug_orders": {"type": "bool", "default": True},
|
|
|
}
|
|
}
|
|
@@ -158,6 +160,7 @@ class Strategy(Strategy):
|
|
|
"exit_offset_pct": round(exit_offset_pct, 6),
|
|
"exit_offset_pct": round(exit_offset_pct, 6),
|
|
|
"order_notional_quote": float(self.config.get("order_notional_quote") or 0.0),
|
|
"order_notional_quote": float(self.config.get("order_notional_quote") or 0.0),
|
|
|
"max_order_notional_quote": float(self.config.get("max_order_notional_quote") or 0.0),
|
|
"max_order_notional_quote": float(self.config.get("max_order_notional_quote") or 0.0),
|
|
|
|
|
+ "dust_collect": bool(self.config.get("dust_collect", False)),
|
|
|
"last_order_age_seconds": last_order_age_seconds,
|
|
"last_order_age_seconds": last_order_age_seconds,
|
|
|
"last_order_price": float(self.state.get("last_order_price") or 0.0),
|
|
"last_order_price": float(self.state.get("last_order_price") or 0.0),
|
|
|
"chasing_risk": chasing_risk,
|
|
"chasing_risk": chasing_risk,
|
|
@@ -177,6 +180,7 @@ class Strategy(Strategy):
|
|
|
"cooldown_ticks": int(self.config.get("cooldown_ticks") or 2),
|
|
"cooldown_ticks": int(self.config.get("cooldown_ticks") or 2),
|
|
|
"order_notional_quote": quote_notional,
|
|
"order_notional_quote": quote_notional,
|
|
|
"max_order_notional_quote": max_quote_notional,
|
|
"max_order_notional_quote": max_quote_notional,
|
|
|
|
|
+ "dust_collect": bool(self.config.get("dust_collect", False)),
|
|
|
}
|
|
}
|
|
|
return policy
|
|
return policy
|
|
|
|
|
|
|
@@ -210,29 +214,28 @@ class Strategy(Strategy):
|
|
|
|
|
|
|
|
def _suggest_amount(self, price: float, side: str) -> float:
|
|
def _suggest_amount(self, price: float, side: str) -> float:
|
|
|
min_notional = float(self.context.minimum_order_value or 0.0)
|
|
min_notional = float(self.context.minimum_order_value or 0.0)
|
|
|
- quote_notional = float(self.config.get("order_notional_quote") or 0.0)
|
|
|
|
|
- max_quote_notional = float(self.config.get("max_order_notional_quote") or 0.0)
|
|
|
|
|
- if hasattr(self.context, "suggest_order_amount"):
|
|
|
|
|
- kwargs = {
|
|
|
|
|
- "side": side,
|
|
|
|
|
- "price": price,
|
|
|
|
|
- "levels": 1,
|
|
|
|
|
- "min_notional": min_notional,
|
|
|
|
|
- "fee_rate": self._live_fee_rate(),
|
|
|
|
|
- "quote_notional": quote_notional,
|
|
|
|
|
- "max_notional_per_order": max_quote_notional,
|
|
|
|
|
- }
|
|
|
|
|
- try:
|
|
|
|
|
- return float(self.context.suggest_order_amount(**kwargs) or 0.0)
|
|
|
|
|
- except TypeError:
|
|
|
|
|
- kwargs.pop("quote_notional", None)
|
|
|
|
|
- return float(self.context.suggest_order_amount(**kwargs) or 0.0)
|
|
|
|
|
- if quote_notional <= 0:
|
|
|
|
|
- return 0.0
|
|
|
|
|
- amount = quote_notional / price
|
|
|
|
|
- if max_quote_notional > 0:
|
|
|
|
|
- amount = min(amount, max_quote_notional / price)
|
|
|
|
|
- return max(amount, 0.0)
|
|
|
|
|
|
|
+ fee_rate = self._live_fee_rate()
|
|
|
|
|
+ amount = suggest_quote_sized_amount(
|
|
|
|
|
+ self.context,
|
|
|
|
|
+ side=side,
|
|
|
|
|
+ price=price,
|
|
|
|
|
+ levels=1,
|
|
|
|
|
+ min_notional=min_notional,
|
|
|
|
|
+ fee_rate=fee_rate,
|
|
|
|
|
+ order_notional_quote=float(self.config.get("order_notional_quote") or 0.0),
|
|
|
|
|
+ max_order_notional_quote=float(self.config.get("max_order_notional_quote") or 0.0),
|
|
|
|
|
+ dust_collect=bool(self.config.get("dust_collect", False)),
|
|
|
|
|
+ )
|
|
|
|
|
+ return cap_amount_to_balance_target(
|
|
|
|
|
+ suggested_amount=amount,
|
|
|
|
|
+ side=side,
|
|
|
|
|
+ price=price,
|
|
|
|
|
+ fee_rate=fee_rate,
|
|
|
|
|
+ balance_target=self._balance_target(),
|
|
|
|
|
+ base_available=float(self.state.get("base_available") or 0.0),
|
|
|
|
|
+ counter_available=float(self.state.get("counter_available") or 0.0),
|
|
|
|
|
+ min_notional=min_notional,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
def on_tick(self, tick):
|
|
def on_tick(self, tick):
|
|
|
self.state["last_error"] = ""
|
|
self.state["last_error"] = ""
|
|
@@ -300,6 +303,7 @@ class Strategy(Strategy):
|
|
|
"balance_target": self._balance_target(),
|
|
"balance_target": self._balance_target(),
|
|
|
"order_notional_quote": float(self.config.get("order_notional_quote") or 0.0),
|
|
"order_notional_quote": float(self.config.get("order_notional_quote") or 0.0),
|
|
|
"max_order_notional_quote": float(self.config.get("max_order_notional_quote") or 0.0),
|
|
"max_order_notional_quote": float(self.config.get("max_order_notional_quote") or 0.0),
|
|
|
|
|
+ "dust_collect": bool(self.config.get("dust_collect", False)),
|
|
|
"cooldown_remaining": self.state.get("cooldown_remaining", 0),
|
|
"cooldown_remaining": self.state.get("cooldown_remaining", 0),
|
|
|
"base_available": self.state.get("base_available", 0.0),
|
|
"base_available": self.state.get("base_available", 0.0),
|
|
|
"counter_available": self.state.get("counter_available", 0.0),
|
|
"counter_available": self.state.get("counter_available", 0.0),
|
|
@@ -323,6 +327,7 @@ class Strategy(Strategy):
|
|
|
{"type": "metric", "label": "side", "value": self._trade_side()},
|
|
{"type": "metric", "label": "side", "value": self._trade_side()},
|
|
|
{"type": "metric", "label": "balance target", "value": round(self._balance_target(), 6)},
|
|
{"type": "metric", "label": "balance target", "value": round(self._balance_target(), 6)},
|
|
|
{"type": "metric", "label": "quote notional", "value": round(float(self.config.get("order_notional_quote") or 0.0), 6)},
|
|
{"type": "metric", "label": "quote notional", "value": round(float(self.config.get("order_notional_quote") or 0.0), 6)},
|
|
|
|
|
+ {"type": "metric", "label": "dust collect", "value": bool(self.config.get("dust_collect", False))},
|
|
|
{"type": "metric", "label": "state", "value": self.state.get("last_action", "idle")},
|
|
{"type": "metric", "label": "state", "value": self.state.get("last_action", "idle")},
|
|
|
{"type": "metric", "label": "cooldown", "value": int(self.state.get("cooldown_remaining") or 0)},
|
|
{"type": "metric", "label": "cooldown", "value": int(self.state.get("cooldown_remaining") or 0)},
|
|
|
{"type": "text", "label": "error", "value": self.state.get("last_error", "") or "none"},
|
|
{"type": "text", "label": "error", "value": self.state.get("last_error", "") or "none"},
|