Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -149,6 +149,17 @@ You can ask in natural language, e.g.:
|
|
| 149 |
RE_TA = re.compile(r"\bTA\s+([A-Za-z0-9_\-]+)(?:\s+(\d+[mhHdD]))?(?:\s+lookback\s+(\d+))?", re.IGNORECASE)
|
| 150 |
RE_LONG = re.compile(r"\bLONG\s+([A-Za-z0-9_\-]+)\s+([\d.]+)(?:\s+at\s+(market|mkt|[\d.]+))?(?:.*?\bSL\s+([\d.%]+))?(?:.*?\bTP\s+([\d.%]+))?", re.IGNORECASE)
|
| 151 |
RE_SHORT = re.compile(r"\bSHORT\s+([A-Za-z0-9_\-]+)\s+([\d.]+)(?:\s+at\s+(market|mkt|[\d.]+))?(?:.*?\bSL\s+([\d.%]+))?(?:.*?\bTP\s+([\d.%]+))?", re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
def pct_or_price(s):
|
| 154 |
if not s:
|
|
@@ -352,6 +363,61 @@ async def handle_message(message: str, history: list[tuple[str, str]]):
|
|
| 352 |
+ COMMAND_HELP
|
| 353 |
)
|
| 354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
# ---------- Pretty printing for account/positions ----------
|
| 356 |
|
| 357 |
from math import isnan
|
|
|
|
| 149 |
RE_TA = re.compile(r"\bTA\s+([A-Za-z0-9_\-]+)(?:\s+(\d+[mhHdD]))?(?:\s+lookback\s+(\d+))?", re.IGNORECASE)
|
| 150 |
RE_LONG = re.compile(r"\bLONG\s+([A-Za-z0-9_\-]+)\s+([\d.]+)(?:\s+at\s+(market|mkt|[\d.]+))?(?:.*?\bSL\s+([\d.%]+))?(?:.*?\bTP\s+([\d.%]+))?", re.IGNORECASE)
|
| 151 |
RE_SHORT = re.compile(r"\bSHORT\s+([A-Za-z0-9_\-]+)\s+([\d.]+)(?:\s+at\s+(market|mkt|[\d.]+))?(?:.*?\bSL\s+([\d.%]+))?(?:.*?\bTP\s+([\d.%]+))?", re.IGNORECASE)
|
| 152 |
+
RE_CLOSE = re.compile(r"\b(close|exit|flatten)\s+(all|[A-Za-z0-9_\-]+)(?:\s+(\d+)%|\s+([\d.]+))?", re.IGNORECASE)
|
| 153 |
+
|
| 154 |
+
def _close_desc(coin_or_all: str, pct: str | None, qty: str | None) -> str:
|
| 155 |
+
coin_or_all = coin_or_all.upper()
|
| 156 |
+
if coin_or_all == "ALL":
|
| 157 |
+
return "Close ALL open positions at market"
|
| 158 |
+
if pct:
|
| 159 |
+
return f"Close {pct}% of {coin_or_all} position at market"
|
| 160 |
+
if qty:
|
| 161 |
+
return f"Reduce {coin_or_all} position by {qty} units at market"
|
| 162 |
+
return f"Close {coin_or_all} position at market"
|
| 163 |
|
| 164 |
def pct_or_price(s):
|
| 165 |
if not s:
|
|
|
|
| 363 |
+ COMMAND_HELP
|
| 364 |
)
|
| 365 |
|
| 366 |
+
mclose = RE_CLOSE.search(text)
|
| 367 |
+
if mclose:
|
| 368 |
+
# groups: verb, coin_or_all, pct (digits%), qty (number)
|
| 369 |
+
coin_or_all = mclose.group(2).strip()
|
| 370 |
+
pct = mclose.group(3) # e.g., "50" meaning 50%
|
| 371 |
+
qty = mclose.group(4) # absolute size to reduce
|
| 372 |
+
desc = _close_desc(coin_or_all, pct, qty)
|
| 373 |
+
|
| 374 |
+
hyper_servers = []
|
| 375 |
+
news_servers = []
|
| 376 |
+
ta_servers = []
|
| 377 |
+
try:
|
| 378 |
+
# Tools for trader context (optional but helpful)
|
| 379 |
+
news_servers = make_crypto_news_mcp_servers()
|
| 380 |
+
ta_servers = make_technical_analyst_mcp_servers()
|
| 381 |
+
hyper_servers = make_hyperliquid_trader_mcp_servers()
|
| 382 |
+
|
| 383 |
+
await asyncio.gather(
|
| 384 |
+
connect_all(news_servers),
|
| 385 |
+
connect_all(ta_servers),
|
| 386 |
+
connect_all(hyper_servers),
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
news_tool = await build_news_tool(news_servers)
|
| 390 |
+
ta_tool = await build_ta_tool(ta_servers)
|
| 391 |
+
trader = await build_trader(hyper_servers, [news_tool, ta_tool])
|
| 392 |
+
|
| 393 |
+
# Natural-language prompt to place the close orders via Hyperliquid MCP
|
| 394 |
+
# (The trader agent already has the rules + account context)
|
| 395 |
+
trade_prompt = f"""
|
| 396 |
+
User request: {desc}.
|
| 397 |
+
Instructions:
|
| 398 |
+
- If 'ALL', close every open position at market.
|
| 399 |
+
- If a coin is specified:
|
| 400 |
+
- If a percent is provided, close that % of the CURRENT open position size.
|
| 401 |
+
- If a qty is provided, reduce by that absolute base-asset amount.
|
| 402 |
+
- If neither provided, fully close that coin position.
|
| 403 |
+
- Include SL/TP cleanup if needed (cancel/replace any attached orders).
|
| 404 |
+
- If the coin has no open position, report that clearly.
|
| 405 |
+
- Return a concise execution summary listing each order (coin, side, size, order type, price if applicable) and rationale.
|
| 406 |
+
"""
|
| 407 |
+
with trace("close_positions"):
|
| 408 |
+
result = await Runner.run(trader, trade_prompt, max_turns=20)
|
| 409 |
+
|
| 410 |
+
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M}] Close: {desc}")
|
| 411 |
+
return f"### 🧹 Close — {desc}\n\n{result.final_output}"
|
| 412 |
+
except Exception as e:
|
| 413 |
+
return f"❌ Close error: `{e}`"
|
| 414 |
+
finally:
|
| 415 |
+
await asyncio.gather(
|
| 416 |
+
close_all(news_servers),
|
| 417 |
+
close_all(ta_servers),
|
| 418 |
+
close_all(hyper_servers),
|
| 419 |
+
)
|
| 420 |
+
|
| 421 |
# ---------- Pretty printing for account/positions ----------
|
| 422 |
|
| 423 |
from math import isnan
|