Spaces:
Sleeping
Sleeping
Upload 11 files
Browse files- cryptopanic_news.py +44 -0
- cryptopanic_news_server.py +45 -0
- hl_indicators.py +530 -0
- hl_indicators_server.py +325 -0
- hype_accounts.py +59 -0
- hype_accounts_server.py +56 -0
- memories.json +11 -0
- memory_utils.py +39 -0
- production.ipynb +727 -0
- requirements.txt +9 -0
- test.ipynb +1522 -0
cryptopanic_news.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
|
| 3 |
+
def get_posts(
|
| 4 |
+
auth_token: str,
|
| 5 |
+
kind: str = None, # e.g. "news", "blogs", "videos"
|
| 6 |
+
currencies: list = None, # e.g. ["BTC", "ETH"]
|
| 7 |
+
regions: list = None, # e.g. ["en"] or country codes
|
| 8 |
+
public: bool = True, # whether to include public posts
|
| 9 |
+
filter_duplicates: bool = True,
|
| 10 |
+
page: int = 1,
|
| 11 |
+
filter: str = None # a general filter string
|
| 12 |
+
) -> dict:
|
| 13 |
+
"""
|
| 14 |
+
Fetch posts from CryptoPanic.
|
| 15 |
+
"""
|
| 16 |
+
API_BASE = "https://cryptopanic.com/api/developer/v2"
|
| 17 |
+
#url = f"{API_BASE}/posts/"
|
| 18 |
+
url = "https://cryptopanic.com/api/developer/v2/posts/"
|
| 19 |
+
headers = {
|
| 20 |
+
"Accept": "application/json"
|
| 21 |
+
}
|
| 22 |
+
params = {
|
| 23 |
+
"auth_token": auth_token,
|
| 24 |
+
"public": "true" if public else "false",
|
| 25 |
+
"page": page,
|
| 26 |
+
}
|
| 27 |
+
if kind:
|
| 28 |
+
params["kind"] = kind
|
| 29 |
+
if currencies:
|
| 30 |
+
print(currencies)
|
| 31 |
+
# CryptoPanic expects multiple items comma-separated or repeated parameters
|
| 32 |
+
# Checking docs: uses comma-separated list.
|
| 33 |
+
params["currencies"] = ",".join(currencies)
|
| 34 |
+
if regions:
|
| 35 |
+
params["regions"] = ",".join(regions)
|
| 36 |
+
if filter_duplicates:
|
| 37 |
+
params["filter_duplicates"] = "true"
|
| 38 |
+
if filter:
|
| 39 |
+
params["filter"] = filter
|
| 40 |
+
|
| 41 |
+
response = requests.get(url, params=params, headers=headers)
|
| 42 |
+
response.raise_for_status()
|
| 43 |
+
print(url)
|
| 44 |
+
return response.json()
|
cryptopanic_news_server.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from mcp.server.fastmcp import FastMCP
|
| 2 |
+
from cryptopanic_news import get_posts
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
mcp = FastMCP("cryptopanic_news_server")
|
| 6 |
+
|
| 7 |
+
@mcp.tool()
|
| 8 |
+
async def get_crypto_news(currencies_ls: list | str | None = None) -> str:
|
| 9 |
+
"""Fetch latest cryptocurrency news from CryptoPanic.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
currencies_ls: The list of cryptocurrency symbols to filter news by. Accepts a single string (e.g. "BTC") or a list of symbols (e.g. ["HYPE", "ETC", "BTC", "XRP"]).
|
| 13 |
+
|
| 14 |
+
If set to None, retrieves the general CryptoPanic news feed
|
| 15 |
+
without filtering by any specific currency.
|
| 16 |
+
"""
|
| 17 |
+
token = os.getenv("CRYPTOPANIC_API_KEY")
|
| 18 |
+
if not token:
|
| 19 |
+
return "Error: CRYPTOPANIC_API_KEY not set."
|
| 20 |
+
|
| 21 |
+
# Allow a single string or a list
|
| 22 |
+
if isinstance(currencies_ls, str):
|
| 23 |
+
currencies_ls = [currencies_ls]
|
| 24 |
+
|
| 25 |
+
data = get_posts(
|
| 26 |
+
auth_token=token,
|
| 27 |
+
kind="news",
|
| 28 |
+
currencies=currencies_ls,
|
| 29 |
+
page=1
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
if not isinstance(data, dict):
|
| 33 |
+
return f"Error: Unexpected API response: {data!r}"
|
| 34 |
+
|
| 35 |
+
results = []
|
| 36 |
+
for post in data.get("results", []):
|
| 37 |
+
title = post.get("title") or "No title"
|
| 38 |
+
desc = post.get("description") or ""
|
| 39 |
+
pub = post.get("published_at") or "Unknown time"
|
| 40 |
+
results.append(f"title: {title}\n{desc[:200]}\npublished at: {pub}\n")
|
| 41 |
+
|
| 42 |
+
return "\n".join(results) if results else "No recent crypto news found."
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
mcp.run(transport='stdio')
|
hl_indicators.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# hl_indicators.py
|
| 2 |
+
# pip install: hyperliquid-python-sdk pandas numpy
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
from typing import Dict, Any, List, Tuple, Literal, Iterable
|
| 6 |
+
import time
|
| 7 |
+
import numpy as np
|
| 8 |
+
import pandas as pd
|
| 9 |
+
|
| 10 |
+
from hyperliquid.info import Info
|
| 11 |
+
from hyperliquid.utils import constants
|
| 12 |
+
|
| 13 |
+
Interval = Literal["1m", "5m", "15m", "1h", "4h", "1d"]
|
| 14 |
+
|
| 15 |
+
_MS = {"1m": 60_000, "5m": 5*60_000, "15m": 15*60_000, "1h": 60*60_000, "4h": 4*60*60_000, "1d": 24*60*60_000}
|
| 16 |
+
|
| 17 |
+
def _now_ms() -> int:
|
| 18 |
+
return int(time.time() * 1000)
|
| 19 |
+
|
| 20 |
+
def _start_end_from_limit(interval: Interval, limit: int, end_ms: int | None = None) -> tuple[int, int]:
|
| 21 |
+
end_ms = end_ms or _now_ms()
|
| 22 |
+
span = (limit + 2) * _MS[interval] # small buffer for smoothing windows
|
| 23 |
+
start_ms = max(0, end_ms - span)
|
| 24 |
+
return start_ms, end_ms
|
| 25 |
+
|
| 26 |
+
# ---------------- Data fetch via candles_snapshot ---------------- #
|
| 27 |
+
|
| 28 |
+
def fetch_candles(
|
| 29 |
+
name: str,
|
| 30 |
+
interval: Interval = "1h",
|
| 31 |
+
limit: int = 600,
|
| 32 |
+
testnet: bool = True,
|
| 33 |
+
end_ms: int | None = None,
|
| 34 |
+
) -> pd.DataFrame:
|
| 35 |
+
"""
|
| 36 |
+
Fetch OHLCV candles via Info.candles_snapshot(name, interval, startTime, endTime).
|
| 37 |
+
Returns DataFrame with ['timestamp','open','high','low','close','volume'] sorted by time.
|
| 38 |
+
"""
|
| 39 |
+
api_url = constants.TESTNET_API_URL if testnet else constants.MAINNET_API_URL
|
| 40 |
+
info = Info(api_url, skip_ws=True)
|
| 41 |
+
|
| 42 |
+
start_ms, end_ms = _start_end_from_limit(interval, limit, end_ms)
|
| 43 |
+
raw = info.candles_snapshot(name, interval, start_ms, end_ms)
|
| 44 |
+
if not raw:
|
| 45 |
+
raise ValueError(f"No candles returned for {name} {interval}")
|
| 46 |
+
|
| 47 |
+
df = pd.DataFrame(raw).rename(columns={
|
| 48 |
+
"t": "timestamp", "o": "open", "h": "high", "l": "low", "c": "close", "v": "volume",
|
| 49 |
+
"T": "close_time", "i": "interval", "s": "symbol", "n": "trades",
|
| 50 |
+
})
|
| 51 |
+
|
| 52 |
+
needed = ["timestamp", "open", "high", "low", "close", "volume"]
|
| 53 |
+
for k in needed:
|
| 54 |
+
if k not in df.columns:
|
| 55 |
+
raise ValueError(f"Missing '{k}' in candles_snapshot payload. Got: {list(df.columns)}")
|
| 56 |
+
|
| 57 |
+
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms", errors="coerce")
|
| 58 |
+
for k in ["open","high","low","close","volume"]:
|
| 59 |
+
df[k] = pd.to_numeric(df[k], errors="coerce")
|
| 60 |
+
|
| 61 |
+
df = df.dropna(subset=["timestamp","close"]).sort_values("timestamp").reset_index(drop=True)
|
| 62 |
+
if len(df) > limit:
|
| 63 |
+
df = df.iloc[-limit:].reset_index(drop=True)
|
| 64 |
+
return df
|
| 65 |
+
|
| 66 |
+
# ---------------- Base indicators ---------------- #
|
| 67 |
+
|
| 68 |
+
def ema(series: pd.Series, period: int) -> pd.Series:
|
| 69 |
+
return series.ewm(span=period, adjust=False).mean()
|
| 70 |
+
|
| 71 |
+
def macd(series: pd.Series, fast: int = 12, slow: int = 26, signal: int = 9) -> Tuple[pd.Series, pd.Series, pd.Series]:
|
| 72 |
+
fast_ema, slow_ema = ema(series, fast), ema(series, slow)
|
| 73 |
+
line = fast_ema - slow_ema
|
| 74 |
+
sig = ema(line, signal)
|
| 75 |
+
hist = line - sig
|
| 76 |
+
return line, sig, hist
|
| 77 |
+
|
| 78 |
+
def rsi(series: pd.Series, period: int = 14) -> pd.Series:
|
| 79 |
+
delta = series.diff()
|
| 80 |
+
up = pd.Series(np.where(delta > 0, delta, 0.0), index=series.index)
|
| 81 |
+
down = pd.Series(np.where(delta < 0, -delta, 0.0), index=series.index)
|
| 82 |
+
avg_up = up.ewm(alpha=1/period, adjust=False).mean()
|
| 83 |
+
avg_down = down.ewm(alpha=1/period, adjust=False).mean()
|
| 84 |
+
rs = avg_up / avg_down.replace(0, np.nan)
|
| 85 |
+
return (100 - (100 / (1 + rs))).fillna(0)
|
| 86 |
+
|
| 87 |
+
def stoch_rsi(series: pd.Series, rsi_length: int = 14, stoch_length: int = 14, k_smooth: int = 3, d_smooth: int = 3
|
| 88 |
+
) -> Tuple[pd.Series, pd.Series, pd.Series]:
|
| 89 |
+
r = rsi(series, rsi_length)
|
| 90 |
+
r_low, r_high = r.rolling(stoch_length).min(), r.rolling(stoch_length).max()
|
| 91 |
+
base = (r - r_low) / (r_high - r_low)
|
| 92 |
+
k = base.rolling(k_smooth).mean() * 100.0
|
| 93 |
+
d = k.rolling(d_smooth).mean()
|
| 94 |
+
return base * 100.0, k, d
|
| 95 |
+
|
| 96 |
+
# ---------------- Volume/volatility family ---------------- #
|
| 97 |
+
|
| 98 |
+
def adl(high: pd.Series, low: pd.Series, close: pd.Series, volume: pd.Series) -> pd.Series:
|
| 99 |
+
"""
|
| 100 |
+
Chaikin Accumulation/Distribution Line.
|
| 101 |
+
mfm = ((close - low) - (high - close)) / (high - low), guarded for zero range.
|
| 102 |
+
ADL = cumulative sum(mfm * volume)
|
| 103 |
+
"""
|
| 104 |
+
hl_range = (high - low).replace(0, np.nan)
|
| 105 |
+
mfm = ((close - low) - (high - close)) / hl_range
|
| 106 |
+
mfm = mfm.fillna(0.0)
|
| 107 |
+
mfv = mfm * volume
|
| 108 |
+
return mfv.cumsum()
|
| 109 |
+
|
| 110 |
+
def obv(close: pd.Series, volume: pd.Series) -> pd.Series:
|
| 111 |
+
"""
|
| 112 |
+
On-Balance Volume.
|
| 113 |
+
"""
|
| 114 |
+
sign = np.sign(close.diff()).fillna(0)
|
| 115 |
+
return (volume * sign).cumsum()
|
| 116 |
+
|
| 117 |
+
def true_range(high: pd.Series, low: pd.Series, close: pd.Series) -> pd.Series:
|
| 118 |
+
prev_close = close.shift(1)
|
| 119 |
+
tr = pd.concat([(high - low).abs(), (high - prev_close).abs(), (low - prev_close).abs()], axis=1).max(axis=1)
|
| 120 |
+
return tr
|
| 121 |
+
|
| 122 |
+
def atr(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14) -> pd.Series:
|
| 123 |
+
tr = true_range(high, low, close)
|
| 124 |
+
return tr.ewm(alpha=1/period, adjust=False).mean()
|
| 125 |
+
|
| 126 |
+
def di_adx(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14
|
| 127 |
+
) -> Tuple[pd.Series, pd.Series, pd.Series]:
|
| 128 |
+
up_move = high.diff()
|
| 129 |
+
down_move = -low.diff()
|
| 130 |
+
plus_dm = pd.Series(np.where((up_move > down_move) & (up_move > 0), up_move, 0.0), index=high.index)
|
| 131 |
+
minus_dm = pd.Series(np.where((down_move > up_move) & (down_move > 0), down_move, 0.0), index=high.index)
|
| 132 |
+
atr_series = atr(high, low, close, period)
|
| 133 |
+
|
| 134 |
+
plus_di = 100 * (plus_dm.ewm(alpha=1/period, adjust=False).mean() / atr_series.replace(0, np.nan))
|
| 135 |
+
minus_di = 100 * (minus_dm.ewm(alpha=1/period, adjust=False).mean() / atr_series.replace(0, np.nan))
|
| 136 |
+
dx = (100 * (plus_di - minus_di).abs() / (plus_di + minus_di).replace(0, np.nan)).fillna(0)
|
| 137 |
+
adx = dx.ewm(alpha=1/period, adjust=False).mean()
|
| 138 |
+
return plus_di.fillna(0), minus_di.fillna(0), adx.fillna(0)
|
| 139 |
+
|
| 140 |
+
def bbands(series: pd.Series, period: int = 20, std_mult: float = 2.0
|
| 141 |
+
) -> Tuple[pd.Series, pd.Series, pd.Series, pd.Series, pd.Series]:
|
| 142 |
+
ma = series.rolling(period).mean()
|
| 143 |
+
sd = series.rolling(period).std(ddof=0)
|
| 144 |
+
upper = ma + std_mult * sd
|
| 145 |
+
lower = ma - std_mult * sd
|
| 146 |
+
pct_b = (series - lower) / (upper - lower)
|
| 147 |
+
bandwidth = (upper - lower) / ma
|
| 148 |
+
return ma, upper, lower, pct_b, bandwidth
|
| 149 |
+
|
| 150 |
+
def mfi(high: pd.Series, low: pd.Series, close: pd.Series, volume: pd.Series, period: int = 14) -> pd.Series:
|
| 151 |
+
tp = (high + low + close) / 3.0
|
| 152 |
+
rmf = tp * volume
|
| 153 |
+
pos_flow = pd.Series(np.where(tp > tp.shift(1), rmf, 0.0), index=tp.index).rolling(period).sum()
|
| 154 |
+
neg_flow = pd.Series(np.where(tp < tp.shift(1), rmf, 0.0), index=tp.index).rolling(period).sum()
|
| 155 |
+
money_ratio = pos_flow / neg_flow.replace(0, np.nan)
|
| 156 |
+
out = 100 - (100 / (1 + money_ratio))
|
| 157 |
+
return out.fillna(0)
|
| 158 |
+
|
| 159 |
+
def vwap_cumulative(high: pd.Series, low: pd.Series, close: pd.Series, volume: pd.Series) -> pd.Series:
|
| 160 |
+
"""
|
| 161 |
+
Cumulative VWAP over the full series: sum(TP*V)/sum(V) where TP=(H+L+C)/3.
|
| 162 |
+
Resets only at the beginning (not each day).
|
| 163 |
+
"""
|
| 164 |
+
tp = (high + low + close) / 3.0
|
| 165 |
+
cum_v = volume.cumsum().replace(0, np.nan)
|
| 166 |
+
cum_tp_v = (tp * volume).cumsum()
|
| 167 |
+
return (cum_tp_v / cum_v).fillna(method="bfill").fillna(0)
|
| 168 |
+
|
| 169 |
+
def vwap_daily(high: pd.Series, low: pd.Series, close: pd.Series, volume: pd.Series, timestamps: pd.Series) -> pd.Series:
|
| 170 |
+
"""
|
| 171 |
+
Session VWAP that resets daily (by calendar date of 'timestamps').
|
| 172 |
+
"""
|
| 173 |
+
tp = (high + low + close) / 3.0
|
| 174 |
+
dates = pd.to_datetime(timestamps).dt.date
|
| 175 |
+
df = pd.DataFrame({"tp": tp, "v": volume, "date": dates})
|
| 176 |
+
df["tpv"] = df["tp"] * df["v"]
|
| 177 |
+
cum = df.groupby("date")[["tpv", "v"]].cumsum()
|
| 178 |
+
vwap = (cum["tpv"] / cum["v"].replace(0, np.nan)).values
|
| 179 |
+
return pd.Series(vwap, index=high.index).fillna(method="bfill").fillna(0)
|
| 180 |
+
|
| 181 |
+
# ---------------- JSON helpers ---------------- #
|
| 182 |
+
|
| 183 |
+
def _pts(ts: pd.Series, vals: pd.Series) -> List[Dict[str, float]]:
|
| 184 |
+
out: List[Dict[str, float]] = []
|
| 185 |
+
for t, v in zip(ts, vals):
|
| 186 |
+
if pd.isna(t) or pd.isna(v):
|
| 187 |
+
continue
|
| 188 |
+
out.append({"t": int(pd.Timestamp(t).timestamp() * 1000), "v": float(v)})
|
| 189 |
+
return out
|
| 190 |
+
|
| 191 |
+
def _tail_pts(ts: pd.Series, vals: pd.Series, n: int) -> List[Dict[str, float]]:
|
| 192 |
+
"""Return only the last n timestamp/value points (safe if n > len)."""
|
| 193 |
+
if n is None or n <= 0:
|
| 194 |
+
return _pts(ts, vals)
|
| 195 |
+
tail_ts = ts.iloc[-n:] if len(ts) > n else ts
|
| 196 |
+
tail_vals = vals.iloc[-n:] if len(vals) > n else vals
|
| 197 |
+
return _pts(tail_ts, tail_vals)
|
| 198 |
+
|
| 199 |
+
# ---------------- MCP-friendly functions (per indicator) ---------------- #
|
| 200 |
+
|
| 201 |
+
def get_ema(
|
| 202 |
+
name: str,
|
| 203 |
+
periods: List[int] | None = None,
|
| 204 |
+
interval: Interval = "1h",
|
| 205 |
+
limit: int = 300,
|
| 206 |
+
testnet: bool = False,
|
| 207 |
+
output_tail: int = 30, # NEW
|
| 208 |
+
) -> Dict[str, Any]:
|
| 209 |
+
periods = periods or [20, 200]
|
| 210 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 211 |
+
res: Dict[str, Any] = {
|
| 212 |
+
"coin": name,
|
| 213 |
+
"interval": interval,
|
| 214 |
+
"ema": {},
|
| 215 |
+
"close": _tail_pts(df["timestamp"], df["close"], output_tail), # sliced
|
| 216 |
+
"last": {"close": float(df["close"].iloc[-1])},
|
| 217 |
+
}
|
| 218 |
+
for p in periods:
|
| 219 |
+
e = ema(df["close"], p)
|
| 220 |
+
res["ema"][str(p)] = _tail_pts(df["timestamp"], e, output_tail) # sliced
|
| 221 |
+
res["last"][f"ema_{p}"] = float(e.iloc[-1])
|
| 222 |
+
return res
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def get_macd(
|
| 226 |
+
name: str,
|
| 227 |
+
fast: int = 12,
|
| 228 |
+
slow: int = 26,
|
| 229 |
+
signal: int = 9,
|
| 230 |
+
interval: Interval = "1h",
|
| 231 |
+
limit: int = 300,
|
| 232 |
+
testnet: bool = False,
|
| 233 |
+
output_tail: int = 30, # NEW
|
| 234 |
+
) -> Dict[str, Any]:
|
| 235 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 236 |
+
line, sig, hist = macd(df["close"], fast, slow, signal)
|
| 237 |
+
return {
|
| 238 |
+
"coin": name,
|
| 239 |
+
"interval": interval,
|
| 240 |
+
"params": {"fast": fast, "slow": slow, "signal": signal},
|
| 241 |
+
"macd_line": _tail_pts(df["timestamp"], line, output_tail), # sliced
|
| 242 |
+
"signal": _tail_pts(df["timestamp"], sig, output_tail), # sliced
|
| 243 |
+
"histogram": _tail_pts(df["timestamp"], hist, output_tail), # sliced
|
| 244 |
+
"last": {
|
| 245 |
+
"macd_line": float(line.iloc[-1]),
|
| 246 |
+
"signal": float(sig.iloc[-1]),
|
| 247 |
+
"histogram": float(hist.iloc[-1]),
|
| 248 |
+
"close": float(df["close"].iloc[-1]),
|
| 249 |
+
},
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def get_stoch_rsi(
|
| 254 |
+
name: str,
|
| 255 |
+
rsi_length: int = 14,
|
| 256 |
+
stoch_length: int = 14,
|
| 257 |
+
k_smooth: int = 3,
|
| 258 |
+
d_smooth: int = 3,
|
| 259 |
+
interval: Interval = "1h",
|
| 260 |
+
limit: int = 300,
|
| 261 |
+
testnet: bool = False,
|
| 262 |
+
output_tail: int = 30, # NEW
|
| 263 |
+
) -> Dict[str, Any]:
|
| 264 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 265 |
+
stoch, k, d = stoch_rsi(df["close"], rsi_length, stoch_length, k_smooth, d_smooth)
|
| 266 |
+
return {
|
| 267 |
+
"coin": name,
|
| 268 |
+
"interval": interval,
|
| 269 |
+
"params": {
|
| 270 |
+
"rsi_length": rsi_length,
|
| 271 |
+
"stoch_length": stoch_length,
|
| 272 |
+
"k_smooth": k_smooth,
|
| 273 |
+
"d_smooth": d_smooth,
|
| 274 |
+
},
|
| 275 |
+
"stoch_rsi": _tail_pts(df["timestamp"], stoch, output_tail), # sliced
|
| 276 |
+
"%K": _tail_pts(df["timestamp"], k, output_tail), # sliced
|
| 277 |
+
"%D": _tail_pts(df["timestamp"], d, output_tail), # sliced
|
| 278 |
+
"last": {
|
| 279 |
+
"stoch_rsi": float(stoch.iloc[-1]),
|
| 280 |
+
"k": float(k.iloc[-1]),
|
| 281 |
+
"d": float(d.iloc[-1]),
|
| 282 |
+
"close": float(df["close"].iloc[-1]),
|
| 283 |
+
},
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def get_adl(
|
| 288 |
+
name: str,
|
| 289 |
+
interval: Interval = "1h",
|
| 290 |
+
limit: int = 300,
|
| 291 |
+
testnet: bool = False,
|
| 292 |
+
output_tail: int = 30, # NEW
|
| 293 |
+
) -> Dict[str, Any]:
|
| 294 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 295 |
+
series = adl(df["high"], df["low"], df["close"], df["volume"])
|
| 296 |
+
return {
|
| 297 |
+
"coin": name,
|
| 298 |
+
"interval": interval,
|
| 299 |
+
"adl": _tail_pts(df["timestamp"], series, output_tail), # sliced
|
| 300 |
+
"last": {"adl": float(series.iloc[-1])},
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def get_obv(
|
| 305 |
+
name: str,
|
| 306 |
+
interval: Interval = "1h",
|
| 307 |
+
limit: int = 300,
|
| 308 |
+
testnet: bool = False,
|
| 309 |
+
output_tail: int = 30, # NEW
|
| 310 |
+
) -> Dict[str, Any]:
|
| 311 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 312 |
+
series = obv(df["close"], df["volume"])
|
| 313 |
+
return {
|
| 314 |
+
"coin": name,
|
| 315 |
+
"interval": interval,
|
| 316 |
+
"obv": _tail_pts(df["timestamp"], series, output_tail), # sliced
|
| 317 |
+
"last": {"obv": float(series.iloc[-1])},
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def get_atr_adx(
|
| 322 |
+
name: str,
|
| 323 |
+
period: int = 14,
|
| 324 |
+
interval: Interval = "1h",
|
| 325 |
+
limit: int = 300,
|
| 326 |
+
testnet: bool = False,
|
| 327 |
+
output_tail: int = 30, # NEW
|
| 328 |
+
) -> Dict[str, Any]:
|
| 329 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 330 |
+
plus_di, minus_di, adx_series = di_adx(df["high"], df["low"], df["close"], period)
|
| 331 |
+
atr_series = atr(df["high"], df["low"], df["close"], period)
|
| 332 |
+
return {
|
| 333 |
+
"coin": name,
|
| 334 |
+
"interval": interval,
|
| 335 |
+
"params": {"period": period},
|
| 336 |
+
"+DI": _tail_pts(df["timestamp"], plus_di, output_tail), # sliced
|
| 337 |
+
"-DI": _tail_pts(df["timestamp"], minus_di, output_tail), # sliced
|
| 338 |
+
"ADX": _tail_pts(df["timestamp"], adx_series, output_tail),# sliced
|
| 339 |
+
"ATR": _tail_pts(df["timestamp"], atr_series, output_tail),# sliced
|
| 340 |
+
"last": {
|
| 341 |
+
"+DI": float(plus_di.iloc[-1]),
|
| 342 |
+
"-DI": float(minus_di.iloc[-1]),
|
| 343 |
+
"ADX": float(adx_series.iloc[-1]),
|
| 344 |
+
"ATR": float(atr_series.iloc[-1]),
|
| 345 |
+
},
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def get_bbands(
|
| 350 |
+
name: str,
|
| 351 |
+
period: int = 20,
|
| 352 |
+
std_mult: float = 2.0,
|
| 353 |
+
interval: Interval = "1h",
|
| 354 |
+
limit: int = 300,
|
| 355 |
+
testnet: bool = False,
|
| 356 |
+
output_tail: int = 30, # NEW
|
| 357 |
+
) -> Dict[str, Any]:
|
| 358 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 359 |
+
ma, upper, lower, pct_b, bandwidth = bbands(df["close"], period, std_mult)
|
| 360 |
+
return {
|
| 361 |
+
"coin": name,
|
| 362 |
+
"interval": interval,
|
| 363 |
+
"params": {"period": period, "std_mult": std_mult},
|
| 364 |
+
"basis": _tail_pts(df["timestamp"], ma, output_tail), # sliced
|
| 365 |
+
"upper": _tail_pts(df["timestamp"], upper, output_tail), # sliced
|
| 366 |
+
"lower": _tail_pts(df["timestamp"], lower, output_tail), # sliced
|
| 367 |
+
"%b": _tail_pts(df["timestamp"], pct_b, output_tail), # sliced
|
| 368 |
+
"bandwidth": _tail_pts(df["timestamp"], bandwidth, output_tail), # sliced
|
| 369 |
+
"last": {
|
| 370 |
+
"basis": float(ma.iloc[-1]),
|
| 371 |
+
"upper": float(upper.iloc[-1]),
|
| 372 |
+
"lower": float(lower.iloc[-1]),
|
| 373 |
+
"%b": float(pct_b.iloc[-1]),
|
| 374 |
+
"bandwidth": float(bandwidth.iloc[-1]),
|
| 375 |
+
},
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def get_mfi(
|
| 380 |
+
name: str,
|
| 381 |
+
period: int = 14,
|
| 382 |
+
interval: Interval = "1h",
|
| 383 |
+
limit: int = 300,
|
| 384 |
+
testnet: bool = False,
|
| 385 |
+
output_tail: int = 30, # NEW
|
| 386 |
+
) -> Dict[str, Any]:
|
| 387 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 388 |
+
series = mfi(df["high"], df["low"], df["close"], df["volume"], period)
|
| 389 |
+
return {
|
| 390 |
+
"coin": name,
|
| 391 |
+
"interval": interval,
|
| 392 |
+
"params": {"period": period},
|
| 393 |
+
"mfi": _tail_pts(df["timestamp"], series, output_tail), # sliced
|
| 394 |
+
"last": {"mfi": float(series.iloc[-1])},
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
def get_vwap(
|
| 399 |
+
name: str,
|
| 400 |
+
daily_reset: bool = False,
|
| 401 |
+
interval: Interval = "1h",
|
| 402 |
+
limit: int = 300,
|
| 403 |
+
testnet: bool = False,
|
| 404 |
+
output_tail: int = 30, # NEW
|
| 405 |
+
) -> Dict[str, Any]:
|
| 406 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 407 |
+
series = (
|
| 408 |
+
vwap_daily(df["high"], df["low"], df["close"], df["volume"], df["timestamp"])
|
| 409 |
+
if daily_reset else
|
| 410 |
+
vwap_cumulative(df["high"], df["low"], df["close"], df["volume"])
|
| 411 |
+
)
|
| 412 |
+
return {
|
| 413 |
+
"coin": name,
|
| 414 |
+
"interval": interval,
|
| 415 |
+
"params": {"daily_reset": bool(daily_reset)},
|
| 416 |
+
"vwap": _tail_pts(df["timestamp"], series, output_tail), # sliced
|
| 417 |
+
"last": {"vwap": float(series.iloc[-1])},
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def get_volume(
|
| 422 |
+
name: str,
|
| 423 |
+
interval: Interval = "1h",
|
| 424 |
+
limit: int = 300,
|
| 425 |
+
testnet: bool = False,
|
| 426 |
+
output_tail: int = 30, # NEW
|
| 427 |
+
) -> Dict[str, Any]:
|
| 428 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 429 |
+
return {
|
| 430 |
+
"coin": name,
|
| 431 |
+
"interval": interval,
|
| 432 |
+
"volume": _tail_pts(df["timestamp"], df["volume"], output_tail), # sliced
|
| 433 |
+
"last": {"volume": float(df["volume"].iloc[-1])},
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
def get_bundle(
|
| 438 |
+
name: str,
|
| 439 |
+
interval: Interval = "1h",
|
| 440 |
+
limit: int = 300,
|
| 441 |
+
testnet: bool = False,
|
| 442 |
+
include: Iterable[str] = ("ema","macd","stoch_rsi","adl","obv","atr_adx","bbands","mfi","vwap","volume"),
|
| 443 |
+
ema_periods: List[int] | None = None,
|
| 444 |
+
macd_fast: int = 12, macd_slow: int = 26, macd_signal: int = 9,
|
| 445 |
+
stoch_rsi_len: int = 14, stoch_len: int = 14, k_smooth: int = 3, d_smooth: int = 3,
|
| 446 |
+
bb_period: int = 20, bb_std: float = 2.0,
|
| 447 |
+
mfi_period: int = 14,
|
| 448 |
+
vwap_daily_reset: bool = False,
|
| 449 |
+
output_tail: int = 30, # NEW
|
| 450 |
+
) -> Dict[str, Any]:
|
| 451 |
+
df = fetch_candles(name, interval, limit, testnet)
|
| 452 |
+
out: Dict[str, Any] = {
|
| 453 |
+
"coin": name,
|
| 454 |
+
"interval": interval,
|
| 455 |
+
"close": _tail_pts(df["timestamp"], df["close"], output_tail), # sliced
|
| 456 |
+
"last": {"close": float(df["close"].iloc[-1])},
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
if "ema" in include:
|
| 460 |
+
ema_periods = ema_periods or [20, 200]
|
| 461 |
+
out["ema"] = {}
|
| 462 |
+
for p in ema_periods:
|
| 463 |
+
e = ema(df["close"], p)
|
| 464 |
+
out["ema"][str(p)] = _tail_pts(df["timestamp"], e, output_tail) # sliced
|
| 465 |
+
out["last"][f"ema_{p}"] = float(e.iloc[-1])
|
| 466 |
+
|
| 467 |
+
if "macd" in include:
|
| 468 |
+
line, sig, hist = macd(df["close"], macd_fast, macd_slow, macd_signal)
|
| 469 |
+
out["macd"] = {
|
| 470 |
+
"params": {"fast": macd_fast, "slow": macd_slow, "signal": macd_signal},
|
| 471 |
+
"macd_line": _tail_pts(df["timestamp"], line, output_tail), # sliced
|
| 472 |
+
"signal": _tail_pts(df["timestamp"], sig, output_tail), # sliced
|
| 473 |
+
"histogram": _tail_pts(df["timestamp"], hist, output_tail), # sliced
|
| 474 |
+
"last": {"macd_line": float(line.iloc[-1]), "signal": float(sig.iloc[-1]), "histogram": float(hist.iloc[-1])},
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
if "stoch_rsi" in include:
|
| 478 |
+
st, k, d = stoch_rsi(df["close"], stoch_rsi_len, stoch_len, k_smooth, d_smooth)
|
| 479 |
+
out["stoch_rsi"] = {
|
| 480 |
+
"params": {"rsi_length": stoch_rsi_len, "stoch_length": stoch_len, "k_smooth": k_smooth, "d_smooth": d_smooth},
|
| 481 |
+
"stoch_rsi": _tail_pts(df["timestamp"], st, output_tail), # sliced
|
| 482 |
+
"%K": _tail_pts(df["timestamp"], k, output_tail), # sliced
|
| 483 |
+
"%D": _tail_pts(df["timestamp"], d, output_tail), # sliced
|
| 484 |
+
"last": {"stoch_rsi": float(st.iloc[-1]), "k": float(k.iloc[-1]), "d": float(d.iloc[-1])},
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
if "adl" in include:
|
| 488 |
+
series = adl(df["high"], df["low"], df["close"], df["volume"])
|
| 489 |
+
out["adl"] = {"series": _tail_pts(df["timestamp"], series, output_tail), "last": float(series.iloc[-1])}
|
| 490 |
+
|
| 491 |
+
if "obv" in include:
|
| 492 |
+
series = obv(df["close"], df["volume"])
|
| 493 |
+
out["obv"] = {"series": _tail_pts(df["timestamp"], series, output_tail), "last": float(series.iloc[-1])}
|
| 494 |
+
|
| 495 |
+
if "atr_adx" in include:
|
| 496 |
+
plus_di, minus_di, adx_series = di_adx(df["high"], df["low"], df["close"])
|
| 497 |
+
atr_series = atr(df["high"], df["low"], df["close"])
|
| 498 |
+
out["atr_adx"] = {
|
| 499 |
+
"+DI": _tail_pts(df["timestamp"], plus_di, output_tail), # sliced
|
| 500 |
+
"-DI": _tail_pts(df["timestamp"], minus_di, output_tail), # sliced
|
| 501 |
+
"ADX": _tail_pts(df["timestamp"], adx_series, output_tail), # sliced
|
| 502 |
+
"ATR": _tail_pts(df["timestamp"], atr_series, output_tail), # sliced
|
| 503 |
+
"last": {"+DI": float(plus_di.iloc[-1]), "-DI": float(minus_di.iloc[-1]), "ADX": float(adx_series.iloc[-1]), "ATR": float(atr_series.iloc[-1])},
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
if "bbands" in include:
|
| 507 |
+
ma, up, lo, pct_b, bw = bbands(df["close"], bb_period, bb_std)
|
| 508 |
+
out["bbands"] = {
|
| 509 |
+
"params": {"period": bb_period, "std_mult": bb_std},
|
| 510 |
+
"basis": _tail_pts(df["timestamp"], ma, output_tail), # sliced
|
| 511 |
+
"upper": _tail_pts(df["timestamp"], up, output_tail), # sliced
|
| 512 |
+
"lower": _tail_pts(df["timestamp"], lo, output_tail), # sliced
|
| 513 |
+
"%b": _tail_pts(df["timestamp"], pct_b, output_tail),# sliced
|
| 514 |
+
"bandwidth": _tail_pts(df["timestamp"], bw, output_tail), # sliced
|
| 515 |
+
"last": {"basis": float(ma.iloc[-1]), "upper": float(up.iloc[-1]), "lower": float(lo.iloc[-1]), "%b": float(pct_b.iloc[-1]), "bandwidth": float(bw.iloc[-1])},
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
if "mfi" in include:
|
| 519 |
+
series = mfi(df["high"], df["low"], df["close"], df["volume"], mfi_period)
|
| 520 |
+
out["mfi"] = {"params": {"period": mfi_period}, "series": _tail_pts(df["timestamp"], series, output_tail), "last": float(series.iloc[-1])}
|
| 521 |
+
|
| 522 |
+
if "vwap" in include:
|
| 523 |
+
series = vwap_daily(df["high"], df["low"], df["close"], df["volume"], df["timestamp"]) if vwap_daily_reset else \
|
| 524 |
+
vwap_cumulative(df["high"], df["low"], df["close"], df["volume"])
|
| 525 |
+
out["vwap"] = {"params": {"daily_reset": bool(vwap_daily_reset)}, "series": _tail_pts(df["timestamp"], series, output_tail), "last": float(series.iloc[-1])}
|
| 526 |
+
|
| 527 |
+
if "volume" in include:
|
| 528 |
+
out["volume"] = {"series": _tail_pts(df["timestamp"], df["volume"], output_tail), "last": float(df["volume"].iloc[-1])}
|
| 529 |
+
|
| 530 |
+
return out
|
hl_indicators_server.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# hl_indicators_server.py
|
| 2 |
+
"""
|
| 3 |
+
FastMCP server exposing Hyperliquid indicator tools.
|
| 4 |
+
|
| 5 |
+
This server provides a unified interface to compute common trading indicators
|
| 6 |
+
directly from Hyperliquid testnet market data via the `candles_snapshot` API.
|
| 7 |
+
|
| 8 |
+
Available tools:
|
| 9 |
+
- ema → Exponential Moving Average
|
| 10 |
+
- macd → Moving Average Convergence Divergence
|
| 11 |
+
- stoch_rsi → Stochastic RSI
|
| 12 |
+
- adl → Accumulation / Distribution Line
|
| 13 |
+
- obv → On-Balance Volume
|
| 14 |
+
- atr_adx → Average True Range / Directional Index / ADX
|
| 15 |
+
- bbands → Bollinger Bands
|
| 16 |
+
- mfi → Money Flow Index
|
| 17 |
+
- vwap → Volume-Weighted Average Price
|
| 18 |
+
- volume → Raw trading volume
|
| 19 |
+
- bundle → Compute multiple indicators in one call
|
| 20 |
+
|
| 21 |
+
Run:
|
| 22 |
+
python hl_indicators_server.py
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
from typing import List, Optional, Literal, Dict, Any
|
| 27 |
+
|
| 28 |
+
from mcp.server.fastmcp import FastMCP
|
| 29 |
+
import hl_indicators as hi
|
| 30 |
+
|
| 31 |
+
Interval = Literal["1m", "5m", "15m", "1h", "4h", "1d"]
|
| 32 |
+
|
| 33 |
+
mcp = FastMCP("hl_indicators_server")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# ------------------ Health check ------------------ #
|
| 37 |
+
|
| 38 |
+
@mcp.tool()
|
| 39 |
+
async def ping() -> str:
|
| 40 |
+
"""Check if the MCP server is online and responding."""
|
| 41 |
+
return "pong"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# ------------------ Indicator tools ------------------ #
|
| 45 |
+
|
| 46 |
+
@mcp.tool()
|
| 47 |
+
async def ema(
|
| 48 |
+
name: str,
|
| 49 |
+
interval: Interval = "1h",
|
| 50 |
+
periods: Optional[List[int]] = None,
|
| 51 |
+
lookback: Optional[int] = None,
|
| 52 |
+
limit: int = 600,
|
| 53 |
+
) -> Dict[str, Any]:
|
| 54 |
+
"""
|
| 55 |
+
Compute Exponential Moving Averages (EMA).
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
name: Coin name (e.g. "BTC", "ETH", "HYPE").
|
| 59 |
+
interval: Candle interval ("1m", "5m", "15m", "1h", "4h", "1d").
|
| 60 |
+
periods: List of EMA window lengths (e.g. [20, 200]).
|
| 61 |
+
lookback: Optional shorthand for a single EMA (e.g. 36).
|
| 62 |
+
limit: Number of candles to fetch from the API.
|
| 63 |
+
|
| 64 |
+
Notes:
|
| 65 |
+
- `limit` controls how many data points are retrieved; it should be at
|
| 66 |
+
least 2–3× the largest EMA period for accurate results.
|
| 67 |
+
- The function automatically uses Hyperliquid testnet data.
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
A dictionary containing EMA series for each period and the most recent values.
|
| 71 |
+
"""
|
| 72 |
+
if periods is None and lookback is not None:
|
| 73 |
+
periods = [lookback]
|
| 74 |
+
return hi.get_ema(name=name, periods=periods, interval=interval, limit=limit, testnet=False)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@mcp.tool()
|
| 78 |
+
async def macd(
|
| 79 |
+
name: str,
|
| 80 |
+
interval: Interval = "1h",
|
| 81 |
+
fast: int = 12,
|
| 82 |
+
slow: int = 26,
|
| 83 |
+
signal: int = 9,
|
| 84 |
+
limit: int = 600,
|
| 85 |
+
) -> Dict[str, Any]:
|
| 86 |
+
"""
|
| 87 |
+
Compute the Moving Average Convergence Divergence (MACD).
|
| 88 |
+
|
| 89 |
+
Args:
|
| 90 |
+
name: Coin name (e.g. "BTC").
|
| 91 |
+
interval: Candle interval.
|
| 92 |
+
fast: Period for the fast EMA (default: 12).
|
| 93 |
+
slow: Period for the slow EMA (default: 26).
|
| 94 |
+
signal: Period for the MACD signal line (default: 9).
|
| 95 |
+
limit: Number of candles to fetch.
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
A dictionary with MACD line, signal line, histogram, and last computed values.
|
| 99 |
+
"""
|
| 100 |
+
return hi.get_macd(name=name, fast=fast, slow=slow, signal=signal, interval=interval, limit=limit, testnet=False)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
@mcp.tool()
|
| 104 |
+
async def stoch_rsi(
|
| 105 |
+
name: str,
|
| 106 |
+
interval: Interval = "1h",
|
| 107 |
+
rsi_length: int = 14,
|
| 108 |
+
stoch_length: int = 14,
|
| 109 |
+
k_smooth: int = 3,
|
| 110 |
+
d_smooth: int = 3,
|
| 111 |
+
limit: int = 600,
|
| 112 |
+
) -> Dict[str, Any]:
|
| 113 |
+
"""
|
| 114 |
+
Compute the Stochastic RSI oscillator (%K and %D).
|
| 115 |
+
|
| 116 |
+
Args:
|
| 117 |
+
name: Coin name.
|
| 118 |
+
interval: Candle interval.
|
| 119 |
+
rsi_length: Period for RSI computation (default: 14).
|
| 120 |
+
stoch_length: Period for Stochastic window (default: 14).
|
| 121 |
+
k_smooth: Smoothing factor for %K (default: 3).
|
| 122 |
+
d_smooth: Smoothing factor for %D (default: 3).
|
| 123 |
+
limit: Number of candles to fetch.
|
| 124 |
+
|
| 125 |
+
Returns:
|
| 126 |
+
A dictionary containing %K, %D, and the raw StochRSI values.
|
| 127 |
+
"""
|
| 128 |
+
return hi.get_stoch_rsi(
|
| 129 |
+
name=name,
|
| 130 |
+
rsi_length=rsi_length,
|
| 131 |
+
stoch_length=stoch_length,
|
| 132 |
+
k_smooth=k_smooth,
|
| 133 |
+
d_smooth=d_smooth,
|
| 134 |
+
interval=interval,
|
| 135 |
+
limit=limit,
|
| 136 |
+
testnet=False,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@mcp.tool()
|
| 141 |
+
async def adl(name: str, interval: Interval = "1h", limit: int = 600) -> Dict[str, Any]:
|
| 142 |
+
"""
|
| 143 |
+
Compute the Accumulation/Distribution Line (ADL).
|
| 144 |
+
|
| 145 |
+
Args:
|
| 146 |
+
name: Coin name.
|
| 147 |
+
interval: Candle interval.
|
| 148 |
+
limit: Number of candles to fetch.
|
| 149 |
+
|
| 150 |
+
Returns:
|
| 151 |
+
A dictionary containing the ADL time series and the latest ADL value.
|
| 152 |
+
"""
|
| 153 |
+
return hi.get_adl(name=name, interval=interval, limit=limit, testnet=False)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
@mcp.tool()
|
| 157 |
+
async def obv(name: str, interval: Interval = "1h", limit: int = 600) -> Dict[str, Any]:
|
| 158 |
+
"""
|
| 159 |
+
Compute the On-Balance Volume (OBV).
|
| 160 |
+
|
| 161 |
+
Args:
|
| 162 |
+
name: Coin name.
|
| 163 |
+
interval: Candle interval.
|
| 164 |
+
limit: Number of candles to fetch.
|
| 165 |
+
|
| 166 |
+
Returns:
|
| 167 |
+
OBV values accumulated over time and the latest OBV.
|
| 168 |
+
"""
|
| 169 |
+
return hi.get_obv(name=name, interval=interval, limit=limit, testnet=False)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
@mcp.tool()
|
| 173 |
+
async def atr_adx(name: str, interval: Interval = "1h", period: int = 14, limit: int = 600) -> Dict[str, Any]:
|
| 174 |
+
"""
|
| 175 |
+
Compute volatility and directional indicators: ATR, +DI, -DI, and ADX.
|
| 176 |
+
|
| 177 |
+
Args:
|
| 178 |
+
name: Coin name.
|
| 179 |
+
interval: Candle interval.
|
| 180 |
+
period: Lookback for smoothing (default: 14).
|
| 181 |
+
limit: Number of candles to fetch.
|
| 182 |
+
|
| 183 |
+
Returns:
|
| 184 |
+
A dictionary with ATR, +DI, -DI, and ADX values.
|
| 185 |
+
"""
|
| 186 |
+
return hi.get_atr_adx(name=name, period=period, interval=interval, limit=limit, testnet=False)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
@mcp.tool()
|
| 190 |
+
async def bbands(
|
| 191 |
+
name: str,
|
| 192 |
+
interval: Interval = "1h",
|
| 193 |
+
period: int = 20,
|
| 194 |
+
std_mult: float = 2.0,
|
| 195 |
+
limit: int = 600,
|
| 196 |
+
) -> Dict[str, Any]:
|
| 197 |
+
"""
|
| 198 |
+
Compute Bollinger Bands (basis, upper/lower bands, %b, bandwidth).
|
| 199 |
+
|
| 200 |
+
Args:
|
| 201 |
+
name: Coin name.
|
| 202 |
+
interval: Candle interval.
|
| 203 |
+
period: Window for SMA (default: 20).
|
| 204 |
+
std_mult: Standard deviation multiplier (default: 2.0).
|
| 205 |
+
limit: Number of candles to fetch.
|
| 206 |
+
|
| 207 |
+
Returns:
|
| 208 |
+
A dictionary with band series and the most recent band values.
|
| 209 |
+
"""
|
| 210 |
+
return hi.get_bbands(name=name, period=period, std_mult=std_mult, interval=interval, limit=limit, testnet=False)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
@mcp.tool()
|
| 214 |
+
async def mfi(name: str, interval: Interval = "1h", period: int = 14, limit: int = 600) -> Dict[str, Any]:
|
| 215 |
+
"""
|
| 216 |
+
Compute the Money Flow Index (MFI), a volume-weighted momentum oscillator.
|
| 217 |
+
|
| 218 |
+
Args:
|
| 219 |
+
name: Coin name.
|
| 220 |
+
interval: Candle interval.
|
| 221 |
+
period: Rolling window (default: 14).
|
| 222 |
+
limit: Number of candles to fetch.
|
| 223 |
+
|
| 224 |
+
Returns:
|
| 225 |
+
A dictionary containing MFI series and the most recent value.
|
| 226 |
+
"""
|
| 227 |
+
return hi.get_mfi(name=name, period=period, interval=interval, limit=limit, testnet=False)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
@mcp.tool()
|
| 231 |
+
async def vwap(name: str, interval: Interval = "1h", daily_reset: bool = False, limit: int = 600) -> Dict[str, Any]:
|
| 232 |
+
"""
|
| 233 |
+
Compute the Volume-Weighted Average Price (VWAP).
|
| 234 |
+
|
| 235 |
+
Args:
|
| 236 |
+
name: Coin name.
|
| 237 |
+
interval: Candle interval.
|
| 238 |
+
daily_reset: If True, VWAP resets each trading day.
|
| 239 |
+
limit: Number of candles to fetch.
|
| 240 |
+
|
| 241 |
+
Returns:
|
| 242 |
+
VWAP time series and the last computed VWAP value.
|
| 243 |
+
"""
|
| 244 |
+
return hi.get_vwap(name=name, daily_reset=daily_reset, interval=interval, limit=limit, testnet=False)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
@mcp.tool()
|
| 248 |
+
async def volume(name: str, interval: Interval = "1h", limit: int = 600) -> Dict[str, Any]:
|
| 249 |
+
"""
|
| 250 |
+
Retrieve the raw trading volume per candle.
|
| 251 |
+
|
| 252 |
+
Args:
|
| 253 |
+
name: Coin name.
|
| 254 |
+
interval: Candle interval.
|
| 255 |
+
limit: Number of candles to fetch.
|
| 256 |
+
|
| 257 |
+
Returns:
|
| 258 |
+
Volume values for each candle and the latest volume.
|
| 259 |
+
"""
|
| 260 |
+
return hi.get_volume(name=name, interval=interval, limit=limit, testnet=False)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
@mcp.tool()
|
| 264 |
+
async def bundle(
|
| 265 |
+
name: str,
|
| 266 |
+
interval: Interval = "1h",
|
| 267 |
+
limit: int = 600,
|
| 268 |
+
include: Optional[List[str]] = None,
|
| 269 |
+
ema_periods: Optional[List[int]] = None,
|
| 270 |
+
macd_fast: int = 12,
|
| 271 |
+
macd_slow: int = 26,
|
| 272 |
+
macd_signal: int = 9,
|
| 273 |
+
stoch_rsi_len: int = 14,
|
| 274 |
+
stoch_len: int = 14,
|
| 275 |
+
k_smooth: int = 3,
|
| 276 |
+
d_smooth: int = 3,
|
| 277 |
+
bb_period: int = 20,
|
| 278 |
+
bb_std: float = 2.0,
|
| 279 |
+
mfi_period: int = 14,
|
| 280 |
+
vwap_daily_reset: bool = False,
|
| 281 |
+
) -> Dict[str, Any]:
|
| 282 |
+
"""
|
| 283 |
+
Compute multiple indicators in a single request.
|
| 284 |
+
|
| 285 |
+
Args:
|
| 286 |
+
name: Coin name.
|
| 287 |
+
interval: Candle interval.
|
| 288 |
+
limit: Number of candles to fetch.
|
| 289 |
+
include: List of indicators to include. Default includes all:
|
| 290 |
+
["ema","macd","stoch_rsi","adl","obv","atr_adx","bbands","mfi","vwap","volume"]
|
| 291 |
+
ema_periods: EMA periods (default: [20, 200]).
|
| 292 |
+
macd_fast / macd_slow / macd_signal: MACD configuration.
|
| 293 |
+
stoch_rsi_len / stoch_len / k_smooth / d_smooth: StochRSI configuration.
|
| 294 |
+
bb_period / bb_std: Bollinger Band configuration.
|
| 295 |
+
mfi_period: Money Flow Index lookback.
|
| 296 |
+
vwap_daily_reset: Whether VWAP resets daily.
|
| 297 |
+
|
| 298 |
+
Returns:
|
| 299 |
+
A combined dictionary with all requested indicators.
|
| 300 |
+
"""
|
| 301 |
+
return hi.get_bundle(
|
| 302 |
+
name=name,
|
| 303 |
+
interval=interval,
|
| 304 |
+
limit=limit,
|
| 305 |
+
testnet=False,
|
| 306 |
+
include=include or ("ema","macd","stoch_rsi","adl","obv","atr_adx","bbands","mfi","vwap","volume"),
|
| 307 |
+
ema_periods=ema_periods,
|
| 308 |
+
macd_fast=macd_fast,
|
| 309 |
+
macd_slow=macd_slow,
|
| 310 |
+
macd_signal=macd_signal,
|
| 311 |
+
stoch_rsi_len=stoch_rsi_len,
|
| 312 |
+
stoch_len=stoch_len,
|
| 313 |
+
k_smooth=k_smooth,
|
| 314 |
+
d_smooth=d_smooth,
|
| 315 |
+
bb_period=bb_period,
|
| 316 |
+
bb_std=bb_std,
|
| 317 |
+
mfi_period=mfi_period,
|
| 318 |
+
vwap_daily_reset=vwap_daily_reset,
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
# ------------------ Entry point ------------------ #
|
| 323 |
+
|
| 324 |
+
if __name__ == "__main__":
|
| 325 |
+
mcp.run(transport='stdio')
|
hype_accounts.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# uv pip install hyperliquid-python-sdk pandas numpy python-dotenv
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
import eth_account
|
| 5 |
+
import numpy as np
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from eth_account.signers.local import LocalAccount
|
| 8 |
+
|
| 9 |
+
from hyperliquid.exchange import Exchange
|
| 10 |
+
from hyperliquid.info import Info
|
| 11 |
+
from hyperliquid.utils import constants
|
| 12 |
+
|
| 13 |
+
from datetime import datetime, timezone
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# https://github.com/hyperliquid-dex/hyperliquid-python-sdk/blob/master/examples/example_utils.py
|
| 17 |
+
def setup(base_url=constants.MAINNET_API_URL, skip_ws=False, perp_dexs=None):
|
| 18 |
+
account: LocalAccount = eth_account.Account.from_key(os.getenv("HYPERLIQUID_PRIVATE_KEY"))
|
| 19 |
+
address = os.getenv("HYPERLIQUID_ACCOUNT_ADDRESS") # config["account_address"]
|
| 20 |
+
if address == "":
|
| 21 |
+
address = account.address
|
| 22 |
+
print("Running with account address:", address)
|
| 23 |
+
if address != account.address:
|
| 24 |
+
print("Running with agent address:", account.address)
|
| 25 |
+
info = Info(base_url, skip_ws, perp_dexs=perp_dexs)
|
| 26 |
+
user_state = info.user_state(address)
|
| 27 |
+
spot_user_state = info.spot_user_state(address)
|
| 28 |
+
margin_summary = user_state["marginSummary"]
|
| 29 |
+
if float(margin_summary["accountValue"]) == 0 and len(spot_user_state["balances"]) == 0:
|
| 30 |
+
print("Not running the example because the provided account has no equity.")
|
| 31 |
+
url = info.base_url.split(".", 1)[1]
|
| 32 |
+
error_string = f"No accountValue:\nIf you think this is a mistake, make sure that {address} has a balance on {url}.\nIf address shown is your API wallet address, update the config to specify the address of your account, not the address of the API wallet."
|
| 33 |
+
raise Exception(error_string)
|
| 34 |
+
exchange = Exchange(account, base_url, account_address=address, perp_dexs=perp_dexs)
|
| 35 |
+
return address, info, exchange
|
| 36 |
+
|
| 37 |
+
def long_short_shares_mkt_price(symbol, value, leverage, is_buy) -> str:
|
| 38 |
+
address, info, exchange = setup()
|
| 39 |
+
end_time = int(datetime.now(timezone.utc).timestamp() * 1000) # Current time in ms
|
| 40 |
+
start_time = int(datetime.now(timezone.utc).timestamp() * 1000) # Current time in ms
|
| 41 |
+
close_price = float(info.candles_snapshot(symbol, "1m", start_time, end_time)[0]['c'])
|
| 42 |
+
coins_metadata_df = pd.DataFrame(info.meta()['universe'])
|
| 43 |
+
rounding_decimals = coins_metadata_df[coins_metadata_df['name']==symbol]['szDecimals'].values[0]
|
| 44 |
+
quantity = np.round(float(value)/float(close_price),rounding_decimals)
|
| 45 |
+
print(quantity)
|
| 46 |
+
mkt_order_result = exchange.market_open(
|
| 47 |
+
name=symbol,
|
| 48 |
+
is_buy=is_buy,
|
| 49 |
+
sz=quantity,
|
| 50 |
+
px=None,
|
| 51 |
+
slippage=0.01
|
| 52 |
+
)
|
| 53 |
+
is_cross = True
|
| 54 |
+
user_state = info.user_state(address)
|
| 55 |
+
for asset_position in user_state["assetPositions"]:
|
| 56 |
+
if asset_position["position"]["coin"] == symbol:
|
| 57 |
+
print(f"Current leverage for {symbol}:", json.dumps(asset_position["position"]["leverage"], indent=2))
|
| 58 |
+
leverage_position = exchange.update_leverage(leverage, symbol, is_cross)
|
| 59 |
+
return {'mkt_order_result': mkt_order_result, 'leverage_position': leverage_position}
|
hype_accounts_server.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from mcp.server.fastmcp import FastMCP
|
| 2 |
+
from hype_accounts import setup, long_short_shares_mkt_price
|
| 3 |
+
|
| 4 |
+
mcp = FastMCP("hype_accounts_server")
|
| 5 |
+
|
| 6 |
+
@mcp.tool()
|
| 7 |
+
async def get_account_details() -> dict:
|
| 8 |
+
"""Get cash balance, cryptocurrency holdings and PNL of hyperliquid account.
|
| 9 |
+
"""
|
| 10 |
+
account_details_dict = {
|
| 11 |
+
"holdings": None,
|
| 12 |
+
"cash_balance": None,
|
| 13 |
+
"profit_and_loss": None
|
| 14 |
+
}
|
| 15 |
+
address, info, exchange = setup()
|
| 16 |
+
user_state = info.user_state(address)
|
| 17 |
+
account_details_dict["cash_balance"] = user_state['marginSummary']['accountValue']
|
| 18 |
+
if user_state['assetPositions'] == []:
|
| 19 |
+
account_details_dict["holdings"] = "No holdings"
|
| 20 |
+
else:
|
| 21 |
+
account_details_dict["holdings"] = user_state['assetPositions']
|
| 22 |
+
account_details_dict["profit_and_loss"] = exchange.info.portfolio(address)[3][1]['pnlHistory'][-1][1]
|
| 23 |
+
|
| 24 |
+
return account_details_dict
|
| 25 |
+
|
| 26 |
+
@mcp.tool()
|
| 27 |
+
async def long_perps_mkt_price(symbol: str, value:float, leverage:int) -> str:
|
| 28 |
+
"""Create a long position for a cryptocurrency perpetual contract at market price.
|
| 29 |
+
Args:
|
| 30 |
+
symbol: The symbol of the cryptocurrency (e.g., "HYPE","ETC","BTC","XRP").
|
| 31 |
+
value: The USD value to long.
|
| 32 |
+
leverage: The leverage to use for the position.
|
| 33 |
+
"""
|
| 34 |
+
return str(long_short_shares_mkt_price(symbol, value, leverage, is_buy=True))
|
| 35 |
+
|
| 36 |
+
@mcp.tool()
|
| 37 |
+
async def short_perps_mkt_price(symbol: str, value:float, leverage:int) -> str:
|
| 38 |
+
"""Create a short position for a cryptocurrency perpetual contract at market price.
|
| 39 |
+
Args:
|
| 40 |
+
symbol: The symbol of the cryptocurrency (e.g., "HYPE","ETC","BTC").
|
| 41 |
+
value: The USD value to short.
|
| 42 |
+
leverage: The leverage to use for the position.
|
| 43 |
+
"""
|
| 44 |
+
return str(long_short_shares_mkt_price(symbol, value, leverage, is_buy=False))
|
| 45 |
+
|
| 46 |
+
@mcp.tool()
|
| 47 |
+
async def close_perps_mkt_price(symbol: str) -> str:
|
| 48 |
+
"""Close the position for a cryptocurrency perpetual contract at market price.
|
| 49 |
+
Args:
|
| 50 |
+
symbol: The symbol of the cryptocurrency (e.g., "HYPE","ETC","BTC").
|
| 51 |
+
"""
|
| 52 |
+
_, _, exchange = setup()
|
| 53 |
+
return str(exchange.market_close(symbol))
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
mcp.run(transport='stdio')
|
memories.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"timestamp": "2025-10-14T00:01:08.706950", "summary": "Step 1: Current holdings and balance:\n- ETH: Long 0.0048 ETH at 4132.4 USD, cross 2x leverage, position value ~$19.85, small unrealized profit, bullish overall trend but short-term bearish signals.\n- BNB: Long 0.02 BNB at 1276.4 USD, cross 3x leverage, position value ~$25.5, slight unrealized loss, strong bullish momentum signs.\n- XRP: Long 4 XRP at 2.5872 USD, cross 2x leverage, position value ~$10.35, essentially flat PnL, showing short-term bearish momentum but longer-term support intact.\n- Cash balance: $64.84\n- Total profit and loss: +$4.84\n\nStep 2: Summary of latest crypto news:\n- Bitcoin price dropped $20B recently but recovering above $113K.\n- BNB has strong rebound with 13% gains.\n- Bitcoin whale sold 1,423 BTC.\n- Ethereum saw notable gains with institutional buying.\n- XRP gaining institutional interest and real-world use cases.\n- Overall market with some volatility but notable institutional accumulation.\n\nStep 3: Technical analysis and strategy proposal:\n\nETH (Daily):\n- EMA20 > EMA200 signals longer-term bullish, but current price below EMA20 and bearish MACD.\n- Stoch RSI near oversold levels.\n- ADL slight accumulation.\n- Strategy: Hold current long, consider adding on confirmed rebound above EMA20 with positive MACD crossover. Short positions not favored given overall bullish trend.\n\nBNB (Daily):\n- Price above EMA20 and EMA200, MACD positive, Stoch RSI coming out of oversold.\n- ADL rising with strong volume.\n- Strategy: Strong bullish signals support holding current long. Consider adding on minor pullbacks toward EMA20 (~1146). No short position recommended.\n\nBTC (Daily):\n- Price slightly below EMA20 but above EMA200; MACD bearish short-term; stochastic RSI oversold.\n- ADL indicates accumulation.\n- Strategy: Wait for bullish MACD crossover and reclaim above EMA20 for long entries. Aggressive small longs could be considered due to oversold condition, but tight stops advised.\n\nXRP (Daily):\n- Price below EMA20 but above EMA200; bearish MACD; Stoch RSI neutral to oversold.\n- Stable ADL, moderate volume.\n- Strategy: Hold current position, await confirmation of reversal above EMA20 or breakdown below EMA200 before trading changes.\n\nStep 4: Execution decisions:\n\n- ETH: No new action now; hold current long.\n- BNB: No new action; current position aligns with bullish trend.\n- BTC: No position currently held; considering opportunity if confirmed bullish signals emerge. No trade now.\n- XRP: Hold; no clear bullish or bearish trigger for new trade.\n\nSummary:\n- Maintain current longs on ETH, BNB, XRP.\n- Monitor BTC for a potential long entry if MACD and price action confirm bullish reversal.\n- No shorts executed due to unclear or unfavorable setups.\n- Preserve cash balance for better entry points.\n\nNo trades executed at this time based on current signals and risk management."}
|
| 2 |
+
{"timestamp": "2025-10-14T00:31:22.935302", "summary": "Step 1: Confirmed current holdings and cash balance:\n- ETH: Long 0.0048 ETH, 2x cross leverage, entry around 4144.7 USD, small unrealized profit.\n- BNB: Long 0.02 BNB, 3x cross leverage, entry around 1278.4 USD, small unrealized profit.\n- XRP: Long 4 XRP, 2x cross leverage, entry around 2.58 USD, slight unrealized loss.\n- Cash balance approximately $65.05.\n- Total profit and loss about +$5.05.\n\nStep 2: General latest crypto news:\n- Bitcoin rebounded strongly above $114,000 after a large drop.\n- Major liquidation event ($19.38B) recently occurred, market still volatile.\n- XRP shows warning signs and encounters resistance around $2.49.\n- BNB sharply rebounding over 13%, fueled by Binance Chain utility but with regulatory risks.\n- Bitcoin whale sold 1,423 BTC, signaling portfolio rebalancing.\n- Ethena\u2019s stablecoin plummeted but stabilized.\n- Market highly volatile with opportunities amidst caution.\n\nStep 3: Technical analysis on selected cryptocurrencies (ETH, BNB, XRP, BTC) on 1-hour charts:\n\nETH:\n- Price above 20 EMA (4113.88) and near 200 EMA (4146.51).\n- MACD bearish but possibly bottoming.\n- Stoch RSI recovering from oversold.\n- ADL shows some accumulation.\n- Volume moderate.\nStrategy: Neutral to mild bullish; wait for confirmed bullish crossovers of EMA and MACD or bounce on 200 EMA support before adding long; protect with stops.\n\nBNB:\n- Price slightly below 20 EMA but above 200 EMA, indicating bullish trend.\n- MACD negative short term; Stoch RSI oversold but recovering.\n- ADL positive and rising.\n- Volume moderate-high.\nStrategy: Overall strong bullish trend; look for price to move above 20 EMA and MACD crossover to resume longs; avoid shorts; use tight stops.\n\nXRP:\n- Price near 20 EMA, below 200 EMA.\n- MACD slightly bearish; Stoch RSI oversold.\n- ADL positive but volume moderate.\nStrategy: Cautious bullish bias; potential rebound from oversold; wait for MACD confirmation; stop loss below 20 EMA; watch for breakdown.\n\nBTC:\n- Price below 20 and 200 EMAs.\n- MACD bearish; Stoch RSI oversold.\n- ADL declining, selling pressure.\n- Volume moderate.\nStrategy: No long entry until bullish confirmation; consider short on rallies with tight stops; risk cautious.\n\nStep 4: Executions:\n- Added to existing long positions on ETH ($20 at 2x leverage), BNB ($25 at 3x leverage), and XRP ($10 at 2x leverage) as conditions align with bullish recovery signals and accumulation indicators.\n- No new BTC trades due to mixed bearish signals and lack of clear confirmation.\n\nSummary: Maintained and added some longs on ETH, BNB, and XRP based on selective positive short-term signals within broader cautious market context. Preserved cash for better entries or clearer setups in BTC or if market deteriorates. No shorts taken due to unclear setups.\n\nIf you require updated monitoring or further trades, just advise."}
|
| 3 |
+
{"timestamp": "2025-10-14T01:01:24.284063", "summary": "Step 1: Current holdings and balance:\n- ETH: Long 0.0096 ETH at entry ~4138.55, small unrealized profit, 2x cross leverage, position value ~$39.96.\n- BNB: Long 0.04 BNB at entry ~1277.4, negligible unrealized profit, 3x cross leverage, position value ~$51.11.\n- XRP: Long 8 XRP at entry ~2.5844, small unrealized profit, 2x cross leverage, position value ~$20.74.\n- Cash balance: ~$65.16.\n- Overall profit: +$5.16.\n\nStep 2: Latest general crypto news:\n- Bitcoin rebounded to $114,000+ after dips, with volatility and a $20 billion recent liquidation event.\n- BNB surged 13% in 24h, showing strong utility and bullish momentum despite short-term oversold indicators.\n- XRP shows mixed signals with resistance signs and caution advised.\n- Bitcoin whale sales indicate portfolio rebalancing.\n- Overall market volatility with opportunities but requiring caution.\n\nStep 3: Technical analysis on 1-hour charts:\n\nETH:\n- Price: 4162.5, above EMA 20 (~4119) and EMA 200 (~4145).\n- MACD bearish crossover but overall positive momentum.\n- Stoch RSI neutral near oversold (41.9).\n- ADL shows some accumulation.\n- Volume low, signaling possible pause.\nStrategy: Wait for MACD confirming bounce, volume surge, and price to hold above EMA 20 before adding long positions. Use stops at recent swing low or EMA 20.\n\nBNB:\n- Price: 1277.4, just below EMA 20 (~1289) but above EMA 200 (~1230).\n- MACD bearish short-term; Stoch RSI oversold.\n- ADL stable, moderate accumulation.\n- Volume recently increased.\nStrategy: Watch for price to reclaim EMA 20 and a bullish MACD crossover before adding longs. Tight stops below EMA 20 advised.\n\nXRP:\n- Price: 2.5945, above 20 EMA (~2.57) but below 200 EMA (~2.64).\n- MACD slightly bearish; Stoch RSI near oversold (30).\n- ADL indicates buying interest, but volume low.\nStrategy: Consider entering longs only if volume picks up and MACD reverts bullishly above signal line, with stops near EMA 20.\n\nBTC:\n- Price: 114,547, below 200 EMA (~117,082), slightly above 20 EMA (~114,487).\n- MACD bearish; Stoch RSI oversold.\n- ADL declining; volume low.\nStrategy: No new longs until MACD bullish crossover and price breaks 200 EMA. Consider short exposure on rallies near 20 EMA with tight stops.\n\nStep 4: Execution:\n- No strong bullish confirmation yet for adding new longs in ETH, BNB, or XRP.\n- BTC remains bearish; no shorts due to low volume and uncertain bounce.\n- Hold current positions; preserve cash for clearer signals.\n\nSummary:\nCurrent positions in ETH, BNB, and XRP show small profits with cautious bullish bias. Market volatility and mixed indicator signals warrant patience. Await confirmed MACD reversal and volume increase before adding or scaling longs. No new trades executed now.\n\nI will monitor closely and act on any clear edge."}
|
| 4 |
+
{"timestamp": "2025-10-14T02:01:12.769442", "summary": "Step 1: Current holdings and balance remain unchanged with small unrealized profits in ETH, BNB, and XRP, and cash balance ~$66.37.\n\nStep 2: Latest crypto news overview:\n- Market rebound driven by easing US-China trade tensions.\n- Large whale opening major short positions, holding $4.5M unrealized profits.\n- Bitcoin and Ethereum rebounding but cautious due to extreme fear sentiment.\n- Isolated stablecoin attack on Binance.\n- XRP has a large market opportunity ahead.\n- Solana showing bullish momentum after recent sell-off.\n- General market recovering amid volatility.\n\nStep 3: Technical analysis (1-hour):\n\nETH:\n- Price: 4227.5 above EMA20 (4136.1) and EMA200 (4135.2).\n- MACD slightly bearish (line below signal, negative histogram).\n- Stoch RSI bullish but not overbought.\n- ADL shows accumulation but declining recently.\n- Volume low.\nStrategy: Wait for MACD bullish crossover and volume increase; possible long entries on dips near EMA20 (~4136) with tight stops below EMA200 (~4135).\n\nBNB:\n- Price: 1284 below EMA20 (1289.4) but above EMA200 (1232.1).\n- MACD bearish; Stoch RSI oversold (~18.7).\n- ADL shows moderate accumulation; volume low.\nStrategy: Watch for price reclaiming EMA20 with MACD bullish crossover; oversold condition could mean short-term bounce. Consider small long positions with stop losses below recent lows, else be cautious.\n\nXRP:\n- Price: 2.6219 above EMA20 (2.5751) but below EMA200 (2.6388).\n- MACD slightly bearish; Stoch RSI neutral.\n- ADL rising signaling accumulation; volume moderate to low.\nStrategy: Consider longs only if price breaks above EMA200 with MACD turning positive and volume picking up; cautious for now.\n\nBTC:\n- Price: 114,934 below EMA200 (117,009), near EMA20 (114,565).\n- MACD bearish; Stoch RSI neutral but recovering from oversold.\n- ADL negative; volume low.\nStrategy: Bearish bias; consider shorts on rallies near EMA20 with confirmation. Avoid longs until MACD bullish and price moves above EMA200 with volume.\n\nSOL:\n- Price: 205.7 above EMA20 (196.68) and EMA200 (203.62).\n- MACD bullish; Stoch RSI overbought (100).\n- ADL rising; volume low.\nStrategy: Bullish trend with strong momentum. Await pullback towards EMA20 or consolidation before adding longs to reduce risk, watch volume for confirmation.\n\nStep 4: Execution:\n- Existing ETH, BNB, and XRP positions showing small profits; hold these.\n- No clear buy/sell signals to adjust positions in ETH, BNB, XRP, BTC now.\n- SOL shows bullish momentum but overbought Stoch RSI and volume low, prefer to wait for a pullback or consolidation.\n- No new trades executed to avoid risk amid mixed signals.\n\nSummary: Hold current positions with small profits; no new trade entries given unclear momentum and low volume except possible watch on SOL pullbacks. Await clear MACD crossovers and volume confirmation for new longs or shorts."}
|
| 5 |
+
{"timestamp": "2025-10-14T02:30:53.996902", "summary": "Step 1: Current holdings and balance confirmed:\n- ETH long 0.0096 @ 4138.55, unrealized PnL ~$0.85, 2x leverage\n- BNB long 0.04 @1277.4, slight unrealized loss ~$-0.01, 3x leverage\n- XRP long 8.0 @ 2.5844, unrealized PnL ~$0.30, 2x leverage\n- Cash balance: ~$66.0\n- Total PnL: ~$6.0\n\nStep 2: Latest crypto news highlights:\n- XRP remains in focus amid ETF developments, but mixed market with XRP losing some ground.\n- Large whale activity with significant short positions ongoing.\n- Market recovering moderately after recent crash and geopolitical tensions.\n- Bitcoin and Ethereum still volatile with skepticism among institutions.\n- BNB recent highs driven by real ecosystem growth.\n- Extreme fear sentiment in market currently.\n- Stablecoin issues on Binance appear isolated.\n- Overall, selective recovery amid risk appetite returning.\n\nStep 3: Technical indicator summaries and strategies:\n\nETH (1h):\n- Price (4228) above EMA20 (~4136) and EMA200 (~4135).\n- MACD mildly bearish (line below signal), but histogram near zero.\n- Stoch RSI moderately bullish, not overbought.\n- ADL turning upward indicating accumulation.\n- Volume moderate.\n- Strategy: Maintain long position, consider adding on pullback toward 20 EMA if MACD turns bullish and volume increases. Stop below 200 EMA (~4135). Target new highs.\n\nBNB (1h):\n- Price (1277) below EMA20 (1288), above EMA200 (1215).\n- MACD bearish, Stoch RSI oversold.\n- ADL flat to mildly positive; volume mixed.\n- Strategy: Wait for MACD bullish crossover and price reclaiming EMA20 before adding longs. Otherwise hold current with tight stops below recent lows.\n\nXRP (1h):\n- Price (2.625) between EMA20 (2.575) and EMA200 (2.638).\n- MACD slightly bearish, Stoch RSI neutral.\n- ADL rising (accumulation), volume moderate.\n- Strategy: Hold current position, consider longs only on price break above EMA200 with MACD confirmation. Stop near 20 EMA (~2.57).\n\nBTC (1h):\n- Price (115,225) below EMA200 (117,012), just above EMA20 (114,592).\n- MACD bearish, Stoch RSI moderately bullish but low volume.\n- ADL negative (distribution).\n- Strategy: Maintain bearish bias; consider shorts near 20 EMA resistance with tight stops; avoid longs until breakout above EMA200 with MACD confirmation.\n\nSOL (1h):\n- Price (206) above EMA20 (196.7) and EMA200 (203.6).\n- MACD bullish, Stoch RSI overbought.\n- ADL rising, volume steady.\n- Strategy: Wait for pullback to EMA20 for safer longs; avoid buying into overbought condition. Partial profit-taking on existing longs if held.\n\nStep 4: Execution:\n- Current positions in ETH and XRP show unrealized profits; hold.\n- BNB position slightly negative; hold and watch for MACD reversal.\n- No BTC or SOL positions currently.\n- No clear new trade entries with strong conviction due to mixed or overbought signals.\n- Abstain from new trades to avoid risk.\n\nSummary:\nMaintain current ETH, BNB, XRP longs. No new trades executed due to mixed/unclear momentum or overbought signals. Await clearer confirmations on MACD/video for ETH add, BNB rebound, XRP breakout, or BTC short entries. Watch SOL pullback opportunities.\n\nNo trades executed given current analysis."}
|
| 6 |
+
{"timestamp": "2025-10-14T03:00:56.196126", "summary": "Step 1: Current holdings and balance:\n- ETH long 0.0096 @ 4138.55, unrealized PnL +0.85, 2x leverage\n- BNB long 0.04 @ 1277.4, unrealized PnL -0.41, 3x leverage\n- XRP long 8 @ 2.5844, unrealized PnL +0.20, 2x leverage\n- Cash balance ~$65.50\n- Total PnL +5.50\n\nStep 2: Latest crypto news summary:\n- Major short squeeze liquidations in BTC and ETH triggered bullish rebound.\n- Market crash due to geopolitical and tariff fears reversed after clarification.\n- XRP eyed for potential year-end bullish seasonal trends amid ETF news.\n- Large whale activity with significant new short positions noted.\n- Institutional skepticism on Bitcoin remains.\n- BNB rally driven by real ecosystem growth.\n- Altcoins suffered more in crash than BTC.\n- Market recovering with selective risk appetite.\n\nStep 3: Technical analysis and strategy for selected coins (ETH, BNB, XRP, BTC):\n\nETH (1h):\n- Price ~4224 above EMA20 (4144) and EMA200 (4122)\n- MACD weakly bearish, histogram slightly negative\n- Stoch RSI moderately high (68.2), not yet overbought\n- ADL shows reduction in selling pressure\n- Volume very low recently\nStrategy: Hold current long; consider adding if MACD turns positive and volume picks up, placing stop below EMA20 (~4144). Avoid new longs while momentum weakens or volume low.\n\nBNB (1h):\n- Price ~1267 below EMA20 (1286), above EMA200 (1228)\n- MACD bearish; histogram negative\n- Stoch RSI very oversold (6.5), indicating potential bounce\n- ADL steady; volume very low\nStrategy: Watch for signs of momentum reversal such as Stoch RSI rising above 20 and MACD histogram positive before adding longs. Current position hold with tight stops below recent lows.\n\nXRP (1h):\n- Price ~2.607 above EMA20 (2.577), below EMA200 (2.629)\n- MACD slightly bearish, histogram negative\n- Stoch RSI recovering (33.5), not yet bullish\n- ADL rising; volume fluctuating low\nStrategy: Hold long; consider adding on confirmed breakout and sustained price above EMA200 (~2.63) with MACD turning positive and volume increase. Use tight stop below EMA20 (~2.58).\n\nBTC (1h):\n- Price ~115,105 above EMA20 (~114,627) and EMA200 (~113,636)\n- MACD weak bearish; histogram negative\n- Stoch RSI neutral (~50)\n- ADL rising, low volume\nStrategy: Consider cautious long at dips near EMA20 support, tight stops below EMA200. Await MACD bullish crossover and volume increase before adding aggressively.\n\nStep 4: Execution:\n- Maintain all current positions (ETH, BNB, XRP) as indicators show mixed signals without clear strong new entry or exit signals.\n- No new positions opened or closed due to lack of high-confidence setups.\n- Monitor for MACD and volume confirmation on ETH and XRP for potential add.\n- Watch BNB for reversal signals before increasing exposure.\n- BTC longs can be considered on dips if stop risks are respected, but not initiating new shorts now.\n\nSummary:\nHold current ETH, BNB, XRP longs with caution. Await stronger confirmation signals for additions. No new trades executed given current mixed/inconclusive short-term momentum and low volume."}
|
| 7 |
+
{"timestamp": "2025-10-14T03:30:41.740954", "summary": "Step 1 & 2: Current holdings and balance remain as follows:\n- ETH long 0.0143 @ avg price ~4246.3 (added 0.0047 ETH at $4246.3, 2x leverage)\n- BNB long 0.04 @ 1277.4, current unrealized PnL slightly negative\n- XRP long 8 @ 2.5844, small positive unrealized PnL\n- Cash balance ~$45.8 after partial ETH add\n- Total PnL +5.85\n\nLatest crypto market news:\n- Severe downturn triggered by unexpected tariffs, led to $19B liquidations.\n- XRP flash crash but quick rebound.\n- Binance compensated users for collateral asset depegging event.\n- Stablecoins injected $1.75B liquidity.\n- ETH and BTC rallied after massive liquidations on shorts.\n- BNB ecosystem growth pushing price to all-time highs.\n- Large whale short activity at play.\n- Market highly volatile, caution advised.\n\nStep 3:\nTechnical analysis (1h):\n- ETH showing bullish indications: Price above EMA20/200, positive MACD histogram, Stoch RSI nearing overbought but still bullish, recommending adding longs on dips near EMA20.\n- BNB in longer-term uptrend but short-term pullback: EMA20 above EMA200 but MACD weak bearish, Stoch RSI oversold signaling potential reversal.\n- XRP neutral with sideways movement near resistance: EMA20 slightly below EMA200, weak MACD bearish, Stoch RSI neutral.\n- BTC remains bearish with EMA20 below EMA200 and negative MACD, Stoch RSI shows possible short retracement but trend weak.\n\nStep 4: Execution\n- Added to ETH long position with an additional $20 at 2x leverage around 4246.3, increasing exposure due to strong bullish technical signals and positive momentum.\n\nNo positions added or closed on BNB, XRP, or BTC given mixed or weak signals.\n\nSummary:\n- Maintain current BNB and XRP longs.\n- Add to ETH long on dip with confirmed momentum.\n- Monitor BNB for reversal signals before adding.\n- Avoid BTC longs until stronger bullish signals appear.\n\nLet me know if you want updated technical signals or further execution."}
|
| 8 |
+
{"timestamp": "2025-10-14T04:01:03.373794", "summary": "Step 1: Current holdings and balance checked. Cash balance approximately $65.94 with open longs in ETH, BNB, and XRP.\n\nStep 2: Latest news summary found:\n- Positive market sentiment due to easing US-China trade tensions.\n- ETH rallied strongly, reclaiming $4,000 and showing potential to $5,000.\n- Binance compensated users for a collateral event; XRP experienced a sharp flash crash but rebounded quickly.\n- BTC and ETH shorts heavily liquidated signaling bullish momentum.\n- Stablecoin inflows providing liquidity; altcoins pumping but with caution advised.\n\nStep 3: Technical Indicators and Strategy Summary (1h timeframe):\n- ETH: Bullish overall with EMA20 above EMA200, MACD positive, ADL rising, Stoch RSI high but still bullish. Strategy: Add to longs on dips near EMA20.\n- BNB: Long-term uptrend remains but short-term bearish with price below EMA20, MACD bearish, Stoch RSI oversold. Strategy: Wait for price to cross above EMA20 with MACD confirmation before adding longs.\n- XRP: Neutral to cautious bullish, price between EMA20 and EMA200 with weak MACD. ADL rising but volume low. Strategy: Watch for breakout above EMA200 to consider longs, else cautious.\n- BTC: Overall bearish trend below EMA200 but short-term bounce with MACD crossover and high Stoch RSI. Strategy: Cautious short-term longs with tight stops near EMA20.\n\nStep 4: Execution:\n- Added to ETH long position with $20 at 2x leverage near 4250.1 as per bullish signals and strong momentum.\n\nNo new trades executed for BNB, XRP, or BTC due to mixed or weak setups.\n\nSummary:\n- ETH longs increased on strong setup.\n- Hold BNB and XRP positions; monitor for stronger signals.\n- Avoid BTC longs until clearer bullish momentum appears.\n\nLet me know if you want additional analysis or further executions."}
|
| 9 |
+
{"timestamp": "2025-10-14T04:30:49.849240", "summary": "Executed adding $20 long ETH at 2x leverage near EMA20 as expected.\n\nSummary:\n- Holding current positions in ETH, BNB, XRP.\n- Added $20 more to ETH long.\n- No trades on BNB, XRP, BTC, ADA due to weak or unclear setups.\n\nWill monitor for XRP dip near EMA20 to consider a long add if conditions improve. Abstain from BTC and ADA positions for now.\n\nLet me know if you want continuous monitoring or specific coin analysis."}
|
| 10 |
+
{"timestamp": "2025-10-14T06:30:53.817960", "summary": "Step 1: Current holdings and balance:\n- ETH long 0.0182 @ entry 4190.24, position value $77.38, unrealized PNL $1.12 (2x leverage)\n- BNB long 0.04 @ entry 1277.4, position value $51.56, unrealized PNL $0.47 (3x leverage)\n- XRP long 8 @ entry 2.5844, position value $20.87, unrealized PNL $0.20 (2x leverage)\n- Cash balance: $66.63\n- Total profit and loss: $6.63\n\nStep 2: Latest crypto news highlights:\n- BTC bullish momentum with surge to $115K and possible major breakout.\n- ADA shows whale buying and bullish momentum breaking resistance at $0.75.\n- XRP formed capitulation wick but finding support near $2.70-$2.80, potential bullish monthly close.\n- SOL has sharp fall but strong treasury accumulation, potential upside.\n- Stablecoins stable; market volatility and sentiment remain cautious.\n\nStep 3: Technical analysis and indicator summary (1-hour timeframe, last 100 candles):\n\nBTC:\n- Price $115,525 above 20EMA but below 200EMA; short-term bullish, longer-term resistance.\n- MACD bullish crossover with moderate momentum.\n- Stoch RSI overbought (~81), caution of pullback.\n- ADL negative, volume low.\nStrategy: Cautious longs with tight stops below 20EMA, target break above 200EMA.\n\nADA:\n- Price $0.731 above 20EMA but below 200EMA; short-term bullish, overall slightly bearish.\n- MACD near bearish crossover.\n- Stoch RSI neutral; ADL rising, volume moderate.\nStrategy: Await MACD bullish crossover or price above 200EMA for strong long entries; cautious longs possible.\n\nXRP:\n- Price $2.61 slightly below 200EMA but above 20EMA; transitional trend.\n- MACD weakening, negative histogram.\n- Stoch RSI oversold but rising.\n- ADL rising but volume down.\nStrategy: Wait for confirmation of reversal/bounce above 200EMA and MACD crossover to long; cautious neutral-bullish bias.\n\nSOL:\n- Price $207.94 above 20 and 200EMA; strong bullish trend.\n- MACD positive, stochastic RSI overbought.\n- ADL showing accumulation, volume moderate.\nStrategy: Long on trend with stops below 20EMA; watch for overbought pullbacks.\n\nETH:\n- Price $4252 above 20 and 200EMA; bullish trend.\n- MACD positive though histogram slightly shrinking.\n- Stoch RSI strong but not extreme.\n- ADL rising, volume lower recently.\nStrategy: Long on dips near 20EMA (~4177) with confirmation from stochastic RSI and volume; cautious for momentum easing.\n\nStep 4: Strategy and trade execution:\n\n- BTC: No new positions due to overbought Stoch RSI and weak volume despite bullish setup.\n- ADA: No trades; waiting for MACD crossover or price confirmation above 200EMA.\n- XRP: Hold current long; no add due to weak MACD, low volume; watch for bounce confirmation.\n- SOL: Current no position; opportunity identified for a long setup but will wait for minor pullback or confirmation due to overbought Stoch RSI.\n- ETH: Already holding long; no changes needed; monitor for dip near 20EMA for possible small adder if signals remain bullish.\n\nNo new trades executed at this time due to mixed signals or risk management considerations. Holding current positions with close monitoring suggested.\n\nSummary:\n- Maintain ETH, BNB, XRP longs.\n- No new longs or shorts now; watch BTC, ADA, XRP for clearer signals.\n- Potential long opportunity on SOL after pullback.\n- Continue monitoring indicators and volume for entries."}
|
| 11 |
+
{"timestamp": "2025-10-14T07:01:08.925098", "summary": "Step 1: Current holdings and balance (as of now):\n- ETH long 0.0182 at $4190.24, position value ~$77.66, unrealized PNL ~$1.40 (2x leverage)\n- BNB long 0.04 at $1277.4, position value $51.66, unrealized PNL $0.57 (3x leverage)\n- XRP long 8 at $2.5844, position value $20.85, unrealized PNL $0.17 (2x leverage)\n- Cash balance: $66.99\n- Total profit and loss: ~$6.99\n\nStep 2: Latest general crypto news highlights:\n- Bitcoin price surged to $115,000 amid talk of a strong upcoming bull market.\n- Bitcoin Core v30.0 release sparks fork speculation.\n- Tether CEO praises USDT stability during crashes.\n- Binance CEO defends BNB amid manipulation rumors.\n- XRP shows signs of a potentially bullish monthly close supported by whale activity.\n- Solana (SOL) dropped 20% due to correction and geopolitical concerns but shows buying interest.\n- Cardano (ADA) whales buying dips, signaling possible recovery.\n- Stellar (XLM) poised for bullish rally.\n- Grayscale's move may boost an AI-related altcoin.\n- Mental health awareness raised due to a trader's death amid market crash.\n\nStep 3: Technical analysis and strategy summary (1-hour timeframe):\n\nBTC:\n- Price $115,381 above 20 EMA but below 200 EMA.\n- MACD shows weakening bullish momentum (negative histogram and MACD below signal).\n- Stoch RSI moderate (68.5), volume low, ADL recovering from bearish.\n- Strategy: Wait for breakout above 200 EMA with volume to long. Watch for short if price drops below 20 EMA with bearish MACD and volume increase.\n- No trade now due to mixed signals and low volume.\n\nETH:\n- Price $4265 above 20 and 200 EMA; EMA 20 > EMA 200 (bullish).\n- MACD positive, histogram positive.\n- Stoch RSI strong but not extreme (~78).\n- ADL increasing, volume low.\n- Strategy: Maintain/increase long on dips near 20 EMA ~$4187 with tight stops.\n- No immediate new trade; hold current long.\n\nXRP:\n- Price $2.6063 below 200 EMA but above 20 EMA.\n- MACD weakening, negative histogram.\n- Stoch RSI near oversold (~31).\n- ADL stable, volume low.\n- Strategy: Wait for bounce confirmation above 200 EMA and MACD crossover before new long. Current position hold.\n\nADA:\n- Price $0.731 above 20 EMA but below 200 EMA.\n- MACD weak bearish crossover.\n- Stoch RSI neutral.\n- ADL rising, volume low.\n- Strategy: Wait for MACD bullish crossover or price break above 200 EMA for long entry. Otherwise, avoid new trades.\n\nSOL:\n- Price $208 above 20 EMA but slightly below 200 EMA.\n- 20 EMA crossed above 200 EMA (golden cross).\n- MACD bullish.\n- Stoch RSI overbought (~93).\n- ADL up, volume low.\n- Strategy: Possible long on dips to 20 EMA ($201) with tight stop, target recent highs $208-210. Watch overbought RSI for pullback risk.\n\nStep 4: Trade execution:\n- Hold current ETH, BNB, XRP longs.\n- No new trades on BTC, ADA due to unclear signals.\n- No new long on XRP until stronger confirmation.\n- No new long on SOL now; watch for pullback or volume confirmation.\n\nSummary:\n- No new trades executed now due to mixed or cautious signals and low volumes.\n- Continue monitoring for stronger signals especially on BTC, ADA, SOL.\n- Maintain current positions with tight risk management.\n\nNo trade executions at this time."}
|
memory_utils.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# memory_utils.py
|
| 2 |
+
import json
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from typing import Optional, List, Dict, Any
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
MEMORY_FILE = Path("memories.json")
|
| 10 |
+
|
| 11 |
+
def load_memories(n: int = 3) -> list[str]:
|
| 12 |
+
"""
|
| 13 |
+
Load the last n summarized memories from the JSONL file.
|
| 14 |
+
Returns a list of summary strings.
|
| 15 |
+
"""
|
| 16 |
+
if not MEMORY_FILE.exists():
|
| 17 |
+
return []
|
| 18 |
+
with open(MEMORY_FILE, "r", encoding="utf-8") as f:
|
| 19 |
+
lines = [json.loads(l) for l in f if l.strip()]
|
| 20 |
+
return [m["summary"] for m in lines[-n:]]
|
| 21 |
+
|
| 22 |
+
def save_memory(summary: str):
|
| 23 |
+
"""
|
| 24 |
+
Append a new memory summary to the JSONL file.
|
| 25 |
+
Each line is a JSON object: {"timestamp": "...", "summary": "..."}
|
| 26 |
+
"""
|
| 27 |
+
entry = {"timestamp": datetime.now().isoformat(), "summary": summary}
|
| 28 |
+
with open(MEMORY_FILE, "a", encoding="utf-8") as f:
|
| 29 |
+
f.write(json.dumps(entry) + "\n")
|
| 30 |
+
|
| 31 |
+
def load_memories_df(n: Optional[int] = None):
|
| 32 |
+
"""
|
| 33 |
+
Return recent memories as a pandas DataFrame (newest first).
|
| 34 |
+
"""
|
| 35 |
+
with open("memories.json", "r", encoding="utf-8") as f:
|
| 36 |
+
lines = [json.loads(line) for line in f if line.strip()]
|
| 37 |
+
df = pd.DataFrame(lines)
|
| 38 |
+
|
| 39 |
+
return df.tail(n).iloc[::-1] if n else df.iloc[::-1]
|
production.ipynb
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"name": "stderr",
|
| 10 |
+
"output_type": "stream",
|
| 11 |
+
"text": [
|
| 12 |
+
"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
| 13 |
+
" from .autonotebook import tqdm as notebook_tqdm\n"
|
| 14 |
+
]
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"data": {
|
| 18 |
+
"text/plain": [
|
| 19 |
+
"True"
|
| 20 |
+
]
|
| 21 |
+
},
|
| 22 |
+
"execution_count": 1,
|
| 23 |
+
"metadata": {},
|
| 24 |
+
"output_type": "execute_result"
|
| 25 |
+
}
|
| 26 |
+
],
|
| 27 |
+
"source": [
|
| 28 |
+
"import os\n",
|
| 29 |
+
"import asyncio\n",
|
| 30 |
+
"import importlib\n",
|
| 31 |
+
"import hype_accounts_server\n",
|
| 32 |
+
"import gradio as gr\n",
|
| 33 |
+
"import pandas as pd\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"from datetime import datetime\n",
|
| 36 |
+
"from dotenv import load_dotenv\n",
|
| 37 |
+
"from agents import Agent, Runner, trace, Tool\n",
|
| 38 |
+
"from agents.mcp import MCPServerStdio\n",
|
| 39 |
+
"from IPython.display import display, Markdown\n",
|
| 40 |
+
"from memory_utils import load_memories, save_memory, load_memories_df\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"load_dotenv(override=True)"
|
| 43 |
+
]
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"cell_type": "code",
|
| 47 |
+
"execution_count": 2,
|
| 48 |
+
"metadata": {},
|
| 49 |
+
"outputs": [],
|
| 50 |
+
"source": [
|
| 51 |
+
"# --- MCP Server Factories ---\n",
|
| 52 |
+
"def make_hyperliquid_trader_mcp_servers():\n",
|
| 53 |
+
" return [MCPServerStdio(\n",
|
| 54 |
+
" {\"command\": \"python3\", \"args\": [\"-u\", \"hype_accounts_server.py\"], \"env\": {\"HYPERLIQUID_API_KEY\": os.getenv(\"HYPERLIQUID_API_KEY\"), \"HYPERLIQUID_PRIVATE_KEY\": os.getenv(\"HYPERLIQUID_PRIVATE_KEY\"), \"HYPERLIQUID_ACCOUNT_ADDRESS\": os.getenv(\"HYPERLIQUID_ACCOUNT_ADDRESS\")}},\n",
|
| 55 |
+
" client_session_timeout_seconds=30\n",
|
| 56 |
+
" )]\n",
|
| 57 |
+
"\n",
|
| 58 |
+
"def make_crypto_news_mcp_servers():\n",
|
| 59 |
+
" return [MCPServerStdio(\n",
|
| 60 |
+
" {\"command\": \"python3\", \"args\": [\"-u\", \"cryptopanic_news_server.py\"], \"env\": {\"CRYPTOPANIC_API_PLAN\": \"developer\", \"CRYPTOPANIC_API_KEY\": os.getenv(\"CRYPTOPANIC_API_KEY\")}},\n",
|
| 61 |
+
" client_session_timeout_seconds=30\n",
|
| 62 |
+
" )]\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"def make_technical_analyst_mcp_servers():\n",
|
| 65 |
+
" return [MCPServerStdio(\n",
|
| 66 |
+
" {\"command\": \"python3\", \"args\": [\"-u\", \"hl_indicators_server.py\"]},\n",
|
| 67 |
+
" client_session_timeout_seconds=30\n",
|
| 68 |
+
" )]\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"# --- Utilities ---\n",
|
| 71 |
+
"async def connect_all(servers):\n",
|
| 72 |
+
" for s in servers:\n",
|
| 73 |
+
" await s.connect()\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"async def close_all(servers):\n",
|
| 76 |
+
" for s in servers:\n",
|
| 77 |
+
" try:\n",
|
| 78 |
+
" await s.close()\n",
|
| 79 |
+
" except Exception:\n",
|
| 80 |
+
" pass\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"# --- Researcher Tools ---\n",
|
| 83 |
+
"async def get_crypto_news_researcher(mcp_servers) -> Agent:\n",
|
| 84 |
+
" instructions = f\"\"\"You are a cryptocurrency researcher. You have the tools to search for interesting cryptocurrency news,\n",
|
| 85 |
+
" look for possible trading opportunities, and help with research.\n",
|
| 86 |
+
" Default None if there are no specific cryptocurrency news or opportunities in the user request.\n",
|
| 87 |
+
" Otherwise, indicate the symbol of specific cryptocurrency if there are specific requests (e.g. HYPE, BTC, ETH, XRP).\n",
|
| 88 |
+
" Take time to make multiple searches, then summarize findings.\n",
|
| 89 |
+
" If there isn't a specific request, respond with long/short opportunities based on the latest news.\n",
|
| 90 |
+
" The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 91 |
+
" return Agent(name=\"Crypto news researcher\", instructions=instructions, model=\"gpt-4.1-mini\", mcp_servers=mcp_servers)\n",
|
| 92 |
+
"\n",
|
| 93 |
+
"async def get_crypto_news_researcher_tool(mcp_servers) -> Tool:\n",
|
| 94 |
+
" researcher = await get_crypto_news_researcher(mcp_servers)\n",
|
| 95 |
+
" return researcher.as_tool(\n",
|
| 96 |
+
" tool_name=\"crypto_news_researcher\",\n",
|
| 97 |
+
" tool_description=(\"Research cryptocurrency news and opportunities based on a specific coin request \"\n",
|
| 98 |
+
" \"or scan broadly for notable news.\")\n",
|
| 99 |
+
" )\n",
|
| 100 |
+
"\n",
|
| 101 |
+
"async def get_technical_analyst_researcher(mcp_servers) -> Agent:\n",
|
| 102 |
+
" instructions = f\"\"\"You are a cryptocurrency perpetuals technical trading researcher.\n",
|
| 103 |
+
" Default analysis interval: 1h; default lookback: 36.\n",
|
| 104 |
+
" Indicators/strategies:\n",
|
| 105 |
+
" - EMA (20, 200)\n",
|
| 106 |
+
" - MACD (12, 26, 9)\n",
|
| 107 |
+
" - stochastic RSI (14, 14, 3, 3)\n",
|
| 108 |
+
" - Accumulation/Distribution (ADL)\n",
|
| 109 |
+
" - Volume\n",
|
| 110 |
+
" Based on the indicators, find long/short opportunities and propose strategies.\n",
|
| 111 |
+
" The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 112 |
+
" return Agent(name=\"Crypto technical researcher\", instructions=instructions, model=\"gpt-4.1-mini\", mcp_servers=mcp_servers)\n",
|
| 113 |
+
"\n",
|
| 114 |
+
"async def get_technical_analyst_researcher_tool(mcp_servers) -> Tool:\n",
|
| 115 |
+
" # ✅ fix: call the *technical* researcher, not the news one\n",
|
| 116 |
+
" researcher = await get_technical_analyst_researcher(mcp_servers)\n",
|
| 117 |
+
" return researcher.as_tool(\n",
|
| 118 |
+
" tool_name=\"crypto_technical_researcher\",\n",
|
| 119 |
+
" tool_description=(\"Research technical indicators (EMA, MACD, Stoch RSI, ADL, Volume) for trading opportunities. \"\n",
|
| 120 |
+
" \"Specify coin/interval/lookback if needed.\")\n",
|
| 121 |
+
" )"
|
| 122 |
+
]
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"cell_type": "code",
|
| 126 |
+
"execution_count": null,
|
| 127 |
+
"metadata": {},
|
| 128 |
+
"outputs": [],
|
| 129 |
+
"source": [
|
| 130 |
+
"# --- Main Trader Logic ---\n",
|
| 131 |
+
"async def run_trader():\n",
|
| 132 |
+
" hyper_servers = []; news_servers = []; ta_servers = []\n",
|
| 133 |
+
" try:\n",
|
| 134 |
+
" print(f\"\\n🕒 Starting trading cycle at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n",
|
| 135 |
+
" # Load memory\n",
|
| 136 |
+
" past_memories = load_memories(1)\n",
|
| 137 |
+
" memory_text = \"\\n\".join(past_memories) if past_memories else \"No prior memories.\"\n",
|
| 138 |
+
" account_details = await hype_accounts_server.get_account_details()\n",
|
| 139 |
+
"\n",
|
| 140 |
+
" instructions = f\"\"\"\n",
|
| 141 |
+
" You are a cryptocurrency perpetuals trader. \n",
|
| 142 |
+
" Here are your past insights:\n",
|
| 143 |
+
" {memory_text}\n",
|
| 144 |
+
" Your current holdings and balance is:\n",
|
| 145 |
+
" {account_details}\n",
|
| 146 |
+
" You have tools for crypto news, hourly technical indicators, holdings/balance, and execution on Hyperliquid.\n",
|
| 147 |
+
" Carry out trades as you see fit — take profits and cut losses as needed.\n",
|
| 148 |
+
" Do not wait for instructions or ask for confirmation.\n",
|
| 149 |
+
" Each transaction costs 0.04%.\n",
|
| 150 |
+
" However, do not execute any trades if market signals are unclear or you are not confident in the setup.\n",
|
| 151 |
+
" \"\"\"\n",
|
| 152 |
+
"\n",
|
| 153 |
+
" prompt = f\"\"\"\n",
|
| 154 |
+
" Step 1: Check current holdings and balance.\n",
|
| 155 |
+
" Step 2: Research general latest crypto news.\n",
|
| 156 |
+
" Step 3: Select 3–5 cryptocurrencies based on latest news and current holdings. Analyze price + hourly indicators (EMA20/200, MACD 12-26-9, StochRSI 14-14-3-3, ADL, Volume); propose strategies.\n",
|
| 157 |
+
" Step 4: Execute long/short if edge exists; otherwise abstain. Carry out trades as you see fit; do not wait for instructions or ask for confirmation.\n",
|
| 158 |
+
" Return: research, indicator values, strategy, executions (if any).\n",
|
| 159 |
+
" Now: {datetime.now().strftime(\"%Y-%m-%d %H:%M\")}\n",
|
| 160 |
+
" \"\"\"\n",
|
| 161 |
+
"\n",
|
| 162 |
+
" # Fresh servers each run\n",
|
| 163 |
+
" hyper_servers = make_hyperliquid_trader_mcp_servers()\n",
|
| 164 |
+
" news_servers = make_crypto_news_mcp_servers()\n",
|
| 165 |
+
" ta_servers = make_technical_analyst_mcp_servers()\n",
|
| 166 |
+
"\n",
|
| 167 |
+
" await connect_all(hyper_servers)\n",
|
| 168 |
+
" await connect_all(news_servers)\n",
|
| 169 |
+
" await connect_all(ta_servers)\n",
|
| 170 |
+
"\n",
|
| 171 |
+
" # Bind tools to *their* servers\n",
|
| 172 |
+
" crypto_news_tool = await get_crypto_news_researcher_tool(news_servers)\n",
|
| 173 |
+
" ta_tool = await get_technical_analyst_researcher_tool(ta_servers)\n",
|
| 174 |
+
"\n",
|
| 175 |
+
" trader = Agent(\n",
|
| 176 |
+
" name=\"crypto_trader\",\n",
|
| 177 |
+
" instructions=instructions,\n",
|
| 178 |
+
" tools=[crypto_news_tool, ta_tool],\n",
|
| 179 |
+
" mcp_servers=hyper_servers, # servers that expose trading actions\n",
|
| 180 |
+
" model=\"gpt-4.1-mini\",\n",
|
| 181 |
+
" )\n",
|
| 182 |
+
"\n",
|
| 183 |
+
" async def _run():\n",
|
| 184 |
+
" with trace(\"crypto_trader\"):\n",
|
| 185 |
+
" return await Runner.run(trader, prompt, max_turns=30)\n",
|
| 186 |
+
"\n",
|
| 187 |
+
" # Hard timeout so a wedged tool doesn't block the UI\n",
|
| 188 |
+
" result = await asyncio.wait_for(_run(), timeout=120)\n",
|
| 189 |
+
"\n",
|
| 190 |
+
" save_memory(result.final_output)\n",
|
| 191 |
+
" mem_df = load_memories_df(10) if len(load_memories(1)) else pd.DataFrame(columns=[\"ts\",\"summary\"])\n",
|
| 192 |
+
" output = f\"\"\"### ✅ Trade Completed {datetime.now().strftime(\"%Y-%m-%d %H:%M\")}\\n\\n{result.final_output}\"\"\"\n",
|
| 193 |
+
" return output, mem_df\n",
|
| 194 |
+
"\n",
|
| 195 |
+
" except asyncio.TimeoutError:\n",
|
| 196 |
+
" return \"❌ Timed out (120s). Consider raising timeout or reducing tool work per cycle.\", pd.DataFrame(columns=[\"ts\",\"summary\"])\n",
|
| 197 |
+
" except Exception as e:\n",
|
| 198 |
+
" return f\"❌ Error during trading cycle: {e}\", pd.DataFrame(columns=[\"ts\",\"summary\"])\n",
|
| 199 |
+
" finally:\n",
|
| 200 |
+
" await close_all(hyper_servers)\n",
|
| 201 |
+
" await close_all(news_servers)\n",
|
| 202 |
+
" await close_all(ta_servers)\n"
|
| 203 |
+
]
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"cell_type": "code",
|
| 207 |
+
"execution_count": null,
|
| 208 |
+
"metadata": {},
|
| 209 |
+
"outputs": [
|
| 210 |
+
{
|
| 211 |
+
"name": "stdout",
|
| 212 |
+
"output_type": "stream",
|
| 213 |
+
"text": [
|
| 214 |
+
"* Running on local URL: http://127.0.0.1:7861\n"
|
| 215 |
+
]
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"data": {
|
| 219 |
+
"text/html": [
|
| 220 |
+
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[10/13/25 23:57:02] </span><span style=\"color: #000080; text-decoration-color: #000080\">INFO </span> HTTP Request: <span style=\"color: #808000; text-decoration-color: #808000; font-weight: bold\">GET</span> <span style=\"color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline\">http://127.0.0.1:7861/gradio_api/startup-events</span> <a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">_client.py</span></a><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">:</span><a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">1025</span></a>\n",
|
| 221 |
+
"<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\"> </span> <span style=\"color: #008000; text-decoration-color: #008000\">\"HTTP/1.1 200 OK\"</span> <span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\"> </span>\n",
|
| 222 |
+
"</pre>\n"
|
| 223 |
+
],
|
| 224 |
+
"text/plain": [
|
| 225 |
+
"\u001b[2;36m[10/13/25 23:57:02]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m HTTP Request: \u001b[1;33mGET\u001b[0m \u001b[4;94mhttp://127.0.0.1:7861/gradio_api/startup-events\u001b[0m \u001b]8;id=172711;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\u001b\\\u001b[2m_client.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=992343;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\u001b\\\u001b[2m1025\u001b[0m\u001b]8;;\u001b\\\n",
|
| 226 |
+
"\u001b[2;36m \u001b[0m \u001b[32m\"HTTP/1.1 200 OK\"\u001b[0m \u001b[2m \u001b[0m\n"
|
| 227 |
+
]
|
| 228 |
+
},
|
| 229 |
+
"metadata": {},
|
| 230 |
+
"output_type": "display_data"
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"data": {
|
| 234 |
+
"text/html": [
|
| 235 |
+
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\"> </span><span style=\"color: #000080; text-decoration-color: #000080\">INFO </span> HTTP Request: <span style=\"color: #808000; text-decoration-color: #808000; font-weight: bold\">HEAD</span> <span style=\"color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline\">http://127.0.0.1:7861/</span> <span style=\"color: #008000; text-decoration-color: #008000\">\"HTTP/1.1 200 OK\"</span> <a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">_client.py</span></a><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">:</span><a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">1025</span></a>\n",
|
| 236 |
+
"</pre>\n"
|
| 237 |
+
],
|
| 238 |
+
"text/plain": [
|
| 239 |
+
"\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m HTTP Request: \u001b[1;33mHEAD\u001b[0m \u001b[4;94mhttp://127.0.0.1:7861/\u001b[0m \u001b[32m\"HTTP/1.1 200 OK\"\u001b[0m \u001b]8;id=174034;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\u001b\\\u001b[2m_client.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=851874;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\u001b\\\u001b[2m1025\u001b[0m\u001b]8;;\u001b\\\n"
|
| 240 |
+
]
|
| 241 |
+
},
|
| 242 |
+
"metadata": {},
|
| 243 |
+
"output_type": "display_data"
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"name": "stderr",
|
| 247 |
+
"output_type": "stream",
|
| 248 |
+
"text": [
|
| 249 |
+
"--- Logging error ---\n",
|
| 250 |
+
"Traceback (most recent call last):\n",
|
| 251 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/logging.py\", line 178, in emit\n",
|
| 252 |
+
" self.console.print(log_renderable)\n",
|
| 253 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 1697, in print\n",
|
| 254 |
+
" with self:\n",
|
| 255 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 870, in __exit__\n",
|
| 256 |
+
" self._exit_buffer()\n",
|
| 257 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 826, in _exit_buffer\n",
|
| 258 |
+
" self._check_buffer()\n",
|
| 259 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 2038, in _check_buffer\n",
|
| 260 |
+
" self._write_buffer()\n",
|
| 261 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 2054, in _write_buffer\n",
|
| 262 |
+
" display(self._buffer, self._render_buffer(self._buffer[:]))\n",
|
| 263 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/jupyter.py\", line 91, in display\n",
|
| 264 |
+
" ipython_display(jupyter_renderable)\n",
|
| 265 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/IPython/core/display_functions.py\", line 285, in display\n",
|
| 266 |
+
" publish_display_data(data=format_dict, metadata=md_dict, **kwargs)\n",
|
| 267 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/IPython/core/display_functions.py\", line 73, in publish_display_data\n",
|
| 268 |
+
" display_pub.publish(\n",
|
| 269 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/ipykernel/zmqshell.py\", line 135, in publish\n",
|
| 270 |
+
" msg = self.session.msg(msg_type, json_clean(content), parent=self.parent_header)\n",
|
| 271 |
+
" ^^^^^^^^^^^^^^^^^^\n",
|
| 272 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/ipykernel/zmqshell.py\", line 69, in parent_header\n",
|
| 273 |
+
" return self._parent_header.get()\n",
|
| 274 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 275 |
+
"LookupError: <ContextVar name='parent_header' at 0x10dec1cb0>\n",
|
| 276 |
+
"Call stack:\n",
|
| 277 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py\", line 1002, in _bootstrap\n",
|
| 278 |
+
" self._bootstrap_inner()\n",
|
| 279 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py\", line 1045, in _bootstrap_inner\n",
|
| 280 |
+
" self.run()\n",
|
| 281 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/ipykernel/ipkernel.py\", line 788, in run_closure\n",
|
| 282 |
+
" _threading_Thread_run(self)\n",
|
| 283 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py\", line 982, in run\n",
|
| 284 |
+
" self._target(*self._args, **self._kwargs)\n",
|
| 285 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/gradio/analytics.py\", line 91, in version_check\n",
|
| 286 |
+
" latest_pkg_version = httpx.get(url=PKG_VERSION_URL, timeout=3).json()[\"version\"]\n",
|
| 287 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_api.py\", line 195, in get\n",
|
| 288 |
+
" return request(\n",
|
| 289 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_api.py\", line 109, in request\n",
|
| 290 |
+
" return client.request(\n",
|
| 291 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\", line 825, in request\n",
|
| 292 |
+
" return self.send(request, auth=auth, follow_redirects=follow_redirects)\n",
|
| 293 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\", line 914, in send\n",
|
| 294 |
+
" response = self._send_handling_auth(\n",
|
| 295 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\", line 942, in _send_handling_auth\n",
|
| 296 |
+
" response = self._send_handling_redirects(\n",
|
| 297 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\", line 979, in _send_handling_redirects\n",
|
| 298 |
+
" response = self._send_single_request(request)\n",
|
| 299 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\", line 1025, in _send_single_request\n",
|
| 300 |
+
" logger.info(\n",
|
| 301 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1489, in info\n",
|
| 302 |
+
" self._log(INFO, msg, args, **kwargs)\n",
|
| 303 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1634, in _log\n",
|
| 304 |
+
" self.handle(record)\n",
|
| 305 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1644, in handle\n",
|
| 306 |
+
" self.callHandlers(record)\n",
|
| 307 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1706, in callHandlers\n",
|
| 308 |
+
" hdlr.handle(record)\n",
|
| 309 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 978, in handle\n",
|
| 310 |
+
" self.emit(record)\n",
|
| 311 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/logging.py\", line 180, in emit\n",
|
| 312 |
+
" self.handleError(record)\n",
|
| 313 |
+
"Message: 'HTTP Request: %s %s \"%s %d %s\"'\n",
|
| 314 |
+
"Arguments: ('GET', URL('https://api.gradio.app/pkg-version'), 'HTTP/1.1', 200, 'OK')\n"
|
| 315 |
+
]
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"data": {
|
| 319 |
+
"text/html": [
|
| 320 |
+
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[10/13/25 23:57:03] </span><span style=\"color: #000080; text-decoration-color: #000080\">INFO </span> HTTP Request: <span style=\"color: #808000; text-decoration-color: #808000; font-weight: bold\">GET</span> <span style=\"color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline\">https://api.gradio.app/v3/tunnel-request</span> <span style=\"color: #008000; text-decoration-color: #008000\">\"HTTP/1.1 </span> <a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">_client.py</span></a><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">:</span><a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">1025</span></a>\n",
|
| 321 |
+
"<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\"> </span> <span style=\"color: #008000; text-decoration-color: #008000\">200 OK\"</span> <span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\"> </span>\n",
|
| 322 |
+
"</pre>\n"
|
| 323 |
+
],
|
| 324 |
+
"text/plain": [
|
| 325 |
+
"\u001b[2;36m[10/13/25 23:57:03]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m HTTP Request: \u001b[1;33mGET\u001b[0m \u001b[4;94mhttps://api.gradio.app/v3/tunnel-request\u001b[0m \u001b[32m\"HTTP/1.1 \u001b[0m \u001b]8;id=299345;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\u001b\\\u001b[2m_client.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=555061;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\u001b\\\u001b[2m1025\u001b[0m\u001b]8;;\u001b\\\n",
|
| 326 |
+
"\u001b[2;36m \u001b[0m \u001b[32m200 OK\"\u001b[0m \u001b[2m \u001b[0m\n"
|
| 327 |
+
]
|
| 328 |
+
},
|
| 329 |
+
"metadata": {},
|
| 330 |
+
"output_type": "display_data"
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"name": "stdout",
|
| 334 |
+
"output_type": "stream",
|
| 335 |
+
"text": [
|
| 336 |
+
"* Running on public URL: https://85810c0a582ea7122c.gradio.live\n",
|
| 337 |
+
"\n",
|
| 338 |
+
"This share link expires in 1 week. For free permanent hosting and GPU upgrades, run `gradio deploy` from the terminal in the working directory to deploy to Hugging Face Spaces (https://huggingface.co/spaces)\n"
|
| 339 |
+
]
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"data": {
|
| 343 |
+
"text/html": [
|
| 344 |
+
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\">[10/13/25 23:57:05] </span><span style=\"color: #000080; text-decoration-color: #000080\">INFO </span> HTTP Request: <span style=\"color: #808000; text-decoration-color: #808000; font-weight: bold\">HEAD</span> <span style=\"color: #0000ff; text-decoration-color: #0000ff; text-decoration: underline\">https://85810c0a582ea7122c.gradio.live</span> <span style=\"color: #008000; text-decoration-color: #008000\">\"HTTP/1.1 </span> <a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">_client.py</span></a><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">:</span><a href=\"file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\" target=\"_blank\"><span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\">1025</span></a>\n",
|
| 345 |
+
"<span style=\"color: #7fbfbf; text-decoration-color: #7fbfbf\"> </span> <span style=\"color: #008000; text-decoration-color: #008000\">200 OK\"</span> <span style=\"color: #7f7f7f; text-decoration-color: #7f7f7f\"> </span>\n",
|
| 346 |
+
"</pre>\n"
|
| 347 |
+
],
|
| 348 |
+
"text/plain": [
|
| 349 |
+
"\u001b[2;36m[10/13/25 23:57:05]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m HTTP Request: \u001b[1;33mHEAD\u001b[0m \u001b[4;94mhttps://85810c0a582ea7122c.gradio.live\u001b[0m \u001b[32m\"HTTP/1.1 \u001b[0m \u001b]8;id=92393;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py\u001b\\\u001b[2m_client.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=538708;file:///Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/httpx/_client.py#1025\u001b\\\u001b[2m1025\u001b[0m\u001b]8;;\u001b\\\n",
|
| 350 |
+
"\u001b[2;36m \u001b[0m \u001b[32m200 OK\"\u001b[0m \u001b[2m \u001b[0m\n"
|
| 351 |
+
]
|
| 352 |
+
},
|
| 353 |
+
"metadata": {},
|
| 354 |
+
"output_type": "display_data"
|
| 355 |
+
},
|
| 356 |
+
{
|
| 357 |
+
"data": {
|
| 358 |
+
"text/html": [
|
| 359 |
+
"<div><iframe src=\"https://85810c0a582ea7122c.gradio.live\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
|
| 360 |
+
],
|
| 361 |
+
"text/plain": [
|
| 362 |
+
"<IPython.core.display.HTML object>"
|
| 363 |
+
]
|
| 364 |
+
},
|
| 365 |
+
"metadata": {},
|
| 366 |
+
"output_type": "display_data"
|
| 367 |
+
},
|
| 368 |
+
{
|
| 369 |
+
"name": "stdout",
|
| 370 |
+
"output_type": "stream",
|
| 371 |
+
"text": [
|
| 372 |
+
"[23:57:12] 💓 🟡 Idle\n",
|
| 373 |
+
"[23:57:17] 💓 🟡 Idle\n",
|
| 374 |
+
"[23:57:22] 💓 🟡 Idle\n",
|
| 375 |
+
"[23:57:27] 💓 🟡 Idle\n",
|
| 376 |
+
"[23:57:32] 💓 🟡 Idle\n",
|
| 377 |
+
"[23:57:37] 💓 🟡 Idle\n",
|
| 378 |
+
"[23:57:42] 💓 🟡 Idle\n",
|
| 379 |
+
"[23:57:47] 💓 🟡 Idle\n",
|
| 380 |
+
"[23:57:52] 💓 🟡 Idle\n",
|
| 381 |
+
"[23:57:57] 💓 🟡 Idle\n",
|
| 382 |
+
"[23:58:02] 💓 🟡 Idle\n",
|
| 383 |
+
"[23:58:07] 💓 🟡 Idle\n",
|
| 384 |
+
"[23:58:12] 💓 🟡 Idle\n",
|
| 385 |
+
"[23:58:17] 💓 🟡 Idle\n",
|
| 386 |
+
"[23:58:22] 💓 🟡 Idle\n",
|
| 387 |
+
"[23:58:27] 💓 🟡 Idle\n",
|
| 388 |
+
"[23:58:32] 💓 🟡 Idle\n",
|
| 389 |
+
"[23:58:37] 💓 🟡 Idle\n",
|
| 390 |
+
"[23:58:42] 💓 🟡 Idle\n",
|
| 391 |
+
"[23:58:47] 💓 🟡 Idle\n",
|
| 392 |
+
"[23:58:52] 💓 🟡 Idle\n",
|
| 393 |
+
"[23:58:57] 💓 🟡 Idle\n",
|
| 394 |
+
"[23:59:02] 💓 🟡 Idle\n",
|
| 395 |
+
"[23:59:07] 💓 🟡 Idle\n",
|
| 396 |
+
"[23:59:12] 💓 🟡 Idle\n",
|
| 397 |
+
"[23:59:17] 💓 🟡 Idle\n",
|
| 398 |
+
"[23:59:22] 💓 🟡 Idle\n",
|
| 399 |
+
"[23:59:27] 💓 🟡 Idle\n",
|
| 400 |
+
"[23:59:32] 💓 🟡 Idle\n",
|
| 401 |
+
"[23:59:37] 💓 🟡 Idle\n",
|
| 402 |
+
"[23:59:42] 💓 🟡 Idle\n",
|
| 403 |
+
"[23:59:47] 💓 🟡 Idle\n",
|
| 404 |
+
"[23:59:52] 💓 🟡 Idle\n",
|
| 405 |
+
"[23:59:57] 💓 🟡 Idle\n"
|
| 406 |
+
]
|
| 407 |
+
},
|
| 408 |
+
{
|
| 409 |
+
"name": "stderr",
|
| 410 |
+
"output_type": "stream",
|
| 411 |
+
"text": [
|
| 412 |
+
"--- Logging error ---\n",
|
| 413 |
+
"Traceback (most recent call last):\n",
|
| 414 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/logging.py\", line 178, in emit\n",
|
| 415 |
+
" self.console.print(log_renderable)\n",
|
| 416 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 1697, in print\n",
|
| 417 |
+
" with self:\n",
|
| 418 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 870, in __exit__\n",
|
| 419 |
+
" self._exit_buffer()\n",
|
| 420 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 826, in _exit_buffer\n",
|
| 421 |
+
" self._check_buffer()\n",
|
| 422 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 2038, in _check_buffer\n",
|
| 423 |
+
" self._write_buffer()\n",
|
| 424 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/console.py\", line 2054, in _write_buffer\n",
|
| 425 |
+
" display(self._buffer, self._render_buffer(self._buffer[:]))\n",
|
| 426 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/jupyter.py\", line 91, in display\n",
|
| 427 |
+
" ipython_display(jupyter_renderable)\n",
|
| 428 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/IPython/core/display_functions.py\", line 285, in display\n",
|
| 429 |
+
" publish_display_data(data=format_dict, metadata=md_dict, **kwargs)\n",
|
| 430 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/IPython/core/display_functions.py\", line 73, in publish_display_data\n",
|
| 431 |
+
" display_pub.publish(\n",
|
| 432 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/ipykernel/zmqshell.py\", line 135, in publish\n",
|
| 433 |
+
" msg = self.session.msg(msg_type, json_clean(content), parent=self.parent_header)\n",
|
| 434 |
+
" ^^^^^^^^^^^^^^^^^^\n",
|
| 435 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/ipykernel/zmqshell.py\", line 69, in parent_header\n",
|
| 436 |
+
" return self._parent_header.get()\n",
|
| 437 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 438 |
+
"LookupError: <ContextVar name='parent_header' at 0x10dec1cb0>\n",
|
| 439 |
+
"Call stack:\n",
|
| 440 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py\", line 1002, in _bootstrap\n",
|
| 441 |
+
" self._bootstrap_inner()\n",
|
| 442 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py\", line 1045, in _bootstrap_inner\n",
|
| 443 |
+
" self.run()\n",
|
| 444 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/hyperliquid/websocket_manager.py\", line 91, in run\n",
|
| 445 |
+
" self.ws.run_forever()\n",
|
| 446 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/websocket/_app.py\", line 556, in run_forever\n",
|
| 447 |
+
" initialize_socket()\n",
|
| 448 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/websocket/_app.py\", line 422, in initialize_socket\n",
|
| 449 |
+
" _logging.info(\"Websocket connected\")\n",
|
| 450 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/websocket/_logging.py\", line 89, in info\n",
|
| 451 |
+
" _logger.info(msg)\n",
|
| 452 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1489, in info\n",
|
| 453 |
+
" self._log(INFO, msg, args, **kwargs)\n",
|
| 454 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1634, in _log\n",
|
| 455 |
+
" self.handle(record)\n",
|
| 456 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1644, in handle\n",
|
| 457 |
+
" self.callHandlers(record)\n",
|
| 458 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 1706, in callHandlers\n",
|
| 459 |
+
" hdlr.handle(record)\n",
|
| 460 |
+
" File \"/usr/local/Cellar/python@3.11/3.11.14/Frameworks/Python.framework/Versions/3.11/lib/python3.11/logging/__init__.py\", line 978, in handle\n",
|
| 461 |
+
" self.emit(record)\n",
|
| 462 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/rich/logging.py\", line 180, in emit\n",
|
| 463 |
+
" self.handleError(record)\n",
|
| 464 |
+
"Message: 'Websocket connected'\n",
|
| 465 |
+
"Arguments: ()\n"
|
| 466 |
+
]
|
| 467 |
+
},
|
| 468 |
+
{
|
| 469 |
+
"name": "stdout",
|
| 470 |
+
"output_type": "stream",
|
| 471 |
+
"text": [
|
| 472 |
+
"[00:00:02] 💓 🟢 Running\n",
|
| 473 |
+
"[00:00:07] 💓 🟢 Running\n",
|
| 474 |
+
"[00:00:12] 💓 🟢 Running\n",
|
| 475 |
+
"[00:00:17] 💓 🟢 Running\n",
|
| 476 |
+
"[00:00:22] 💓 🟢 Running\n",
|
| 477 |
+
"[00:00:27] 💓 🟢 Running\n",
|
| 478 |
+
"[00:00:32] 💓 🟢 Running\n",
|
| 479 |
+
"[00:00:37] 💓 🟢 Running\n",
|
| 480 |
+
"[00:00:42] 💓 🟢 Running\n",
|
| 481 |
+
"[00:00:47] 💓 🟢 Running\n",
|
| 482 |
+
"[00:00:52] 💓 🟢 Running\n",
|
| 483 |
+
"[00:00:57] 💓 🟢 Running\n",
|
| 484 |
+
"[00:01:02] 💓 🟢 Running\n",
|
| 485 |
+
"[00:01:07] 💓 🟢 Running\n",
|
| 486 |
+
"[00:01:12] 💓 🟡 Idle\n",
|
| 487 |
+
"[00:01:17] 💓 🟡 Idle\n",
|
| 488 |
+
"[00:01:22] 💓 🟡 Idle\n",
|
| 489 |
+
"[00:01:27] 💓 🟡 Idle\n",
|
| 490 |
+
"[00:01:32] 💓 🟡 Idle\n",
|
| 491 |
+
"[00:01:37] 💓 🟡 Idle\n",
|
| 492 |
+
"[00:01:42] 💓 🟡 Idle\n",
|
| 493 |
+
"[00:01:47] 💓 🟡 Idle\n",
|
| 494 |
+
"[00:01:52] 💓 🟡 Idle\n",
|
| 495 |
+
"[00:01:57] 💓 🟡 Idle\n",
|
| 496 |
+
"[00:02:02] 💓 🟡 Idle\n",
|
| 497 |
+
"[00:02:07] 💓 🟡 Idle\n",
|
| 498 |
+
"[00:02:12] 💓 🟡 Idle\n",
|
| 499 |
+
"[00:02:17] 💓 🟡 Idle\n",
|
| 500 |
+
"[00:02:22] 💓 🟡 Idle\n",
|
| 501 |
+
"[00:02:27] 💓 🟡 Idle\n",
|
| 502 |
+
"[00:02:32] 💓 🟡 Idle\n",
|
| 503 |
+
"[00:02:37] 💓 🟡 Idle\n",
|
| 504 |
+
"[00:02:42] 💓 🟡 Idle\n",
|
| 505 |
+
"[00:02:47] 💓 🟡 Idle\n",
|
| 506 |
+
"[00:02:52] 💓 🟡 Idle\n",
|
| 507 |
+
"[00:02:57] 💓 🟡 Idle\n",
|
| 508 |
+
"[00:03:02] 💓 🟡 Idle\n",
|
| 509 |
+
"[00:03:07] 💓 🟡 Idle\n",
|
| 510 |
+
"[00:03:12] 💓 🟡 Idle\n",
|
| 511 |
+
"[00:03:17] 💓 🟡 Idle\n",
|
| 512 |
+
"[00:03:22] 💓 🟡 Idle\n",
|
| 513 |
+
"[00:03:27] 💓 🟡 Idle\n",
|
| 514 |
+
"[00:03:32] 💓 🟡 Idle\n",
|
| 515 |
+
"[00:03:37] 💓 🟡 Idle\n",
|
| 516 |
+
"[00:03:42] 💓 🟡 Idle\n",
|
| 517 |
+
"[00:03:47] 💓 🟡 Idle\n",
|
| 518 |
+
"[00:03:52] 💓 🟡 Idle\n",
|
| 519 |
+
"[00:03:57] 💓 🟡 Idle\n",
|
| 520 |
+
"[00:04:02] 💓 🟡 Idle\n",
|
| 521 |
+
"[00:04:07] 💓 🟡 Idle\n",
|
| 522 |
+
"[00:04:12] 💓 🟡 Idle\n",
|
| 523 |
+
"[00:04:17] 💓 🟡 Idle\n",
|
| 524 |
+
"[00:04:22] 💓 🟡 Idle\n",
|
| 525 |
+
"[00:04:27] 💓 🟡 Idle\n",
|
| 526 |
+
"[00:04:32] 💓 🟡 Idle\n",
|
| 527 |
+
"[00:04:37] 💓 🟡 Idle\n",
|
| 528 |
+
"[00:04:42] 💓 🟡 Idle\n",
|
| 529 |
+
"[00:04:47] 💓 🟡 Idle\n",
|
| 530 |
+
"[00:04:52] 💓 🟡 Idle\n",
|
| 531 |
+
"[00:04:57] 💓 🟡 Idle\n"
|
| 532 |
+
]
|
| 533 |
+
}
|
| 534 |
+
],
|
| 535 |
+
"source": [
|
| 536 |
+
"import asyncio\n",
|
| 537 |
+
"from datetime import datetime, timedelta\n",
|
| 538 |
+
"import pandas as pd\n",
|
| 539 |
+
"import gradio as gr\n",
|
| 540 |
+
"import os\n",
|
| 541 |
+
"import tempfile\n",
|
| 542 |
+
"import traceback\n",
|
| 543 |
+
"\n",
|
| 544 |
+
"# from your_module import run_trader\n",
|
| 545 |
+
"\n",
|
| 546 |
+
"LATEST_OUTPUT = \"⏳ Waiting for first scheduled run…\"\n",
|
| 547 |
+
"LATEST_MEM_DF = pd.DataFrame(columns=[\"ts\", \"summary\"])\n",
|
| 548 |
+
"NEXT_RUN_AT = None\n",
|
| 549 |
+
"IS_RUNNING = False\n",
|
| 550 |
+
"LAST_RUN_STARTED_AT = None\n",
|
| 551 |
+
"LAST_RUN_ENDED_AT = None\n",
|
| 552 |
+
"\n",
|
| 553 |
+
"_STATE_LOCK = asyncio.Lock()\n",
|
| 554 |
+
"_SCHEDULER_STARTED = False\n",
|
| 555 |
+
"_RUN_LOCK = asyncio.Lock()\n",
|
| 556 |
+
"\n",
|
| 557 |
+
"HEARTBEAT_SECS = 5\n",
|
| 558 |
+
"MAX_RUN_SECS = 110\n",
|
| 559 |
+
"CSV_PATH = os.path.join(tempfile.gettempdir(), \"memories_latest.csv\")\n",
|
| 560 |
+
"\n",
|
| 561 |
+
"# ✅ NEW\n",
|
| 562 |
+
"STOP_TRADING = False\n",
|
| 563 |
+
"\n",
|
| 564 |
+
"\n",
|
| 565 |
+
"def now(): return datetime.now()\n",
|
| 566 |
+
"\n",
|
| 567 |
+
"def next_half_hour_dt(t=None):\n",
|
| 568 |
+
" t = t or now()\n",
|
| 569 |
+
" if t.minute < 30:\n",
|
| 570 |
+
" return t.replace(minute=30, second=0, microsecond=0)\n",
|
| 571 |
+
" return (t.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1))\n",
|
| 572 |
+
"\n",
|
| 573 |
+
"def eta_str(target):\n",
|
| 574 |
+
" secs = max(0, int((target - now()).total_seconds()))\n",
|
| 575 |
+
" m, s = divmod(secs, 60)\n",
|
| 576 |
+
" return f\"{m:02d}:{s:02d}\"\n",
|
| 577 |
+
"\n",
|
| 578 |
+
"\n",
|
| 579 |
+
"async def safe_run_trader():\n",
|
| 580 |
+
" global LATEST_OUTPUT, LATEST_MEM_DF, IS_RUNNING, LAST_RUN_STARTED_AT, LAST_RUN_ENDED_AT\n",
|
| 581 |
+
" if _RUN_LOCK.locked() or STOP_TRADING:\n",
|
| 582 |
+
" return\n",
|
| 583 |
+
"\n",
|
| 584 |
+
" async with _RUN_LOCK:\n",
|
| 585 |
+
" IS_RUNNING = True\n",
|
| 586 |
+
" LAST_RUN_STARTED_AT = now()\n",
|
| 587 |
+
" try:\n",
|
| 588 |
+
" md, mem_df = await asyncio.wait_for(run_trader(), timeout=MAX_RUN_SECS)\n",
|
| 589 |
+
" async with _STATE_LOCK:\n",
|
| 590 |
+
" LATEST_OUTPUT = md or \"(no output)\"\n",
|
| 591 |
+
" if isinstance(mem_df, pd.DataFrame) and not mem_df.empty:\n",
|
| 592 |
+
" LATEST_MEM_DF = mem_df.copy()\n",
|
| 593 |
+
" else:\n",
|
| 594 |
+
" stamp = now().strftime(\"%Y-%m-%d %H:%M:%S\")\n",
|
| 595 |
+
" extra = pd.DataFrame([{\"ts\": stamp, \"summary\": \"✅ Run completed\"}])\n",
|
| 596 |
+
" LATEST_MEM_DF = pd.concat([LATEST_MEM_DF, extra], ignore_index=True)\n",
|
| 597 |
+
" except Exception:\n",
|
| 598 |
+
" err = traceback.format_exc()\n",
|
| 599 |
+
" async with _STATE_LOCK:\n",
|
| 600 |
+
" LATEST_OUTPUT = f\"❌ Exception\\n```\\n{err}\\n```\"\n",
|
| 601 |
+
" finally:\n",
|
| 602 |
+
" LAST_RUN_ENDED_AT = now()\n",
|
| 603 |
+
" IS_RUNNING = False\n",
|
| 604 |
+
"\n",
|
| 605 |
+
"\n",
|
| 606 |
+
"async def halfhour_scheduler():\n",
|
| 607 |
+
" global NEXT_RUN_AT\n",
|
| 608 |
+
" NEXT_RUN_AT = next_half_hour_dt()\n",
|
| 609 |
+
" await asyncio.sleep(max(0.0, (NEXT_RUN_AT - now()).total_seconds()))\n",
|
| 610 |
+
" while True:\n",
|
| 611 |
+
" if not STOP_TRADING:\n",
|
| 612 |
+
" await safe_run_trader()\n",
|
| 613 |
+
" NEXT_RUN_AT = next_half_hour_dt()\n",
|
| 614 |
+
" else:\n",
|
| 615 |
+
" NEXT_RUN_AT = None\n",
|
| 616 |
+
" await asyncio.sleep(HEARTBEAT_SECS if STOP_TRADING else\n",
|
| 617 |
+
" max(0.0, (NEXT_RUN_AT - now()).total_seconds()))\n",
|
| 618 |
+
"\n",
|
| 619 |
+
"\n",
|
| 620 |
+
"async def heartbeat():\n",
|
| 621 |
+
" while True:\n",
|
| 622 |
+
" state = \"⏸️ Paused\" if STOP_TRADING else (\"🟢 Running\" if IS_RUNNING else \"🟡 Idle\")\n",
|
| 623 |
+
" print(f\"[{now():%H:%M:%S}] 💓 {state}\")\n",
|
| 624 |
+
" await asyncio.sleep(HEARTBEAT_SECS)\n",
|
| 625 |
+
"\n",
|
| 626 |
+
"\n",
|
| 627 |
+
"def write_mem_csv(df):\n",
|
| 628 |
+
" if df is None or df.empty:\n",
|
| 629 |
+
" df = pd.DataFrame(columns=[\"ts\", \"summary\"])\n",
|
| 630 |
+
" df.to_csv(CSV_PATH, index=False)\n",
|
| 631 |
+
" return CSV_PATH\n",
|
| 632 |
+
"\n",
|
| 633 |
+
"\n",
|
| 634 |
+
"async def ensure_scheduler_started():\n",
|
| 635 |
+
" global _SCHEDULER_STARTED\n",
|
| 636 |
+
" if not _SCHEDULER_STARTED:\n",
|
| 637 |
+
" _SCHEDULER_STARTED = True\n",
|
| 638 |
+
" asyncio.create_task(heartbeat())\n",
|
| 639 |
+
" asyncio.create_task(halfhour_scheduler())\n",
|
| 640 |
+
"\n",
|
| 641 |
+
"\n",
|
| 642 |
+
"def status_markdown():\n",
|
| 643 |
+
" lines = [f\"**Status:** {'⏸️ Paused' if STOP_TRADING else ('🟢 Running' if IS_RUNNING else '🟡 Idle')}\"]\n",
|
| 644 |
+
" if LAST_RUN_STARTED_AT:\n",
|
| 645 |
+
" lines.append(f\"**Last start:** {LAST_RUN_STARTED_AT:%Y-%m-%d %H:%M:%S}\")\n",
|
| 646 |
+
" if LAST_RUN_ENDED_AT:\n",
|
| 647 |
+
" lines.append(f\"**Last end:** {LAST_RUN_ENDED_AT:%Y-%m-%d %H:%M:%S}\")\n",
|
| 648 |
+
" if NEXT_RUN_AT and not STOP_TRADING:\n",
|
| 649 |
+
" lines.append(f\"**Next run:** {NEXT_RUN_AT:%Y-%m-%d %H:%M} (ETA {eta_str(NEXT_RUN_AT)})\")\n",
|
| 650 |
+
" if STOP_TRADING:\n",
|
| 651 |
+
" lines.append(f\"**Scheduler:** waiting for Resume\")\n",
|
| 652 |
+
" return \"\\n\\n\".join(lines)\n",
|
| 653 |
+
"\n",
|
| 654 |
+
"\n",
|
| 655 |
+
"async def ui_poll():\n",
|
| 656 |
+
" async with _STATE_LOCK:\n",
|
| 657 |
+
" latest = LATEST_OUTPUT\n",
|
| 658 |
+
" mem_df = LATEST_MEM_DF.copy()\n",
|
| 659 |
+
" csv_path = write_mem_csv(mem_df)\n",
|
| 660 |
+
" return latest, status_markdown(), mem_df, csv_path\n",
|
| 661 |
+
"\n",
|
| 662 |
+
"\n",
|
| 663 |
+
"# ✅ BUTTON HANDLERS\n",
|
| 664 |
+
"def _toggle_stop(flag: bool):\n",
|
| 665 |
+
" global STOP_TRADING\n",
|
| 666 |
+
" STOP_TRADING = flag\n",
|
| 667 |
+
" return status_markdown()\n",
|
| 668 |
+
"\n",
|
| 669 |
+
"\n",
|
| 670 |
+
"with gr.Blocks(fill_height=True) as demo:\n",
|
| 671 |
+
" gr.Markdown(\"## ���� Crypto Trader — Live Board\")\n",
|
| 672 |
+
" gr.Markdown(\n",
|
| 673 |
+
" \"\"\"\n",
|
| 674 |
+
" [agent tracer](https://platform.openai.com/logs?api=traces)\\n\n",
|
| 675 |
+
" [hyperliquid platform](https://app.hyperliquid.xyz/trade)\n",
|
| 676 |
+
" \"\"\",\n",
|
| 677 |
+
" )\n",
|
| 678 |
+
" with gr.Row():\n",
|
| 679 |
+
" stop_btn = gr.Button(\"🛑 Stop\", variant=\"stop\")\n",
|
| 680 |
+
" resume_btn = gr.Button(\"▶️ Resume\", variant=\"primary\")\n",
|
| 681 |
+
"\n",
|
| 682 |
+
" latest_md = gr.Markdown(value=LATEST_OUTPUT)\n",
|
| 683 |
+
" status_md = gr.Markdown(value=status_markdown())\n",
|
| 684 |
+
" mem_table = gr.Dataframe(value=LATEST_MEM_DF, interactive=False, wrap=True, show_label=False)\n",
|
| 685 |
+
" mem_csv = gr.File(interactive=False)\n",
|
| 686 |
+
"\n",
|
| 687 |
+
" stop_btn.click(lambda: _toggle_stop(True), outputs=[status_md])\n",
|
| 688 |
+
" resume_btn.click(lambda: _toggle_stop(False), outputs=[status_md])\n",
|
| 689 |
+
"\n",
|
| 690 |
+
" demo.load(ensure_scheduler_started)\n",
|
| 691 |
+
" timer = gr.Timer(5.0, active=True)\n",
|
| 692 |
+
" timer.tick(ui_poll, outputs=[latest_md, status_md, mem_table, mem_csv])\n",
|
| 693 |
+
"\n",
|
| 694 |
+
"if __name__ == \"__main__\":\n",
|
| 695 |
+
" demo.queue().launch(share=True)\n"
|
| 696 |
+
]
|
| 697 |
+
},
|
| 698 |
+
{
|
| 699 |
+
"cell_type": "code",
|
| 700 |
+
"execution_count": null,
|
| 701 |
+
"metadata": {},
|
| 702 |
+
"outputs": [],
|
| 703 |
+
"source": []
|
| 704 |
+
}
|
| 705 |
+
],
|
| 706 |
+
"metadata": {
|
| 707 |
+
"kernelspec": {
|
| 708 |
+
"display_name": "venv (3.11.14)",
|
| 709 |
+
"language": "python",
|
| 710 |
+
"name": "python3"
|
| 711 |
+
},
|
| 712 |
+
"language_info": {
|
| 713 |
+
"codemirror_mode": {
|
| 714 |
+
"name": "ipython",
|
| 715 |
+
"version": 3
|
| 716 |
+
},
|
| 717 |
+
"file_extension": ".py",
|
| 718 |
+
"mimetype": "text/x-python",
|
| 719 |
+
"name": "python",
|
| 720 |
+
"nbconvert_exporter": "python",
|
| 721 |
+
"pygments_lexer": "ipython3",
|
| 722 |
+
"version": "3.11.14"
|
| 723 |
+
}
|
| 724 |
+
},
|
| 725 |
+
"nbformat": 4,
|
| 726 |
+
"nbformat_minor": 2
|
| 727 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
pandas
|
| 3 |
+
numpy
|
| 4 |
+
python-dotenv
|
| 5 |
+
openai
|
| 6 |
+
openai-agents
|
| 7 |
+
fastmcp
|
| 8 |
+
hyperliquid-python-sdk
|
| 9 |
+
eth-account
|
test.ipynb
ADDED
|
@@ -0,0 +1,1522 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": null,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"data": {
|
| 10 |
+
"text/plain": [
|
| 11 |
+
"True"
|
| 12 |
+
]
|
| 13 |
+
},
|
| 14 |
+
"execution_count": 15,
|
| 15 |
+
"metadata": {},
|
| 16 |
+
"output_type": "execute_result"
|
| 17 |
+
}
|
| 18 |
+
],
|
| 19 |
+
"source": [
|
| 20 |
+
"# uv pip install ipykernel -U --force-reinstall\n",
|
| 21 |
+
"# uv pip install dotenv openai-agents\n",
|
| 22 |
+
"# uv pip install gradio\n",
|
| 23 |
+
"import os\n",
|
| 24 |
+
"from datetime import datetime\n",
|
| 25 |
+
"import gradio as gr\n",
|
| 26 |
+
"from dotenv import load_dotenv\n",
|
| 27 |
+
"from agents import Agent, Runner, trace, Tool\n",
|
| 28 |
+
"from agents.mcp import MCPServerStdio\n",
|
| 29 |
+
"from IPython.display import display, Markdown\n",
|
| 30 |
+
"\n",
|
| 31 |
+
"load_dotenv(override=True)"
|
| 32 |
+
]
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"cell_type": "markdown",
|
| 36 |
+
"metadata": {},
|
| 37 |
+
"source": [
|
| 38 |
+
"# Hyperliquid Trader"
|
| 39 |
+
]
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"cell_type": "code",
|
| 43 |
+
"execution_count": 3,
|
| 44 |
+
"metadata": {},
|
| 45 |
+
"outputs": [],
|
| 46 |
+
"source": [
|
| 47 |
+
"# Now let's use our accounts server as an MCP server\n",
|
| 48 |
+
"\n",
|
| 49 |
+
"params = {\"command\": \"python\", \"args\": [\"-u\", \"hype_accounts_server.py\"], \"env\": {\"HYPERLIQUID_API_KEY\": os.getenv(\"HYPERLIQUID_API_KEY\"), \"HYPERLIQUID_PRIVATE_KEY\": os.getenv(\"HYPERLIQUID_PRIVATE_KEY\"), \"HYPERLIQUID_ACCOUNT_ADDRESS\": os.getenv(\"HYPERLIQUID_ACCOUNT_ADDRESS\")}}\n",
|
| 50 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
|
| 51 |
+
" mcp_tools = await server.list_tools()"
|
| 52 |
+
]
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"cell_type": "code",
|
| 56 |
+
"execution_count": 4,
|
| 57 |
+
"metadata": {},
|
| 58 |
+
"outputs": [
|
| 59 |
+
{
|
| 60 |
+
"name": "stderr",
|
| 61 |
+
"output_type": "stream",
|
| 62 |
+
"text": [
|
| 63 |
+
"Failed to parse JSONRPC message from server\n",
|
| 64 |
+
"Traceback (most recent call last):\n",
|
| 65 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 66 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 67 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 68 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 69 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 70 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 71 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 72 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 73 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 74 |
+
"Failed to parse JSONRPC message from server\n",
|
| 75 |
+
"Traceback (most recent call last):\n",
|
| 76 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 77 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 78 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 79 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 80 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 81 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 82 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 83 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 84 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n"
|
| 85 |
+
]
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"data": {
|
| 89 |
+
"text/markdown": [
|
| 90 |
+
"Your current cash balance is approximately 64.90 USDT. You do not have any holdings at the moment. Your profit and loss value is 4.90 USDT. Let me know if you would like to take any actions."
|
| 91 |
+
],
|
| 92 |
+
"text/plain": [
|
| 93 |
+
"<IPython.core.display.Markdown object>"
|
| 94 |
+
]
|
| 95 |
+
},
|
| 96 |
+
"metadata": {},
|
| 97 |
+
"output_type": "display_data"
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"name": "stderr",
|
| 101 |
+
"output_type": "stream",
|
| 102 |
+
"text": [
|
| 103 |
+
"Process group termination failed for PID 11334: [Errno 1] Operation not permitted, falling back to simple terminate\n"
|
| 104 |
+
]
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"name": "stderr",
|
| 108 |
+
"output_type": "stream",
|
| 109 |
+
"text": [
|
| 110 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 111 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 112 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 113 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 114 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 115 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 116 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 117 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 118 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 119 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 120 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 121 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 122 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 123 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 124 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 125 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 126 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n",
|
| 127 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/traces/ingest \"HTTP/1.1 204 No Content\"\n"
|
| 128 |
+
]
|
| 129 |
+
}
|
| 130 |
+
],
|
| 131 |
+
"source": [
|
| 132 |
+
"instructions = \"You are able to manage a hyperliquid account for me, answer questions about the account, long/short cryptocurrency perpetuals, and close their positions for me.\"\n",
|
| 133 |
+
"request = \"What's my balance and my holdings?\"\n",
|
| 134 |
+
"model = \"gpt-4.1-mini\"\n",
|
| 135 |
+
"\n",
|
| 136 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 137 |
+
" agent = Agent(name=\"hyperliquid_account_manager\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 138 |
+
" with trace(\"hyperliquid_account_manager\"):\n",
|
| 139 |
+
" result = await Runner.run(agent, request)\n",
|
| 140 |
+
" display(Markdown(result.final_output))"
|
| 141 |
+
]
|
| 142 |
+
},
|
| 143 |
+
{
|
| 144 |
+
"cell_type": "code",
|
| 145 |
+
"execution_count": 31,
|
| 146 |
+
"metadata": {},
|
| 147 |
+
"outputs": [
|
| 148 |
+
{
|
| 149 |
+
"name": "stderr",
|
| 150 |
+
"output_type": "stream",
|
| 151 |
+
"text": [
|
| 152 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 153 |
+
"Traceback (most recent call last):\n",
|
| 154 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 155 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 156 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 157 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 158 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 159 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 160 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 161 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 162 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 163 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 164 |
+
"Traceback (most recent call last):\n",
|
| 165 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 166 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 167 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 168 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 169 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 170 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 171 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 172 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 173 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 174 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 175 |
+
"Traceback (most recent call last):\n",
|
| 176 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 177 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 178 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 179 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 180 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 181 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 182 |
+
"pydantic_core._pydantic_core.ValidationError: 4 validation errors for JSONRPCMessage\n",
|
| 183 |
+
"JSONRPCRequest\n",
|
| 184 |
+
" Input should be an object [type=model_type, input_value=0.0048, input_type=float]\n",
|
| 185 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/model_type\n",
|
| 186 |
+
"JSONRPCNotification\n",
|
| 187 |
+
" Input should be an object [type=model_type, input_value=0.0048, input_type=float]\n",
|
| 188 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/model_type\n",
|
| 189 |
+
"JSONRPCResponse\n",
|
| 190 |
+
" Input should be an object [type=model_type, input_value=0.0048, input_type=float]\n",
|
| 191 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/model_type\n",
|
| 192 |
+
"JSONRPCError\n",
|
| 193 |
+
" Input should be an object [type=model_type, input_value=0.0048, input_type=float]\n",
|
| 194 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/model_type\n",
|
| 195 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 196 |
+
"Traceback (most recent call last):\n",
|
| 197 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 198 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 199 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 200 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 201 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 202 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 203 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 204 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Current leverage for ETH: {', input_type=str]\n",
|
| 205 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 206 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 207 |
+
"Traceback (most recent call last):\n",
|
| 208 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 209 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 210 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 211 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 212 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 213 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 214 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 215 |
+
" Invalid JSON: trailing characters at line 1 column 9 [type=json_invalid, input_value=' \"type\": \"cross\",', input_type=str]\n",
|
| 216 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 217 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 218 |
+
"Traceback (most recent call last):\n",
|
| 219 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 220 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 221 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 222 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 223 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 224 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 225 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 226 |
+
" Invalid JSON: trailing characters at line 1 column 10 [type=json_invalid, input_value=' \"value\": 3', input_type=str]\n",
|
| 227 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 228 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 229 |
+
"Traceback (most recent call last):\n",
|
| 230 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 231 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 232 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 233 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 234 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 235 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 236 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 237 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='}', input_type=str]\n",
|
| 238 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n"
|
| 239 |
+
]
|
| 240 |
+
},
|
| 241 |
+
{
|
| 242 |
+
"data": {
|
| 243 |
+
"text/markdown": [
|
| 244 |
+
"I have placed a market order to long $20 of ETH with 1x leverage. The position has been filled at an average price of approximately $4129.6. Let me know if you need any further assistance!"
|
| 245 |
+
],
|
| 246 |
+
"text/plain": [
|
| 247 |
+
"<IPython.core.display.Markdown object>"
|
| 248 |
+
]
|
| 249 |
+
},
|
| 250 |
+
"metadata": {},
|
| 251 |
+
"output_type": "display_data"
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"name": "stderr",
|
| 255 |
+
"output_type": "stream",
|
| 256 |
+
"text": [
|
| 257 |
+
"WARNING:mcp.os.posix.utilities:Process group termination failed for PID 10496: [Errno 1] Operation not permitted, falling back to simple terminate\n"
|
| 258 |
+
]
|
| 259 |
+
}
|
| 260 |
+
],
|
| 261 |
+
"source": [
|
| 262 |
+
"instructions = \"You are able to manage a hyperliquid account for me, answer questions about the account, long/short cryptocurrency perpetuals, and close their positions for me.\"\n",
|
| 263 |
+
"request = \"Help me long $20 of ETH, leveraged 1x.\"\n",
|
| 264 |
+
"model = \"gpt-4.1-mini\"\n",
|
| 265 |
+
"\n",
|
| 266 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 267 |
+
" agent = Agent(name=\"hyperliquid_account_manager\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 268 |
+
" with trace(\"hyperliquid_account_manager\"):\n",
|
| 269 |
+
" result = await Runner.run(agent, request)\n",
|
| 270 |
+
" display(Markdown(result.final_output))"
|
| 271 |
+
]
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"cell_type": "code",
|
| 275 |
+
"execution_count": 34,
|
| 276 |
+
"metadata": {},
|
| 277 |
+
"outputs": [
|
| 278 |
+
{
|
| 279 |
+
"name": "stderr",
|
| 280 |
+
"output_type": "stream",
|
| 281 |
+
"text": [
|
| 282 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 283 |
+
"Traceback (most recent call last):\n",
|
| 284 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 285 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 286 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 287 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 288 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 289 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 290 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 291 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 292 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 293 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 294 |
+
"Traceback (most recent call last):\n",
|
| 295 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 296 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 297 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 298 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 299 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 300 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 301 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 302 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 303 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n"
|
| 304 |
+
]
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"data": {
|
| 308 |
+
"text/markdown": [
|
| 309 |
+
"Your ETH position has been closed. If you need any further assistance or want to manage other positions, just let me know!"
|
| 310 |
+
],
|
| 311 |
+
"text/plain": [
|
| 312 |
+
"<IPython.core.display.Markdown object>"
|
| 313 |
+
]
|
| 314 |
+
},
|
| 315 |
+
"metadata": {},
|
| 316 |
+
"output_type": "display_data"
|
| 317 |
+
}
|
| 318 |
+
],
|
| 319 |
+
"source": [
|
| 320 |
+
"instructions = \"You are able to manage a hyperliquid account for me, answer questions about the account, long/short cryptocurrency perpetuals, and close their positions for me.\"\n",
|
| 321 |
+
"request = \"Help me close my ETH position\"\n",
|
| 322 |
+
"model = \"gpt-4.1-mini\"\n",
|
| 323 |
+
"\n",
|
| 324 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 325 |
+
" agent = Agent(name=\"hyperliquid_account_manager\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 326 |
+
" with trace(\"hyperliquid_account_manager\"):\n",
|
| 327 |
+
" result = await Runner.run(agent, request)\n",
|
| 328 |
+
" display(Markdown(result.final_output))"
|
| 329 |
+
]
|
| 330 |
+
},
|
| 331 |
+
{
|
| 332 |
+
"cell_type": "markdown",
|
| 333 |
+
"metadata": {},
|
| 334 |
+
"source": [
|
| 335 |
+
"https://platform.openai.com/traces"
|
| 336 |
+
]
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"cell_type": "code",
|
| 340 |
+
"execution_count": null,
|
| 341 |
+
"metadata": {},
|
| 342 |
+
"outputs": [],
|
| 343 |
+
"source": [
|
| 344 |
+
"import gradio as gr\n",
|
| 345 |
+
"\n",
|
| 346 |
+
"# Define the function to handle user requests and interact with the agent\n",
|
| 347 |
+
"async def handle_request(request: str):\n",
|
| 348 |
+
" \"\"\"\n",
|
| 349 |
+
" Handles user requests and interacts with the agent.\n",
|
| 350 |
+
"\n",
|
| 351 |
+
" Args:\n",
|
| 352 |
+
" request (str): The user's request (e.g., \"Help me close my ETH position\").\n",
|
| 353 |
+
"\n",
|
| 354 |
+
" Returns:\n",
|
| 355 |
+
" str: The agent's response.\n",
|
| 356 |
+
" \"\"\"\n",
|
| 357 |
+
" instructions = \"You are able to manage a hyperliquid account for me, answer questions about the account, long/short cryptocurrency perpetuals, and close their positions for me.\"\n",
|
| 358 |
+
" model = \"gpt-4.1-mini\"\n",
|
| 359 |
+
"\n",
|
| 360 |
+
" async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 361 |
+
" agent = Agent(name=\"hyperliquid_account_manager\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 362 |
+
" with trace(\"hyperliquid_account_manager\"):\n",
|
| 363 |
+
" result = await Runner.run(agent, request)\n",
|
| 364 |
+
" return result.final_output\n",
|
| 365 |
+
"\n",
|
| 366 |
+
"# Create the Gradio interface\n",
|
| 367 |
+
"interface = gr.Interface(\n",
|
| 368 |
+
" fn=handle_request,\n",
|
| 369 |
+
" inputs=gr.Textbox(label=\"Request\", placeholder=\"Type your request (e.g., Help me long/short $20 of HYPE; Help me close my ETH position)\"),\n",
|
| 370 |
+
" outputs=gr.Textbox(label=\"Response\", lines=10),\n",
|
| 371 |
+
" title=\"Hyperliquid Account Manager\",\n",
|
| 372 |
+
" description=\"Interact with the Hyperliquid account manager to manage your account, trade cryptocurrency perpetuals, and close positions.\"\n",
|
| 373 |
+
")\n",
|
| 374 |
+
"\n",
|
| 375 |
+
"# Launch the interface\n",
|
| 376 |
+
"interface.launch()"
|
| 377 |
+
]
|
| 378 |
+
},
|
| 379 |
+
{
|
| 380 |
+
"cell_type": "markdown",
|
| 381 |
+
"metadata": {},
|
| 382 |
+
"source": [
|
| 383 |
+
"# News researcher"
|
| 384 |
+
]
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
"cell_type": "code",
|
| 388 |
+
"execution_count": null,
|
| 389 |
+
"metadata": {},
|
| 390 |
+
"outputs": [],
|
| 391 |
+
"source": [
|
| 392 |
+
"# !git clone https://github.com/kukapay/cryptopanic-mcp-server.git\n",
|
| 393 |
+
"# !git clone https://github.com/kukapay/crypto-indicators-mcp.git"
|
| 394 |
+
]
|
| 395 |
+
},
|
| 396 |
+
{
|
| 397 |
+
"cell_type": "code",
|
| 398 |
+
"execution_count": null,
|
| 399 |
+
"metadata": {},
|
| 400 |
+
"outputs": [],
|
| 401 |
+
"source": [
|
| 402 |
+
"# available mcp server"
|
| 403 |
+
]
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"cell_type": "code",
|
| 407 |
+
"execution_count": null,
|
| 408 |
+
"metadata": {},
|
| 409 |
+
"outputs": [
|
| 410 |
+
{
|
| 411 |
+
"name": "stderr",
|
| 412 |
+
"output_type": "stream",
|
| 413 |
+
"text": [
|
| 414 |
+
"Error initializing MCP server: Connection closed\n"
|
| 415 |
+
]
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"ename": "McpError",
|
| 419 |
+
"evalue": "Connection closed",
|
| 420 |
+
"output_type": "error",
|
| 421 |
+
"traceback": [
|
| 422 |
+
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
| 423 |
+
"\u001b[31mMcpError\u001b[39m Traceback (most recent call last)",
|
| 424 |
+
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m 1\u001b[39m params = {\u001b[33m\"\u001b[39m\u001b[33mcommand\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33m\"\u001b[39m\u001b[33mpython\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33margs\u001b[39m\u001b[33m\"\u001b[39m: [\u001b[33m\"\u001b[39m\u001b[33m--u\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mcryptopanic-mcp-server\u001b[39m\u001b[33m\"\u001b[39m], \u001b[33m\"\u001b[39m\u001b[33menv\u001b[39m\u001b[33m\"\u001b[39m: {\u001b[33m\"\u001b[39m\u001b[33mCRYPTOPANIC_API_PLAN\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33m\"\u001b[39m\u001b[33mdeveloper\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mCRYPTOPANIC_API_KEY\u001b[39m\u001b[33m\"\u001b[39m: os.getenv(\u001b[33m\"\u001b[39m\u001b[33mCRYPTOPANIC_API_KEY\u001b[39m\u001b[33m\"\u001b[39m)}}\n\u001b[32m 2\u001b[39m \u001b[38;5;66;03m# params = {\"command\": \"python\", \"args\": [\"-u\", \"hype_accounts_server.py\"], \"env\": {\"HYPERLIQUID_API_KEY\": os.getenv(\"HYPERLIQUID_API_KEY\"), \"HYPERLIQUID_PRIVATE_KEY\": os.getenv(\"HYPERLIQUID_PRIVATE_KEY\"), \"HYPERLIQUID_ACCOUNT_ADDRESS\": os.getenv(\"HYPERLIQUID_ACCOUNT_ADDRESS\")}}\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m \u001b[38;5;28;01masync\u001b[39;00m \u001b[38;5;28;01mwith\u001b[39;00m MCPServerStdio(params=params, client_session_timeout_seconds=\u001b[32m30\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m server:\n\u001b[32m 5\u001b[39m mcp_tools = \u001b[38;5;28;01mawait\u001b[39;00m server.list_tools()\n\u001b[32m 6\u001b[39m mcp_tools\n",
|
| 425 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/agents/mcp/server.py:237\u001b[39m, in \u001b[36m_MCPServerWithClientSession.__aenter__\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 236\u001b[39m \u001b[38;5;28;01masync\u001b[39;00m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__aenter__\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m--> \u001b[39m\u001b[32m237\u001b[39m \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.connect()\n\u001b[32m 238\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\n",
|
| 426 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/agents/mcp/server.py:277\u001b[39m, in \u001b[36m_MCPServerWithClientSession.connect\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 266\u001b[39m read, write, *_ = transport\n\u001b[32m 268\u001b[39m session = \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.exit_stack.enter_async_context(\n\u001b[32m 269\u001b[39m ClientSession(\n\u001b[32m 270\u001b[39m read,\n\u001b[32m (...)\u001b[39m\u001b[32m 275\u001b[39m )\n\u001b[32m 276\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m277\u001b[39m server_result = \u001b[38;5;28;01mawait\u001b[39;00m session.initialize()\n\u001b[32m 278\u001b[39m \u001b[38;5;28mself\u001b[39m.server_initialize_result = server_result\n\u001b[32m 279\u001b[39m \u001b[38;5;28mself\u001b[39m.session = session\n",
|
| 427 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/session.py:151\u001b[39m, in \u001b[36mClientSession.initialize\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 139\u001b[39m elicitation = (\n\u001b[32m 140\u001b[39m types.ElicitationCapability() \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._elicitation_callback \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _default_elicitation_callback \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 141\u001b[39m )\n\u001b[32m 142\u001b[39m roots = (\n\u001b[32m 143\u001b[39m \u001b[38;5;66;03m# TODO: Should this be based on whether we\u001b[39;00m\n\u001b[32m 144\u001b[39m \u001b[38;5;66;03m# _will_ send notifications, or only whether\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 148\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 149\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m151\u001b[39m result = \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.send_request(\n\u001b[32m 152\u001b[39m types.ClientRequest(\n\u001b[32m 153\u001b[39m types.InitializeRequest(\n\u001b[32m 154\u001b[39m params=types.InitializeRequestParams(\n\u001b[32m 155\u001b[39m protocolVersion=types.LATEST_PROTOCOL_VERSION,\n\u001b[32m 156\u001b[39m capabilities=types.ClientCapabilities(\n\u001b[32m 157\u001b[39m sampling=sampling,\n\u001b[32m 158\u001b[39m elicitation=elicitation,\n\u001b[32m 159\u001b[39m experimental=\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 160\u001b[39m roots=roots,\n\u001b[32m 161\u001b[39m ),\n\u001b[32m 162\u001b[39m clientInfo=\u001b[38;5;28mself\u001b[39m._client_info,\n\u001b[32m 163\u001b[39m ),\n\u001b[32m 164\u001b[39m )\n\u001b[32m 165\u001b[39m ),\n\u001b[32m 166\u001b[39m types.InitializeResult,\n\u001b[32m 167\u001b[39m )\n\u001b[32m 169\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m result.protocolVersion \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m SUPPORTED_PROTOCOL_VERSIONS:\n\u001b[32m 170\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mUnsupported protocol version from the server: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresult.protocolVersion\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n",
|
| 428 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/shared/session.py:286\u001b[39m, in \u001b[36mBaseSession.send_request\u001b[39m\u001b[34m(self, request, result_type, request_read_timeout_seconds, metadata, progress_callback)\u001b[39m\n\u001b[32m 274\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m McpError(\n\u001b[32m 275\u001b[39m ErrorData(\n\u001b[32m 276\u001b[39m code=httpx.codes.REQUEST_TIMEOUT,\n\u001b[32m (...)\u001b[39m\u001b[32m 282\u001b[39m )\n\u001b[32m 283\u001b[39m )\n\u001b[32m 285\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(response_or_error, JSONRPCError):\n\u001b[32m--> \u001b[39m\u001b[32m286\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m McpError(response_or_error.error)\n\u001b[32m 287\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 288\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m result_type.model_validate(response_or_error.result)\n",
|
| 429 |
+
"\u001b[31mMcpError\u001b[39m: Connection closed"
|
| 430 |
+
]
|
| 431 |
+
}
|
| 432 |
+
],
|
| 433 |
+
"source": [
|
| 434 |
+
"params = {\"command\": \"python\", \"args\": [\"--directory\", \"cryptopanic-mcp-server\", \"run\", \"main.py\"], \"env\": {\"CRYPTOPANIC_API_PLAN\": \"developer\", \"CRYPTOPANIC_API_KEY\": os.getenv(\"CRYPTOPANIC_API_KEY\")}}\n",
|
| 435 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
|
| 436 |
+
" mcp_tools = await server.list_tools()\n",
|
| 437 |
+
"mcp_tools"
|
| 438 |
+
]
|
| 439 |
+
},
|
| 440 |
+
{
|
| 441 |
+
"cell_type": "code",
|
| 442 |
+
"execution_count": null,
|
| 443 |
+
"metadata": {},
|
| 444 |
+
"outputs": [],
|
| 445 |
+
"source": [
|
| 446 |
+
"instructions = f\"\"\"You are a cryptocurrency researcher. You are able the tools to search for interesting cryptocurrency news,\n",
|
| 447 |
+
"look for possible trading opportunities, and help with research.\n",
|
| 448 |
+
"Based on the request, you carry out necessary research and respond with your findings.\n",
|
| 449 |
+
"Take time to make multiple searches to get a comprehensive overview, and then summarize your findings.\n",
|
| 450 |
+
"If there isn't a specific request, then just respond with long/short opportunities based on searching latest news.\n",
|
| 451 |
+
"The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 452 |
+
"request = \"Give me 5 long/short opportunities in crypto based on latest news.\"\n",
|
| 453 |
+
"# request = \"Give me the current news on HYPE\"\n",
|
| 454 |
+
"model = \"gpt-4.1-mini\"\n",
|
| 455 |
+
"\n",
|
| 456 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 457 |
+
" agent = Agent(name=\"crypto_mkt_researcher\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 458 |
+
" with trace(\"crypto_mkt_researcher\"):\n",
|
| 459 |
+
" result = await Runner.run(agent, request)\n",
|
| 460 |
+
" display(Markdown(result.final_output))"
|
| 461 |
+
]
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"cell_type": "code",
|
| 465 |
+
"execution_count": null,
|
| 466 |
+
"metadata": {},
|
| 467 |
+
"outputs": [],
|
| 468 |
+
"source": [
|
| 469 |
+
"# self developed mcp server"
|
| 470 |
+
]
|
| 471 |
+
},
|
| 472 |
+
{
|
| 473 |
+
"cell_type": "code",
|
| 474 |
+
"execution_count": 16,
|
| 475 |
+
"metadata": {},
|
| 476 |
+
"outputs": [
|
| 477 |
+
{
|
| 478 |
+
"data": {
|
| 479 |
+
"text/plain": [
|
| 480 |
+
"'9b68b6ec514f537c2a0e90eb9efd34b85685ee0f'"
|
| 481 |
+
]
|
| 482 |
+
},
|
| 483 |
+
"execution_count": 16,
|
| 484 |
+
"metadata": {},
|
| 485 |
+
"output_type": "execute_result"
|
| 486 |
+
}
|
| 487 |
+
],
|
| 488 |
+
"source": [
|
| 489 |
+
"os.getenv(\"CRYPTOPANIC_API_KEY\")"
|
| 490 |
+
]
|
| 491 |
+
},
|
| 492 |
+
{
|
| 493 |
+
"cell_type": "code",
|
| 494 |
+
"execution_count": 17,
|
| 495 |
+
"metadata": {},
|
| 496 |
+
"outputs": [
|
| 497 |
+
{
|
| 498 |
+
"name": "stderr",
|
| 499 |
+
"output_type": "stream",
|
| 500 |
+
"text": [
|
| 501 |
+
"Failed to parse JSONRPC message from server\n",
|
| 502 |
+
"Traceback (most recent call last):\n",
|
| 503 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 504 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 505 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 506 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 507 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 508 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 509 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 510 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='https://cryptopanic.com/api/developer/v2/posts/', input_type=str]\n",
|
| 511 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n"
|
| 512 |
+
]
|
| 513 |
+
},
|
| 514 |
+
{
|
| 515 |
+
"data": {
|
| 516 |
+
"text/markdown": [
|
| 517 |
+
"Here are 5 long/short opportunities in crypto based on the latest news with news date/time:\n",
|
| 518 |
+
"\n",
|
| 519 |
+
"1. Long Bitcoin (BTC)\n",
|
| 520 |
+
" - News: Bitcoin bounces back above $113,000\n",
|
| 521 |
+
" - Insight: BTC showed resilience and bounced back above the $113,000 level.\n",
|
| 522 |
+
" - Date/Time: 2025-10-12 15:31:02 UTC\n",
|
| 523 |
+
"\n",
|
| 524 |
+
"2. Long Ethereum (ETH)\n",
|
| 525 |
+
" - News: Ethereum surpasses $4,000 with a 5.49% increase in 24 hours, significant profits for whale investors.\n",
|
| 526 |
+
" - Insight: ETH is showing strong momentum and institutional buying after dip, indicating potential upside.\n",
|
| 527 |
+
" - Date/Time: 2025-10-12 14:56:39 UTC / 15:03:46 UTC\n",
|
| 528 |
+
"\n",
|
| 529 |
+
"3. Long Binance Coin (BNB)\n",
|
| 530 |
+
" - News: BNB surpasses $1,270 with a 12.60% increase in 24 hours.\n",
|
| 531 |
+
" - Insight: BNB demonstrates strong bullish momentum with a sharp price increase.\n",
|
| 532 |
+
" - Date/Time: 2025-10-12 14:55:41 UTC\n",
|
| 533 |
+
"\n",
|
| 534 |
+
"4. Short XRP\n",
|
| 535 |
+
" - News: XRP slides below $2.40 amidst whale selling and ETF delays; rebound likely only after Bitcoin stabilizes.\n",
|
| 536 |
+
" - Insight: XRP currently under downward pressure with uncertain short-term recovery.\n",
|
| 537 |
+
" - Date/Time: 2025-10-12 14:44:19 UTC\n",
|
| 538 |
+
"\n",
|
| 539 |
+
"5. Short Ethereum (ETH) Potential Liquidations\n",
|
| 540 |
+
" - News: If ETH surpasses $4,000, significant short liquidations of $221 million could trigger high volatility.\n",
|
| 541 |
+
" - Insight: Watch for potential sharp corrections or volatility spikes post-breakout.\n",
|
| 542 |
+
" - Date/Time: 2025-10-12 14:53:34 UTC\n",
|
| 543 |
+
"\n",
|
| 544 |
+
"Summary:\n",
|
| 545 |
+
"- Suggested longs: BTC, ETH, BNB based on strong price recoveries and institutional interest.\n",
|
| 546 |
+
"- Suggested shorts: XRP due to persistent selling pressure and short-term uncertainty; cautious short or hedge on ETH due to potential liquidation volatility.\n",
|
| 547 |
+
"\n",
|
| 548 |
+
"Let me know if you want deeper analysis or additional opportunities!"
|
| 549 |
+
],
|
| 550 |
+
"text/plain": [
|
| 551 |
+
"<IPython.core.display.Markdown object>"
|
| 552 |
+
]
|
| 553 |
+
},
|
| 554 |
+
"metadata": {},
|
| 555 |
+
"output_type": "display_data"
|
| 556 |
+
}
|
| 557 |
+
],
|
| 558 |
+
"source": [
|
| 559 |
+
"params = {\"command\": \"python\", \"args\": [\"-u\", \"cryptopanic_news_server.py\"], \"env\": {\"CRYPTOPANIC_API_PLAN\": \"developer\", \"CRYPTOPANIC_API_KEY\": os.getenv(\"CRYPTOPANIC_API_KEY\")}}\n",
|
| 560 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
|
| 561 |
+
" mcp_tools = await server.list_tools()\n",
|
| 562 |
+
"\n",
|
| 563 |
+
"instructions = f\"\"\"You are a cryptocurrency researcher. You are able the tools to search for interesting cryptocurrency news,\n",
|
| 564 |
+
"look for possible trading opportunities, and help with research.\n",
|
| 565 |
+
"Based on the request, you carry out necessary research and respond with your findings.\n",
|
| 566 |
+
"Take time to make multiple searches to get a comprehensive overview, and then summarize your findings.\n",
|
| 567 |
+
"If there isn't a specific request, then just respond with long/short opportunities based on searching latest news.\n",
|
| 568 |
+
"The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 569 |
+
"request = \"Give me 5 long/short opportunities in crypto based on latest news. and the news date / time\"\n",
|
| 570 |
+
"# request = \"Give me the current news on HYPE and XRP\"\n",
|
| 571 |
+
"model = \"gpt-4.1-mini\"\n",
|
| 572 |
+
"\n",
|
| 573 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 574 |
+
" agent = Agent(name=\"crypto_mkt_researcher_local\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 575 |
+
" with trace(\"crypto_mkt_researcher_local\"):\n",
|
| 576 |
+
" result = await Runner.run(agent, request)\n",
|
| 577 |
+
" display(Markdown(result.final_output))"
|
| 578 |
+
]
|
| 579 |
+
},
|
| 580 |
+
{
|
| 581 |
+
"cell_type": "markdown",
|
| 582 |
+
"metadata": {},
|
| 583 |
+
"source": [
|
| 584 |
+
"# Technical Analyst"
|
| 585 |
+
]
|
| 586 |
+
},
|
| 587 |
+
{
|
| 588 |
+
"cell_type": "code",
|
| 589 |
+
"execution_count": 18,
|
| 590 |
+
"metadata": {},
|
| 591 |
+
"outputs": [
|
| 592 |
+
{
|
| 593 |
+
"name": "stderr",
|
| 594 |
+
"output_type": "stream",
|
| 595 |
+
"text": [
|
| 596 |
+
"Error initializing MCP server: Connection closed\n"
|
| 597 |
+
]
|
| 598 |
+
},
|
| 599 |
+
{
|
| 600 |
+
"ename": "McpError",
|
| 601 |
+
"evalue": "Connection closed",
|
| 602 |
+
"output_type": "error",
|
| 603 |
+
"traceback": [
|
| 604 |
+
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
| 605 |
+
"\u001b[31mMcpError\u001b[39m Traceback (most recent call last)",
|
| 606 |
+
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[18]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# Now let's use our researcher server as an MCP server\u001b[39;00m\n\u001b[32m 3\u001b[39m params = {\u001b[33m\"\u001b[39m\u001b[33mcommand\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33m\"\u001b[39m\u001b[33mnode\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33margs\u001b[39m\u001b[33m\"\u001b[39m: [\u001b[33m\"\u001b[39m\u001b[33mcrypto-indicators-mcp/index.js\u001b[39m\u001b[33m\"\u001b[39m], \u001b[33m\"\u001b[39m\u001b[33menv\u001b[39m\u001b[33m\"\u001b[39m: {\u001b[33m\"\u001b[39m\u001b[33mEXCHANGE_NAME\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33m\"\u001b[39m\u001b[33mbinance\u001b[39m\u001b[33m\"\u001b[39m}}\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m \u001b[38;5;28;01masync\u001b[39;00m \u001b[38;5;28;01mwith\u001b[39;00m MCPServerStdio(params=params, client_session_timeout_seconds=\u001b[32m30\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m server:\n\u001b[32m 5\u001b[39m mcp_tools = \u001b[38;5;28;01mawait\u001b[39;00m server.list_tools()\n",
|
| 607 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/agents/mcp/server.py:237\u001b[39m, in \u001b[36m_MCPServerWithClientSession.__aenter__\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 236\u001b[39m \u001b[38;5;28;01masync\u001b[39;00m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__aenter__\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m--> \u001b[39m\u001b[32m237\u001b[39m \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.connect()\n\u001b[32m 238\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\n",
|
| 608 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/agents/mcp/server.py:277\u001b[39m, in \u001b[36m_MCPServerWithClientSession.connect\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 266\u001b[39m read, write, *_ = transport\n\u001b[32m 268\u001b[39m session = \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.exit_stack.enter_async_context(\n\u001b[32m 269\u001b[39m ClientSession(\n\u001b[32m 270\u001b[39m read,\n\u001b[32m (...)\u001b[39m\u001b[32m 275\u001b[39m )\n\u001b[32m 276\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m277\u001b[39m server_result = \u001b[38;5;28;01mawait\u001b[39;00m session.initialize()\n\u001b[32m 278\u001b[39m \u001b[38;5;28mself\u001b[39m.server_initialize_result = server_result\n\u001b[32m 279\u001b[39m \u001b[38;5;28mself\u001b[39m.session = session\n",
|
| 609 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/session.py:151\u001b[39m, in \u001b[36mClientSession.initialize\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 139\u001b[39m elicitation = (\n\u001b[32m 140\u001b[39m types.ElicitationCapability() \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._elicitation_callback \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _default_elicitation_callback \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 141\u001b[39m )\n\u001b[32m 142\u001b[39m roots = (\n\u001b[32m 143\u001b[39m \u001b[38;5;66;03m# TODO: Should this be based on whether we\u001b[39;00m\n\u001b[32m 144\u001b[39m \u001b[38;5;66;03m# _will_ send notifications, or only whether\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 148\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 149\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m151\u001b[39m result = \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m.send_request(\n\u001b[32m 152\u001b[39m types.ClientRequest(\n\u001b[32m 153\u001b[39m types.InitializeRequest(\n\u001b[32m 154\u001b[39m params=types.InitializeRequestParams(\n\u001b[32m 155\u001b[39m protocolVersion=types.LATEST_PROTOCOL_VERSION,\n\u001b[32m 156\u001b[39m capabilities=types.ClientCapabilities(\n\u001b[32m 157\u001b[39m sampling=sampling,\n\u001b[32m 158\u001b[39m elicitation=elicitation,\n\u001b[32m 159\u001b[39m experimental=\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m 160\u001b[39m roots=roots,\n\u001b[32m 161\u001b[39m ),\n\u001b[32m 162\u001b[39m clientInfo=\u001b[38;5;28mself\u001b[39m._client_info,\n\u001b[32m 163\u001b[39m ),\n\u001b[32m 164\u001b[39m )\n\u001b[32m 165\u001b[39m ),\n\u001b[32m 166\u001b[39m types.InitializeResult,\n\u001b[32m 167\u001b[39m )\n\u001b[32m 169\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m result.protocolVersion \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m SUPPORTED_PROTOCOL_VERSIONS:\n\u001b[32m 170\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mUnsupported protocol version from the server: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mresult.protocolVersion\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n",
|
| 610 |
+
"\u001b[36mFile \u001b[39m\u001b[32m/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/shared/session.py:286\u001b[39m, in \u001b[36mBaseSession.send_request\u001b[39m\u001b[34m(self, request, result_type, request_read_timeout_seconds, metadata, progress_callback)\u001b[39m\n\u001b[32m 274\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m McpError(\n\u001b[32m 275\u001b[39m ErrorData(\n\u001b[32m 276\u001b[39m code=httpx.codes.REQUEST_TIMEOUT,\n\u001b[32m (...)\u001b[39m\u001b[32m 282\u001b[39m )\n\u001b[32m 283\u001b[39m )\n\u001b[32m 285\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(response_or_error, JSONRPCError):\n\u001b[32m--> \u001b[39m\u001b[32m286\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m McpError(response_or_error.error)\n\u001b[32m 287\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 288\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m result_type.model_validate(response_or_error.result)\n",
|
| 611 |
+
"\u001b[31mMcpError\u001b[39m: Connection closed"
|
| 612 |
+
]
|
| 613 |
+
}
|
| 614 |
+
],
|
| 615 |
+
"source": [
|
| 616 |
+
"# Now let's use our researcher server as an MCP server\n",
|
| 617 |
+
"\n",
|
| 618 |
+
"params = {\"command\": \"node\", \"args\": [\"crypto-indicators-mcp/index.js\"], \"env\": {\"EXCHANGE_NAME\": \"binance\"}}\n",
|
| 619 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
|
| 620 |
+
" mcp_tools = await server.list_tools()"
|
| 621 |
+
]
|
| 622 |
+
},
|
| 623 |
+
{
|
| 624 |
+
"cell_type": "code",
|
| 625 |
+
"execution_count": null,
|
| 626 |
+
"metadata": {},
|
| 627 |
+
"outputs": [],
|
| 628 |
+
"source": [
|
| 629 |
+
"for tool in mcp_tools:\n",
|
| 630 |
+
" if 'stochastic' in tool.name.lower():\n",
|
| 631 |
+
" print(tool.name, tool.description)"
|
| 632 |
+
]
|
| 633 |
+
},
|
| 634 |
+
{
|
| 635 |
+
"cell_type": "code",
|
| 636 |
+
"execution_count": null,
|
| 637 |
+
"metadata": {},
|
| 638 |
+
"outputs": [],
|
| 639 |
+
"source": [
|
| 640 |
+
"instructions = \"\"\"You are a cryptocurrency perpetuals trading researcher. You have the tools for trend/momentum/volatility/volume technical indicators and strategies.\n",
|
| 641 |
+
"Default analysis interval is 1h, and default lookback period is 36. \n",
|
| 642 |
+
"Default indicators/strategies you have are:\n",
|
| 643 |
+
" - EMA (20, 200)\n",
|
| 644 |
+
" - MACD (12, 26, 9)\n",
|
| 645 |
+
" - stochastic RSI (14, 14, 3, 3)\n",
|
| 646 |
+
" - Accumulation/Distribution (ADL)\n",
|
| 647 |
+
" - Volume\n",
|
| 648 |
+
"Based on the indicators, Look for possible long/short opportunities, and come up with a strategy.\n",
|
| 649 |
+
"The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 650 |
+
"request = \"Give me the current indicator analysis and strategies for 3 cryptocurrencies.\"\n",
|
| 651 |
+
"model = \"gpt-4.1-mini\"\n",
|
| 652 |
+
"\n",
|
| 653 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 654 |
+
" agent = Agent(name=\"crypto_technical_researcher\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 655 |
+
" with trace(\"crypto_technical_researcher\"):\n",
|
| 656 |
+
" result = await Runner.run(agent, request)\n",
|
| 657 |
+
" display(Markdown(result.final_output))"
|
| 658 |
+
]
|
| 659 |
+
},
|
| 660 |
+
{
|
| 661 |
+
"cell_type": "code",
|
| 662 |
+
"execution_count": null,
|
| 663 |
+
"metadata": {},
|
| 664 |
+
"outputs": [],
|
| 665 |
+
"source": [
|
| 666 |
+
"# Create own techinal indicator mcp server"
|
| 667 |
+
]
|
| 668 |
+
},
|
| 669 |
+
{
|
| 670 |
+
"cell_type": "code",
|
| 671 |
+
"execution_count": 22,
|
| 672 |
+
"metadata": {},
|
| 673 |
+
"outputs": [
|
| 674 |
+
{
|
| 675 |
+
"data": {
|
| 676 |
+
"text/markdown": [
|
| 677 |
+
"Here's the current indicator analysis and strategy suggestions for the three cryptocurrencies BTC, ETH, and HYPE on the 1-hour timeframe using the last 36 periods:\n",
|
| 678 |
+
"\n",
|
| 679 |
+
"1. BTC:\n",
|
| 680 |
+
"- Price is above both EMA 20 (114468) and EMA 200 (112207), indicating medium-term uptrend.\n",
|
| 681 |
+
"- MACD histogram is negative (-209) with MACD line below signal line, showing weak bearish momentum.\n",
|
| 682 |
+
"- Stoch RSI is oversold (around 10), suggesting potential upcoming bullish reversal.\n",
|
| 683 |
+
"- ADX is strong at about 36, indicating a strong trend, with +DI above -DI showing bullish trend dominance.\n",
|
| 684 |
+
"- Bollinger Bands %b is low (~0.15), near the lower band, indicating possible oversold conditions.\n",
|
| 685 |
+
"- MFI is moderate (~42), not showing strong volume pressure.\n",
|
| 686 |
+
"\n",
|
| 687 |
+
"Strategy: Watch for bullish reversal signals since price is in uptrend but currently with weak bearish momentum and oversold oscillators. A potential long entry could be planned on stochastic RSI turning up from oversold, confirmed by MACD histogram turning positive and increasing volume/ADL. Stops below recent lows advised.\n",
|
| 688 |
+
"\n",
|
| 689 |
+
"2. ETH:\n",
|
| 690 |
+
"- Price is above EMA 20 (4109) and EMA 200 (3876), indicating medium-term bullish trend.\n",
|
| 691 |
+
"- MACD histogram negative (-16) but MACD line is below the signal line, showing some weakening momentum.\n",
|
| 692 |
+
"- Stoch RSI is low (~18), near oversold territory, potential for reversal.\n",
|
| 693 |
+
"- ADX strong (~38) with +DI above -DI, confirming trend strength biased bullish.\n",
|
| 694 |
+
"- Bollinger Bands %b is moderate (~0.34), price neither extreme.\n",
|
| 695 |
+
"- MFI moderate (~52), no strong volume bias.\n",
|
| 696 |
+
"\n",
|
| 697 |
+
"Strategy: Similar to BTC, ETH is in an overall uptrend with weakening momentum and oversold stochastic RSI. Look for buying opportunities once stoch RSI starts rising, ideally accompanied by MACD histogram crossover to positive and volume rise. Confirm trend strength via ADX and watch volume for validation.\n",
|
| 698 |
+
"\n",
|
| 699 |
+
"3. HYPE:\n",
|
| 700 |
+
"- Price above EMA 20 (40.24) and EMA 200 (38.60), indicating bullish trend.\n",
|
| 701 |
+
"- MACD histogram slightly negative (-0.11) with MACD line below signal line, showing momentary weak bearish pressure.\n",
|
| 702 |
+
"- Stoch RSI ~28, not deeply oversold but low.\n",
|
| 703 |
+
"- ADX weak (~18), suggesting weak trend strength.\n",
|
| 704 |
+
"- Bollinger Bands %b at 0.44, mid to upper band.\n",
|
| 705 |
+
"- MFI moderate (~52), neutral volume.\n",
|
| 706 |
+
"\n",
|
| 707 |
+
"Strategy: HYPE is in a bullish phase but with weak trend strength and mild bearish momentum. This coin may lack strong directional movement currently. Wait for MACD to cross above signal line with rising momentum and stochastic RSI to turn upward to consider longs. Use cautious entry and tight stops due to low ADX.\n",
|
| 708 |
+
"\n",
|
| 709 |
+
"Summary Strategy for All:\n",
|
| 710 |
+
"- All three coins are in a medium-term uptrend (price above EMAs).\n",
|
| 711 |
+
"- Current momentum is weakening with MACD histograms negative.\n",
|
| 712 |
+
"- Stochastic RSI in BTC and ETH are oversold, indicating potential for bullish reversal soon.\n",
|
| 713 |
+
"- ADX is strong in BTC and ETH, validating strong trending environment, but weak in HYPE.\n",
|
| 714 |
+
"- Monitor for bullish confirmation signals in momentum indicators and volume (MACD crossover, stoch RSI turning up, ADL and volume increasing) before entering longs.\n",
|
| 715 |
+
"- Place stops below recent swing lows to manage risk.\n",
|
| 716 |
+
"\n",
|
| 717 |
+
"Would you like me to run any specific strategy backtests or detailed setups on these coins?"
|
| 718 |
+
],
|
| 719 |
+
"text/plain": [
|
| 720 |
+
"<IPython.core.display.Markdown object>"
|
| 721 |
+
]
|
| 722 |
+
},
|
| 723 |
+
"metadata": {},
|
| 724 |
+
"output_type": "display_data"
|
| 725 |
+
}
|
| 726 |
+
],
|
| 727 |
+
"source": [
|
| 728 |
+
"params = {\"command\": \"python\", \"args\": [\"-u\", \"hl_indicators_server.py\"]}\n",
|
| 729 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
|
| 730 |
+
" mcp_tools = await server.list_tools()\n",
|
| 731 |
+
"\n",
|
| 732 |
+
"instructions = \"\"\"You are a cryptocurrency perpetuals trading researcher. You have the tools for trend/momentum/volatility/volume technical indicators and strategies.\n",
|
| 733 |
+
"Default analysis interval is 1h, and default lookback period is 36. \n",
|
| 734 |
+
"Default indicators/strategies you have are:\n",
|
| 735 |
+
" - EMA (20, 200)\n",
|
| 736 |
+
" - MACD (12, 26, 9)\n",
|
| 737 |
+
" - stochastic RSI (14, 14, 3, 3)\n",
|
| 738 |
+
" - Accumulation/Distribution (ADL)\n",
|
| 739 |
+
" - Volume\n",
|
| 740 |
+
"Based on the indicators, Look for possible long/short opportunities, and come up with a strategy.\n",
|
| 741 |
+
"The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 742 |
+
"request = \"Give me the current indicator analysis and strategies for 3 cryptocurrencies.\"\n",
|
| 743 |
+
"model = \"gpt-4.1-mini\"\n",
|
| 744 |
+
"\n",
|
| 745 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 746 |
+
" agent = Agent(name=\"crypto_technical_researcher_local\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 747 |
+
" with trace(\"crypto_technical_researcher_local\"):\n",
|
| 748 |
+
" result = await Runner.run(agent, request)\n",
|
| 749 |
+
" display(Markdown(result.final_output))"
|
| 750 |
+
]
|
| 751 |
+
},
|
| 752 |
+
{
|
| 753 |
+
"cell_type": "markdown",
|
| 754 |
+
"metadata": {},
|
| 755 |
+
"source": [
|
| 756 |
+
"# Create interface"
|
| 757 |
+
]
|
| 758 |
+
},
|
| 759 |
+
{
|
| 760 |
+
"cell_type": "code",
|
| 761 |
+
"execution_count": 23,
|
| 762 |
+
"metadata": {},
|
| 763 |
+
"outputs": [
|
| 764 |
+
{
|
| 765 |
+
"data": {
|
| 766 |
+
"text/plain": [
|
| 767 |
+
"True"
|
| 768 |
+
]
|
| 769 |
+
},
|
| 770 |
+
"execution_count": 23,
|
| 771 |
+
"metadata": {},
|
| 772 |
+
"output_type": "execute_result"
|
| 773 |
+
}
|
| 774 |
+
],
|
| 775 |
+
"source": [
|
| 776 |
+
"import os\n",
|
| 777 |
+
"from datetime import datetime\n",
|
| 778 |
+
"import gradio as gr\n",
|
| 779 |
+
"from dotenv import load_dotenv\n",
|
| 780 |
+
"from agents import Agent, Runner, trace, Tool\n",
|
| 781 |
+
"from agents.mcp import MCPServerStdio\n",
|
| 782 |
+
"from IPython.display import display, Markdown\n",
|
| 783 |
+
"\n",
|
| 784 |
+
"load_dotenv(override=True)"
|
| 785 |
+
]
|
| 786 |
+
},
|
| 787 |
+
{
|
| 788 |
+
"cell_type": "code",
|
| 789 |
+
"execution_count": 31,
|
| 790 |
+
"metadata": {},
|
| 791 |
+
"outputs": [],
|
| 792 |
+
"source": [
|
| 793 |
+
"# hyperliquid_trader_mcp_server_params = [{\"command\": \"uv\", \"args\": [\"run\", \"hype_accounts_server.py\"]}]\n",
|
| 794 |
+
"# crypto_news_mcp_server_params = [{\"command\": \"uv\", \"args\": [\"run\", \"cryptopanic_news_server.py\"]}]\n",
|
| 795 |
+
"# technical_analyst_mcp_server_params = [{\"command\": \"node\", \"args\": [\"crypto-indicators-mcp/index.js\"], \"env\": {\"EXCHANGE_NAME\": \"binance\"}}]\n",
|
| 796 |
+
"\n",
|
| 797 |
+
"hyperliquid_trader_mcp_server_params = [{\"command\": \"python3\", \"args\": [\"-u\", \"hype_accounts_server.py\"], \"env\": {\"HYPERLIQUID_API_KEY\": os.getenv(\"HYPERLIQUID_API_KEY\"), \"HYPERLIQUID_PRIVATE_KEY\": os.getenv(\"HYPERLIQUID_PRIVATE_KEY\"), \"HYPERLIQUID_ACCOUNT_ADDRESS\": os.getenv(\"HYPERLIQUID_ACCOUNT_ADDRESS\")}}]\n",
|
| 798 |
+
"crypto_news_mcp_server_params = [{\"command\": \"python3\", \"args\": [\"-u\", \"cryptopanic_news_server.py\"], \"env\": {\"CRYPTOPANIC_API_PLAN\": \"developer\", \"CRYPTOPANIC_API_KEY\": os.getenv(\"CRYPTOPANIC_API_KEY\")}}]\n",
|
| 799 |
+
"technical_analyst_mcp_server_params = [{\"command\": \"python3\", \"args\": [\"-u\", \"hl_indicators_server.py\"]}]\n",
|
| 800 |
+
"\n",
|
| 801 |
+
"hyperliquid_trader_mcp_servers = [MCPServerStdio(params, client_session_timeout_seconds=30) for params in hyperliquid_trader_mcp_server_params]\n",
|
| 802 |
+
"crypto_news_mcp_servers = [MCPServerStdio(params, client_session_timeout_seconds=30) for params in crypto_news_mcp_server_params]\n",
|
| 803 |
+
"technical_analyst_mcp_servers = [MCPServerStdio(params, client_session_timeout_seconds=30) for params in technical_analyst_mcp_server_params]\n",
|
| 804 |
+
"\n",
|
| 805 |
+
"mcp_servers = hyperliquid_trader_mcp_servers + crypto_news_mcp_servers + technical_analyst_mcp_servers"
|
| 806 |
+
]
|
| 807 |
+
},
|
| 808 |
+
{
|
| 809 |
+
"cell_type": "code",
|
| 810 |
+
"execution_count": 32,
|
| 811 |
+
"metadata": {},
|
| 812 |
+
"outputs": [],
|
| 813 |
+
"source": [
|
| 814 |
+
"# putting agents together\n",
|
| 815 |
+
"async def get_crypto_news_researcher(mcp_servers) -> Agent:\n",
|
| 816 |
+
" instructions = f\"\"\"You are a cryptocurrency researcher. You have the tools to search for interesting cryptocurrency news,\n",
|
| 817 |
+
" look for possible trading opportunities, and help with research.\n",
|
| 818 |
+
" Default None if there are no specific cryptocurrency news or opportunities in the user request.\n",
|
| 819 |
+
" Otherwise, indicate the symbol of specific cryptocurrency if there are specific requests. eg. 'HYPE', 'BTC', 'ETH', 'XRP', etc.\n",
|
| 820 |
+
" Based on the request, you carry out necessary research and respond with your findings.\n",
|
| 821 |
+
" Take time to make multiple searches to get a comprehensive overview, and then summarize your findings.\n",
|
| 822 |
+
" If there isn't a specific request, then just respond with long/short opportunities based on searching latest news.\n",
|
| 823 |
+
" The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\n",
|
| 824 |
+
" \"\"\"\n",
|
| 825 |
+
" researcher = Agent(\n",
|
| 826 |
+
" name=\"Crypto news researcher\",\n",
|
| 827 |
+
" instructions=instructions,\n",
|
| 828 |
+
" model=\"gpt-4.1-mini\",\n",
|
| 829 |
+
" mcp_servers=mcp_servers,\n",
|
| 830 |
+
" )\n",
|
| 831 |
+
" return researcher\n",
|
| 832 |
+
"\n",
|
| 833 |
+
"async def get_crypto_news_researcher_tool(mcp_servers) -> Tool:\n",
|
| 834 |
+
" researcher = await get_crypto_news_researcher(mcp_servers)\n",
|
| 835 |
+
" return researcher.as_tool(\n",
|
| 836 |
+
" tool_name=\"crypto_news_researcher\",\n",
|
| 837 |
+
" tool_description=\"This tool researches online for cryptocurrency news and opportunities, \\\n",
|
| 838 |
+
" either based on your specific request to look into a certain cryptocurrency, \\\n",
|
| 839 |
+
" or generally for notable cryptocurrency news and opportunities. \\\n",
|
| 840 |
+
" Describe what kind of research you're looking for.\"\n",
|
| 841 |
+
" )\n",
|
| 842 |
+
"\n",
|
| 843 |
+
"async def get_technical_analyst_researcher(mcp_servers) -> Agent:\n",
|
| 844 |
+
" instructions = \"\"\"You are a cryptocurrency perpetuals technical trading researcher. You have the tools for trend/momentum/volatility/volume technical indicators and strategies.\n",
|
| 845 |
+
" Default analysis interval is 1h, and default lookback period is 36. \n",
|
| 846 |
+
" Default indicators/strategies you have are:\n",
|
| 847 |
+
" - EMA (20, 200)\n",
|
| 848 |
+
" - MACD (12, 26, 9)\n",
|
| 849 |
+
" - stochastic RSI (14, 14, 3, 3)\n",
|
| 850 |
+
" - Accumulation/Distribution (ADL)\n",
|
| 851 |
+
" - Volume\n",
|
| 852 |
+
" Based on the indicators, Look for possible long/short opportunities, and come up with a strategy.\n",
|
| 853 |
+
" The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 854 |
+
" researcher = Agent(\n",
|
| 855 |
+
" name=\"Crypto technical researcher\",\n",
|
| 856 |
+
" instructions=instructions,\n",
|
| 857 |
+
" model=\"gpt-4.1-mini\",\n",
|
| 858 |
+
" mcp_servers=mcp_servers,\n",
|
| 859 |
+
" )\n",
|
| 860 |
+
" return researcher\n",
|
| 861 |
+
"\n",
|
| 862 |
+
"async def get_technical_analyst_researcher_tool(mcp_servers) -> Tool:\n",
|
| 863 |
+
" researcher = await get_crypto_news_researcher(mcp_servers)\n",
|
| 864 |
+
" return researcher.as_tool(\n",
|
| 865 |
+
" tool_name=\"crypto_technical_researcher\",\n",
|
| 866 |
+
" tool_description=\"This tool researches technical indicators for trading opportunities, \\\n",
|
| 867 |
+
" either based on your specific request to look into a certain cryptocurrency or indicator, \\\n",
|
| 868 |
+
" or generally for notable cryptocurrency news and technical indicators EMA, MACD, stochastic RSI, Accumulation/Distribution (ADL), Volume. \\\n",
|
| 869 |
+
" Describe what cryptocurrency and indicators you're looking for.\"\n",
|
| 870 |
+
" )"
|
| 871 |
+
]
|
| 872 |
+
},
|
| 873 |
+
{
|
| 874 |
+
"cell_type": "code",
|
| 875 |
+
"execution_count": 33,
|
| 876 |
+
"metadata": {},
|
| 877 |
+
"outputs": [],
|
| 878 |
+
"source": [
|
| 879 |
+
"# import importlib\n",
|
| 880 |
+
"# import cryptopanic_news_server\n",
|
| 881 |
+
"# importlib.reload(cryptopanic_news_server)\n",
|
| 882 |
+
"\n",
|
| 883 |
+
"# await cryptopanic_news_server.get_crypto_news(None)"
|
| 884 |
+
]
|
| 885 |
+
},
|
| 886 |
+
{
|
| 887 |
+
"cell_type": "code",
|
| 888 |
+
"execution_count": 34,
|
| 889 |
+
"metadata": {},
|
| 890 |
+
"outputs": [
|
| 891 |
+
{
|
| 892 |
+
"name": "stderr",
|
| 893 |
+
"output_type": "stream",
|
| 894 |
+
"text": [
|
| 895 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 896 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 897 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n"
|
| 898 |
+
]
|
| 899 |
+
},
|
| 900 |
+
{
|
| 901 |
+
"data": {
|
| 902 |
+
"text/markdown": [
|
| 903 |
+
"Current prices:\n",
|
| 904 |
+
"- BTC: $114,134 (1h close)\n",
|
| 905 |
+
"- ETH: $4,118.3 (1h close)\n",
|
| 906 |
+
"- XRP: $2.5766 (1h close)\n",
|
| 907 |
+
"\n",
|
| 908 |
+
"Analysis and strategies based on trend, momentum, volume, and volatility indicators (1h interval, lookback ~36):\n",
|
| 909 |
+
"\n",
|
| 910 |
+
"BTC:\n",
|
| 911 |
+
"- Price is above both EMA20 (~$114,453) and EMA200 (~$112,205), indicating an uptrend.\n",
|
| 912 |
+
"- MACD histogram is negative (-219.56) and MACD line is below the signal line, suggesting bearish momentum short term.\n",
|
| 913 |
+
"- Stoch RSI is very low (~5.3), indicating oversold conditions and potential for rebound.\n",
|
| 914 |
+
"- ADX is healthy (~35.9) showing a strong trend, +DI > -DI confirming bullish.\n",
|
| 915 |
+
"- Bollinger Bands %b is near the lower band (0.075), suggesting price might be near a support zone.\n",
|
| 916 |
+
"- Volume had a recent spike but current volume is moderate.\n",
|
| 917 |
+
"\n",
|
| 918 |
+
"Strategy: Look for a long entry on signs of momentum turning up (MACD histogram moving positive, Stoch RSI crossing upwards), targeting a continuation of the uptrend supported by strong ADX. Use stop loss just below recent lows or lower Bollinger Band.\n",
|
| 919 |
+
"\n",
|
| 920 |
+
"ETH:\n",
|
| 921 |
+
"- Price is above EMA20 (~$4,108) and EMA200 (~$3,876), confirming uptrend.\n",
|
| 922 |
+
"- MACD histogram is negative but closing to zero (-16.8).\n",
|
| 923 |
+
"- Stoch RSI is low (~15.4), suggesting possible oversold bounce.\n",
|
| 924 |
+
"- ADX is ~38.3, indicating a strong trend, with +DI > -DI.\n",
|
| 925 |
+
"- Bollinger Bands %b at ~0.29, price is above lower band but not overextended.\n",
|
| 926 |
+
"- Volume steady with some spikes.\n",
|
| 927 |
+
"\n",
|
| 928 |
+
"Strategy: Similar to BTC, consider a long position on signs of momentum improvement. Use a stop loss near the lower Bollinger Band or recent swing low to manage risk.\n",
|
| 929 |
+
"\n",
|
| 930 |
+
"XRP:\n",
|
| 931 |
+
"- Price above EMA20 (~2.56) and EMA200 (~2.41), showing bullish trend.\n",
|
| 932 |
+
"- MACD histogram slightly negative (-0.008), near neutral momentum.\n",
|
| 933 |
+
"- Stoch RSI is low (~14.3), indicating possible oversold bounce.\n",
|
| 934 |
+
"- ADX is ~37.2 with +DI > -DI, strong trend present.\n",
|
| 935 |
+
"- Bollinger Bands %b around 0.55, price near middle of range.\n",
|
| 936 |
+
"- Volume normal with occasional spikes.\n",
|
| 937 |
+
"\n",
|
| 938 |
+
"Strategy: Potential long opportunity on momentum improvements with Stoch RSI crossing up from oversold. Take profits near upper Bollinger Band and use stop losses around EMA20 or lower Bollinger Band.\n",
|
| 939 |
+
"\n",
|
| 940 |
+
"Summary:\n",
|
| 941 |
+
"All three coins show strong uptrends with correction or momentum pause signs. Best approach is to trade longs on potential momentum bounce in oversold zones with proper stops. Avoid shorts as overall trend remains bullish.\n",
|
| 942 |
+
"\n",
|
| 943 |
+
"Let me know if you want detailed entry/exit signals or additional indicator analysis."
|
| 944 |
+
],
|
| 945 |
+
"text/plain": [
|
| 946 |
+
"<IPython.core.display.Markdown object>"
|
| 947 |
+
]
|
| 948 |
+
},
|
| 949 |
+
"metadata": {},
|
| 950 |
+
"output_type": "display_data"
|
| 951 |
+
},
|
| 952 |
+
{
|
| 953 |
+
"name": "stderr",
|
| 954 |
+
"output_type": "stream",
|
| 955 |
+
"text": [
|
| 956 |
+
"WARNING:mcp.os.posix.utilities:Process group termination failed for PID 11534: [Errno 3] No such process, falling back to simple terminate\n"
|
| 957 |
+
]
|
| 958 |
+
}
|
| 959 |
+
],
|
| 960 |
+
"source": [
|
| 961 |
+
"technical_research_question = \"What's the latest trading indicators for HYPE and XRP? Recommend some trading strategies.\"\n",
|
| 962 |
+
"technical_research_question = \"What's the current prices for BTC, ETH, and XRP? Recommend some trading strategies.\"\n",
|
| 963 |
+
"\n",
|
| 964 |
+
"for server in technical_analyst_mcp_servers:\n",
|
| 965 |
+
" await server.connect()\n",
|
| 966 |
+
"technical_analyst = await get_technical_analyst_researcher(technical_analyst_mcp_servers)\n",
|
| 967 |
+
"with trace(\"Technical analyst\"):\n",
|
| 968 |
+
" result = await Runner.run(technical_analyst, technical_research_question, max_turns=30)\n",
|
| 969 |
+
"display(Markdown(result.final_output))"
|
| 970 |
+
]
|
| 971 |
+
},
|
| 972 |
+
{
|
| 973 |
+
"cell_type": "code",
|
| 974 |
+
"execution_count": 35,
|
| 975 |
+
"metadata": {},
|
| 976 |
+
"outputs": [
|
| 977 |
+
{
|
| 978 |
+
"name": "stderr",
|
| 979 |
+
"output_type": "stream",
|
| 980 |
+
"text": [
|
| 981 |
+
"WARNING:mcp.server.fastmcp.tools.tool_manager:Tool already exists: long_perps_mkt_price\n"
|
| 982 |
+
]
|
| 983 |
+
},
|
| 984 |
+
{
|
| 985 |
+
"name": "stdout",
|
| 986 |
+
"output_type": "stream",
|
| 987 |
+
"text": [
|
| 988 |
+
"Running with account address: 0xa0F8a0f880A49c8e15B3D2bf7e80A886f213D08b\n",
|
| 989 |
+
"Running with agent address: 0x61755bcFd93a7E5dF74e1ef79DAD43E8aF528e31\n"
|
| 990 |
+
]
|
| 991 |
+
},
|
| 992 |
+
{
|
| 993 |
+
"name": "stderr",
|
| 994 |
+
"output_type": "stream",
|
| 995 |
+
"text": [
|
| 996 |
+
"INFO:websocket:Websocket connected\n"
|
| 997 |
+
]
|
| 998 |
+
},
|
| 999 |
+
{
|
| 1000 |
+
"data": {
|
| 1001 |
+
"text/plain": [
|
| 1002 |
+
"{'cash_balance': 64.902113,\n",
|
| 1003 |
+
" 'holdings': 'No holdings',\n",
|
| 1004 |
+
" 'profit_and_loss': 4.902113}"
|
| 1005 |
+
]
|
| 1006 |
+
},
|
| 1007 |
+
"execution_count": 35,
|
| 1008 |
+
"metadata": {},
|
| 1009 |
+
"output_type": "execute_result"
|
| 1010 |
+
}
|
| 1011 |
+
],
|
| 1012 |
+
"source": [
|
| 1013 |
+
"import importlib\n",
|
| 1014 |
+
"import hype_accounts_server\n",
|
| 1015 |
+
"importlib.reload(hype_accounts_server)\n",
|
| 1016 |
+
"\n",
|
| 1017 |
+
"account_details = await hype_accounts_server.get_account_details()\n",
|
| 1018 |
+
"account_details"
|
| 1019 |
+
]
|
| 1020 |
+
},
|
| 1021 |
+
{
|
| 1022 |
+
"cell_type": "code",
|
| 1023 |
+
"execution_count": 36,
|
| 1024 |
+
"metadata": {},
|
| 1025 |
+
"outputs": [
|
| 1026 |
+
{
|
| 1027 |
+
"name": "stderr",
|
| 1028 |
+
"output_type": "stream",
|
| 1029 |
+
"text": [
|
| 1030 |
+
"WARNING:mcp.server.fastmcp.tools.tool_manager:Tool already exists: long_perps_mkt_price\n"
|
| 1031 |
+
]
|
| 1032 |
+
},
|
| 1033 |
+
{
|
| 1034 |
+
"name": "stdout",
|
| 1035 |
+
"output_type": "stream",
|
| 1036 |
+
"text": [
|
| 1037 |
+
"Running with account address: 0xa0F8a0f880A49c8e15B3D2bf7e80A886f213D08b\n",
|
| 1038 |
+
"Running with agent address: 0x61755bcFd93a7E5dF74e1ef79DAD43E8aF528e31\n"
|
| 1039 |
+
]
|
| 1040 |
+
},
|
| 1041 |
+
{
|
| 1042 |
+
"name": "stderr",
|
| 1043 |
+
"output_type": "stream",
|
| 1044 |
+
"text": [
|
| 1045 |
+
"INFO:websocket:Websocket connected\n"
|
| 1046 |
+
]
|
| 1047 |
+
}
|
| 1048 |
+
],
|
| 1049 |
+
"source": [
|
| 1050 |
+
"# Using MCP Servers to read resources\n",
|
| 1051 |
+
"import importlib\n",
|
| 1052 |
+
"import hype_accounts_server\n",
|
| 1053 |
+
"importlib.reload(hype_accounts_server)\n",
|
| 1054 |
+
"\n",
|
| 1055 |
+
"account_details = await hype_accounts_server.get_account_details()\n",
|
| 1056 |
+
"\n",
|
| 1057 |
+
"instructions = f\"\"\"\n",
|
| 1058 |
+
" You are a cryptocurrency perpetuals trader. \n",
|
| 1059 |
+
" Your current holdings and balance is:\n",
|
| 1060 |
+
" {account_details}\n",
|
| 1061 |
+
" You have the tools to perform a search for relevant cryptocurrency news.\n",
|
| 1062 |
+
" You have tools to check cryptocurrency technical indicators and prices.\n",
|
| 1063 |
+
" You have tools to check your current holdings and balance.\n",
|
| 1064 |
+
" You have tools to long and short cryptocurrency perpetuals.\n",
|
| 1065 |
+
" Please make use of these tools to manage your portfolio. Carry out trades as you see fit; do not wait for instructions or ask for confirmation.\n",
|
| 1066 |
+
"\"\"\"\n",
|
| 1067 |
+
"\n",
|
| 1068 |
+
"prompt = f\"\"\"\n",
|
| 1069 |
+
"Use your tools to make decisions about your portfolio.\n",
|
| 1070 |
+
"Step 1: look into your current holdings and balance.\n",
|
| 1071 |
+
"Step 2: look into the latest cryptocurrency news and relevant long/short opportunities. select 3-5 cryptocurrencies to focus on.\n",
|
| 1072 |
+
"Step 3: look into the current price, and technical indicators for these cryptocurrencies. Recommend a few trading strategies.\n",
|
| 1073 |
+
"Step 4: make trades to long/short these cryptocurrencies based on your research and strategies.\n",
|
| 1074 |
+
"You do not have to make any trades if there are no good opportunities.\n",
|
| 1075 |
+
"Respond with your detailed research and actions.\n",
|
| 1076 |
+
"The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M\")}\n",
|
| 1077 |
+
"\"\"\""
|
| 1078 |
+
]
|
| 1079 |
+
},
|
| 1080 |
+
{
|
| 1081 |
+
"cell_type": "code",
|
| 1082 |
+
"execution_count": 37,
|
| 1083 |
+
"metadata": {},
|
| 1084 |
+
"outputs": [
|
| 1085 |
+
{
|
| 1086 |
+
"name": "stderr",
|
| 1087 |
+
"output_type": "stream",
|
| 1088 |
+
"text": [
|
| 1089 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1090 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1091 |
+
"Traceback (most recent call last):\n",
|
| 1092 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1093 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1094 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1095 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1096 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1097 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1098 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1099 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 1100 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1101 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1102 |
+
"Traceback (most recent call last):\n",
|
| 1103 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1104 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1105 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1106 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1107 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1108 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1109 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1110 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 1111 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1112 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1113 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1114 |
+
"Traceback (most recent call last):\n",
|
| 1115 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1116 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1117 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1118 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1119 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1120 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1121 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1122 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='https://cryptopanic.com/api/developer/v2/posts/', input_type=str]\n",
|
| 1123 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1124 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1125 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1126 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1127 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1128 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1129 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1130 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1131 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1132 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1133 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1134 |
+
"Traceback (most recent call last):\n",
|
| 1135 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1136 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1137 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1138 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1139 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1140 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1141 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1142 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 1143 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1144 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1145 |
+
"Traceback (most recent call last):\n",
|
| 1146 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1147 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1148 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1149 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1150 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1151 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1152 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1153 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 1154 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1155 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1156 |
+
"Traceback (most recent call last):\n",
|
| 1157 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1158 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1159 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1160 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1161 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1162 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1163 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1164 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 1165 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1166 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1167 |
+
"Traceback (most recent call last):\n",
|
| 1168 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1169 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1170 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1171 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1172 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1173 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1174 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1175 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 1176 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1177 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1178 |
+
"Traceback (most recent call last):\n",
|
| 1179 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1180 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1181 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1182 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1183 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1184 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1185 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1186 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 1187 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1188 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1189 |
+
"Traceback (most recent call last):\n",
|
| 1190 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1191 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1192 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1193 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1194 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1195 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1196 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1197 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 1198 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1199 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1200 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1201 |
+
"Traceback (most recent call last):\n",
|
| 1202 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1203 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1204 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1205 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1206 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1207 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1208 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1209 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 1210 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1211 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1212 |
+
"Traceback (most recent call last):\n",
|
| 1213 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1214 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1215 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1216 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1217 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1218 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1219 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1220 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 1221 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1222 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1223 |
+
"Traceback (most recent call last):\n",
|
| 1224 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1225 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1226 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1227 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1228 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1229 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1230 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1231 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 1232 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1233 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1234 |
+
"Traceback (most recent call last):\n",
|
| 1235 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1236 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1237 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1238 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1239 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1240 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1241 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1242 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 1243 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1244 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1245 |
+
"Traceback (most recent call last):\n",
|
| 1246 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1247 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1248 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1249 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1250 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1251 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1252 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1253 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with account add...5B3D2bf7e80A886f213D08b', input_type=str]\n",
|
| 1254 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1255 |
+
"ERROR:mcp.client.stdio:Failed to parse JSONRPC message from server\n",
|
| 1256 |
+
"Traceback (most recent call last):\n",
|
| 1257 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/mcp/client/stdio/__init__.py\", line 155, in stdout_reader\n",
|
| 1258 |
+
" message = types.JSONRPCMessage.model_validate_json(line)\n",
|
| 1259 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1260 |
+
" File \"/Applications/Documents/agent_trading/venv/lib/python3.11/site-packages/pydantic/main.py\", line 746, in model_validate_json\n",
|
| 1261 |
+
" return cls.__pydantic_validator__.validate_json(\n",
|
| 1262 |
+
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
|
| 1263 |
+
"pydantic_core._pydantic_core.ValidationError: 1 validation error for JSONRPCMessage\n",
|
| 1264 |
+
" Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Running with agent addre...74e1ef79DAD43E8aF528e31', input_type=str]\n",
|
| 1265 |
+
" For further information visit https://errors.pydantic.dev/2.11/v/json_invalid\n",
|
| 1266 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1267 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n",
|
| 1268 |
+
"INFO:httpx:HTTP Request: POST https://api.openai.com/v1/responses \"HTTP/1.1 200 OK\"\n"
|
| 1269 |
+
]
|
| 1270 |
+
},
|
| 1271 |
+
{
|
| 1272 |
+
"data": {
|
| 1273 |
+
"text/markdown": [
|
| 1274 |
+
"I need to know which exchange or platform you want me to trade on to find the correct perpetual contract symbols. Please specify the exchange or platform name (e.g., Binance, Bybit, BitMEX), so I can identify the right symbols and proceed with trades."
|
| 1275 |
+
],
|
| 1276 |
+
"text/plain": [
|
| 1277 |
+
"<IPython.core.display.Markdown object>"
|
| 1278 |
+
]
|
| 1279 |
+
},
|
| 1280 |
+
"metadata": {},
|
| 1281 |
+
"output_type": "display_data"
|
| 1282 |
+
}
|
| 1283 |
+
],
|
| 1284 |
+
"source": [
|
| 1285 |
+
"agent_name = \"crypto_trader\"\n",
|
| 1286 |
+
"for server in mcp_servers:\n",
|
| 1287 |
+
" await server.connect()\n",
|
| 1288 |
+
"\n",
|
| 1289 |
+
"crypto_news_researcher_tool = await get_crypto_news_researcher_tool(crypto_news_mcp_servers)\n",
|
| 1290 |
+
"technical_analyst_researcher_tool = await get_technical_analyst_researcher_tool(technical_analyst_mcp_servers)\n",
|
| 1291 |
+
"\n",
|
| 1292 |
+
"trader = Agent(\n",
|
| 1293 |
+
" name=agent_name,\n",
|
| 1294 |
+
" instructions=instructions,\n",
|
| 1295 |
+
" tools=[crypto_news_researcher_tool, technical_analyst_researcher_tool],\n",
|
| 1296 |
+
" mcp_servers=hyperliquid_trader_mcp_servers,\n",
|
| 1297 |
+
" model=\"gpt-4.1-mini\",\n",
|
| 1298 |
+
")\n",
|
| 1299 |
+
"with trace(agent_name):\n",
|
| 1300 |
+
" result = await Runner.run(trader, prompt, max_turns=30)\n",
|
| 1301 |
+
"display(Markdown(result.final_output))"
|
| 1302 |
+
]
|
| 1303 |
+
},
|
| 1304 |
+
{
|
| 1305 |
+
"cell_type": "code",
|
| 1306 |
+
"execution_count": null,
|
| 1307 |
+
"metadata": {},
|
| 1308 |
+
"outputs": [],
|
| 1309 |
+
"source": [
|
| 1310 |
+
"# store memory\n",
|
| 1311 |
+
"\n",
|
| 1312 |
+
"import json\n",
|
| 1313 |
+
"import openai\n",
|
| 1314 |
+
"from datetime import datetime\n",
|
| 1315 |
+
"from pathlib import Path\n",
|
| 1316 |
+
"\n",
|
| 1317 |
+
"openai.api_key = os.getenv(\"OPENAI_API_KEY\")\n",
|
| 1318 |
+
"MEMORY_FILE = Path(\"memories.json\")\n",
|
| 1319 |
+
"\n",
|
| 1320 |
+
"def load_memories(n: int = 3) -> list[str]:\n",
|
| 1321 |
+
" \"\"\"Load the last n memories from file.\"\"\"\n",
|
| 1322 |
+
" if not MEMORY_FILE.exists():\n",
|
| 1323 |
+
" return []\n",
|
| 1324 |
+
" with open(MEMORY_FILE, \"r\", encoding=\"utf-8\") as f:\n",
|
| 1325 |
+
" lines = [json.loads(l) for l in f if l.strip()]\n",
|
| 1326 |
+
" return [m[\"summary\"] for m in lines[-n:]]\n",
|
| 1327 |
+
"\n",
|
| 1328 |
+
"def save_memory(summary: str):\n",
|
| 1329 |
+
" \"\"\"Append a new memory summary to file.\"\"\"\n",
|
| 1330 |
+
" entry = {\"timestamp\": datetime.now().isoformat(), \"summary\": summary}\n",
|
| 1331 |
+
" with open(MEMORY_FILE, \"a\", encoding=\"utf-8\") as f:\n",
|
| 1332 |
+
" f.write(json.dumps(entry) + \"\\n\")\n",
|
| 1333 |
+
"\n",
|
| 1334 |
+
"async def summarize_result(result_text: str) -> str:\n",
|
| 1335 |
+
" \"\"\"Use an LLM to summarize a trading report concisely.\"\"\"\n",
|
| 1336 |
+
" client = openai.AsyncOpenAI()\n",
|
| 1337 |
+
" resp = await client.responses.create(\n",
|
| 1338 |
+
" model=\"gpt-4o-mini\",\n",
|
| 1339 |
+
" input=f\"Summarize this trading report in 3–5 bullet points focusing on reasoning and actions:\\n\\n{result_text}\",\n",
|
| 1340 |
+
" max_output_tokens=200,\n",
|
| 1341 |
+
" )\n",
|
| 1342 |
+
" return resp.output_text.strip()\n"
|
| 1343 |
+
]
|
| 1344 |
+
},
|
| 1345 |
+
{
|
| 1346 |
+
"cell_type": "code",
|
| 1347 |
+
"execution_count": null,
|
| 1348 |
+
"metadata": {},
|
| 1349 |
+
"outputs": [],
|
| 1350 |
+
"source": [
|
| 1351 |
+
"import json\n",
|
| 1352 |
+
"import asyncio\n",
|
| 1353 |
+
"import importlib\n",
|
| 1354 |
+
"import hype_accounts_server\n",
|
| 1355 |
+
"from datetime import datetime\n",
|
| 1356 |
+
"from IPython.display import Markdown, display\n",
|
| 1357 |
+
"\n",
|
| 1358 |
+
"\n",
|
| 1359 |
+
"importlib.reload(hype_accounts_server)\n",
|
| 1360 |
+
"\n",
|
| 1361 |
+
"async def trade_loop():\n",
|
| 1362 |
+
" \"\"\"Run trading agent every 30 minutes indefinitely.\"\"\"\n",
|
| 1363 |
+
" while True:\n",
|
| 1364 |
+
" print(f\"\\n🕒 Starting trading cycle at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n",
|
| 1365 |
+
" try:\n",
|
| 1366 |
+
" # 🧠 Load past summaries\n",
|
| 1367 |
+
" past_memories = load_memories(3)\n",
|
| 1368 |
+
" memory_text = \"\\n\".join(past_memories) if past_memories else \"No prior memories.\"\n",
|
| 1369 |
+
" print(\"🧠 Loaded past memories:\\n\", memory_text)\n",
|
| 1370 |
+
" account_details = await hype_accounts_server.get_account_details()\n",
|
| 1371 |
+
"\n",
|
| 1372 |
+
" instructions = f\"\"\"\n",
|
| 1373 |
+
" You are a cryptocurrency perpetuals trader. \n",
|
| 1374 |
+
" Here are your past insights:\n",
|
| 1375 |
+
" {memory_text}\n",
|
| 1376 |
+
" Your current holdings and balance is:\n",
|
| 1377 |
+
" {account_details}\n",
|
| 1378 |
+
" You have the tools to perform a search for relevant cryptocurrency news.\n",
|
| 1379 |
+
" You have tools to check cryptocurrency technical indicators and prices.\n",
|
| 1380 |
+
" You have tools to check your current holdings and balance.\n",
|
| 1381 |
+
" You have tools to long and short cryptocurrency perpetuals.\n",
|
| 1382 |
+
" Please make use of these tools to manage your portfolio. Carry out trades as you see fit; do not wait for instructions or ask for confirmation.\n",
|
| 1383 |
+
" \"\"\"\n",
|
| 1384 |
+
"\n",
|
| 1385 |
+
" prompt = f\"\"\"\n",
|
| 1386 |
+
" Use your tools to make decisions about your portfolio.\n",
|
| 1387 |
+
" Step 1: look into your current holdings and balance.\n",
|
| 1388 |
+
" Step 2: research the latest cryptocurrency news and relevant long/short opportunities. select 3-5 cryptocurrencies to focus on.\n",
|
| 1389 |
+
" Step 3: analyse the current price, and technical indicators (EMA (20, 200), MACD (12, 26, 9), stochastic RSI (14, 14, 3, 3), Accumulation/Distribution (ADL), Volume) for these cryptocurrencies. Recommend a few trading strategies.\n",
|
| 1390 |
+
" Step 4: make long/short trades on these cryptocurrencies based on your research and strategies.\n",
|
| 1391 |
+
" You do not have to make any trades if there are no good opportunities.\n",
|
| 1392 |
+
" Respond with detailed information and analysis for each step - 1) research 2) detailed technical analysis with indicator values 3) trade executions.\n",
|
| 1393 |
+
" The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M\")}\n",
|
| 1394 |
+
" \"\"\"\n",
|
| 1395 |
+
"\n",
|
| 1396 |
+
" # Connect servers\n",
|
| 1397 |
+
" for server in mcp_servers:\n",
|
| 1398 |
+
" await server.connect()\n",
|
| 1399 |
+
"\n",
|
| 1400 |
+
" # Load tools\n",
|
| 1401 |
+
" crypto_news_researcher_tool = await get_crypto_news_researcher_tool(crypto_news_mcp_servers)\n",
|
| 1402 |
+
" technical_analyst_researcher_tool = await get_technical_analyst_researcher_tool(technical_analyst_mcp_servers)\n",
|
| 1403 |
+
"\n",
|
| 1404 |
+
" trader = Agent(\n",
|
| 1405 |
+
" name=\"crypto_trader\",\n",
|
| 1406 |
+
" instructions=instructions,\n",
|
| 1407 |
+
" tools=[crypto_news_researcher_tool, technical_analyst_researcher_tool],\n",
|
| 1408 |
+
" mcp_servers=hyperliquid_trader_mcp_servers,\n",
|
| 1409 |
+
" model=\"gpt-4.1-mini\",\n",
|
| 1410 |
+
" )\n",
|
| 1411 |
+
"\n",
|
| 1412 |
+
" with trace(\"crypto_trader\"):\n",
|
| 1413 |
+
" result = await Runner.run(trader, prompt, max_turns=30)\n",
|
| 1414 |
+
" display(Markdown(result.final_output))\n",
|
| 1415 |
+
"\n",
|
| 1416 |
+
" except Exception as e:\n",
|
| 1417 |
+
" print(\"❌ Error in trading loop:\", e)\n",
|
| 1418 |
+
"\n",
|
| 1419 |
+
" # Sleep for 30 minutes before next run\n",
|
| 1420 |
+
" print(\"⏸️ Sleeping for 30 minutes...\\n\")\n",
|
| 1421 |
+
" await asyncio.sleep(30 * 60)\n",
|
| 1422 |
+
"\n",
|
| 1423 |
+
"# Run the loop once you’re ready\n",
|
| 1424 |
+
"await trade_loop()\n"
|
| 1425 |
+
]
|
| 1426 |
+
},
|
| 1427 |
+
{
|
| 1428 |
+
"cell_type": "code",
|
| 1429 |
+
"execution_count": null,
|
| 1430 |
+
"metadata": {},
|
| 1431 |
+
"outputs": [],
|
| 1432 |
+
"source": []
|
| 1433 |
+
},
|
| 1434 |
+
{
|
| 1435 |
+
"cell_type": "code",
|
| 1436 |
+
"execution_count": null,
|
| 1437 |
+
"metadata": {},
|
| 1438 |
+
"outputs": [],
|
| 1439 |
+
"source": []
|
| 1440 |
+
},
|
| 1441 |
+
{
|
| 1442 |
+
"cell_type": "code",
|
| 1443 |
+
"execution_count": null,
|
| 1444 |
+
"metadata": {},
|
| 1445 |
+
"outputs": [],
|
| 1446 |
+
"source": [
|
| 1447 |
+
"import gradio as gr\n",
|
| 1448 |
+
"\n",
|
| 1449 |
+
"# Define the function to handle user requests and interact with the agent\n",
|
| 1450 |
+
"async def handle_request(request: str):\n",
|
| 1451 |
+
" \"\"\"\n",
|
| 1452 |
+
" Handles user requests and interacts with the agent.\n",
|
| 1453 |
+
"\n",
|
| 1454 |
+
" Args:\n",
|
| 1455 |
+
" request (str): The user's request (e.g., \"Search for possible long / short opportunities based on trend/momentum/volatility/volume strategies\").\n",
|
| 1456 |
+
"\n",
|
| 1457 |
+
" Returns:\n",
|
| 1458 |
+
" str: The agent's response.\n",
|
| 1459 |
+
" \"\"\"\n",
|
| 1460 |
+
" instructions = \"\"\"You are a cryptocurrency perpetuals trading researcher. You have the tools for trend/momentum/volatility/volume technical indicators and strategies.\n",
|
| 1461 |
+
" Look for possible long / short opportunities, and help with research.\n",
|
| 1462 |
+
" Based on the request, you carry out necessary research and respond with your findings.\n",
|
| 1463 |
+
" Take time to make multiple searches to get a comprehensive overview, and then summarize your findings.\n",
|
| 1464 |
+
" If there isn't a specific request, then just respond with searching latest trends.\n",
|
| 1465 |
+
" The current datetime is {datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")}\"\"\"\n",
|
| 1466 |
+
" model = \"gpt-4.1-mini\"\n",
|
| 1467 |
+
"\n",
|
| 1468 |
+
" async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as mcp_server:\n",
|
| 1469 |
+
" agent = Agent(name=\"cryptocurrency_trading_researcher\", instructions=instructions, model=model, mcp_servers=[mcp_server])\n",
|
| 1470 |
+
" with trace(\"cryptocurrency_trading_researcher\"):\n",
|
| 1471 |
+
" result = await Runner.run(agent, request)\n",
|
| 1472 |
+
" return result.final_output\n",
|
| 1473 |
+
"\n",
|
| 1474 |
+
"# Create the Gradio interface\n",
|
| 1475 |
+
"interface = gr.Interface(\n",
|
| 1476 |
+
" fn=handle_request,\n",
|
| 1477 |
+
" inputs=gr.Textbox(label=\"Request\", placeholder=\"Type your request (e.g., Help me search for long / short trading opportunities based on trend/momentum/volatility/volume strategies\"),\n",
|
| 1478 |
+
" outputs=gr.Textbox(label=\"Response\", lines=10),\n",
|
| 1479 |
+
" title=\"Crypto currency researcher\",\n",
|
| 1480 |
+
" description=\"Interact with the cryptocurrency trading researcher to get insights on trading opportunities and strategies.\"\n",
|
| 1481 |
+
")\n",
|
| 1482 |
+
"\n",
|
| 1483 |
+
"# Launch the interface\n",
|
| 1484 |
+
"interface.launch()"
|
| 1485 |
+
]
|
| 1486 |
+
},
|
| 1487 |
+
{
|
| 1488 |
+
"cell_type": "code",
|
| 1489 |
+
"execution_count": null,
|
| 1490 |
+
"metadata": {},
|
| 1491 |
+
"outputs": [],
|
| 1492 |
+
"source": [
|
| 1493 |
+
"# Now let's use our researcher server as an MCP server\n",
|
| 1494 |
+
"\n",
|
| 1495 |
+
"params = {\"command\": \"uv\", \"args\": [\"--directory\", \"./crypto-sentiment-mcp\", \"run\", \"main.py\"], \"env\": {\"SANTIMENT_API_KEY\": \"binance\"}}\n",
|
| 1496 |
+
"async with MCPServerStdio(params=params, client_session_timeout_seconds=30) as server:\n",
|
| 1497 |
+
" mcp_tools = await server.list_tools()"
|
| 1498 |
+
]
|
| 1499 |
+
}
|
| 1500 |
+
],
|
| 1501 |
+
"metadata": {
|
| 1502 |
+
"kernelspec": {
|
| 1503 |
+
"display_name": "venv (3.11.14)",
|
| 1504 |
+
"language": "python",
|
| 1505 |
+
"name": "python3"
|
| 1506 |
+
},
|
| 1507 |
+
"language_info": {
|
| 1508 |
+
"codemirror_mode": {
|
| 1509 |
+
"name": "ipython",
|
| 1510 |
+
"version": 3
|
| 1511 |
+
},
|
| 1512 |
+
"file_extension": ".py",
|
| 1513 |
+
"mimetype": "text/x-python",
|
| 1514 |
+
"name": "python",
|
| 1515 |
+
"nbconvert_exporter": "python",
|
| 1516 |
+
"pygments_lexer": "ipython3",
|
| 1517 |
+
"version": "3.11.14"
|
| 1518 |
+
}
|
| 1519 |
+
},
|
| 1520 |
+
"nbformat": 4,
|
| 1521 |
+
"nbformat_minor": 2
|
| 1522 |
+
}
|