samsonleegh commited on
Commit
f5ed163
·
verified ·
1 Parent(s): 618bb16

Upload 7 files

Browse files
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,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mcp.server.fastmcp import FastMCP
2
+ from cryptopanic_news import get_posts
3
+ import os
4
+ from dotenv import load_dotenv
5
+ load_dotenv(override=True)
6
+
7
+ mcp = FastMCP("cryptopanic_news_server")
8
+
9
+ @mcp.tool()
10
+ async def get_crypto_news(currencies_ls: list | str | None = None) -> str:
11
+ """Fetch latest cryptocurrency news from CryptoPanic.
12
+
13
+ Args:
14
+ 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"]).
15
+
16
+ If set to None, retrieves the general CryptoPanic news feed
17
+ without filtering by any specific currency.
18
+ """
19
+ token = os.getenv("CRYPTOPANIC_API_KEY")
20
+ if not token:
21
+ return "Error: CRYPTOPANIC_API_KEY not set."
22
+
23
+ # Allow a single string or a list
24
+ if isinstance(currencies_ls, str):
25
+ currencies_ls = [currencies_ls]
26
+
27
+ data = get_posts(
28
+ auth_token=token,
29
+ kind="news",
30
+ currencies=currencies_ls,
31
+ page=1
32
+ )
33
+
34
+ if not isinstance(data, dict):
35
+ return f"Error: Unexpected API response: {data!r}"
36
+
37
+ results = []
38
+ for post in data.get("results", []):
39
+ title = post.get("title") or "No title"
40
+ desc = post.get("description") or ""
41
+ pub = post.get("published_at") or "Unknown time"
42
+ results.append(f"title: {title}\n{desc[:200]}\npublished at: {pub}\n")
43
+
44
+ return "\n".join(results) if results else "No recent crypto news found."
45
+
46
+ if __name__ == "__main__":
47
+ mcp.run(transport='stdio')
hype_accounts.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 dotenv import load_dotenv
14
+ from datetime import datetime, timezone
15
+
16
+
17
+ # Load environment variables
18
+ load_dotenv(override=True)
19
+
20
+ # https://github.com/hyperliquid-dex/hyperliquid-python-sdk/blob/master/examples/example_utils.py
21
+ def setup(base_url=constants.MAINNET_API_URL, skip_ws=False, perp_dexs=None):
22
+ account: LocalAccount = eth_account.Account.from_key(os.getenv("HYPERLIQUID_PRIVATE_KEY"))
23
+ address = os.getenv("HYPERLIQUID_ACCOUNT_ADDRESS") # config["account_address"]
24
+ if address == "":
25
+ address = account.address
26
+ print("Running with account address:", address)
27
+ if address != account.address:
28
+ print("Running with agent address:", account.address)
29
+ info = Info(base_url, skip_ws, perp_dexs=perp_dexs)
30
+ user_state = info.user_state(address)
31
+ spot_user_state = info.spot_user_state(address)
32
+ margin_summary = user_state["marginSummary"]
33
+ if float(margin_summary["accountValue"]) == 0 and len(spot_user_state["balances"]) == 0:
34
+ print("Not running the example because the provided account has no equity.")
35
+ url = info.base_url.split(".", 1)[1]
36
+ 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."
37
+ raise Exception(error_string)
38
+ exchange = Exchange(account, base_url, account_address=address, perp_dexs=perp_dexs)
39
+ return address, info, exchange
40
+
41
+ def long_short_shares_mkt_price(symbol, value, leverage, is_buy) -> str:
42
+ address, info, exchange = setup()
43
+ end_time = int(datetime.now(timezone.utc).timestamp() * 1000) # Current time in ms
44
+ start_time = int(datetime.now(timezone.utc).timestamp() * 1000) # Current time in ms
45
+ close_price = float(info.candles_snapshot(symbol, "1m", start_time, end_time)[0]['c'])
46
+ coins_metadata_df = pd.DataFrame(info.meta()['universe'])
47
+ rounding_decimals = coins_metadata_df[coins_metadata_df['name']==symbol]['szDecimals'].values[0]
48
+ quantity = np.round(float(value)/float(close_price),rounding_decimals)
49
+ print(quantity)
50
+ mkt_order_result = exchange.market_open(
51
+ name=symbol,
52
+ is_buy=is_buy,
53
+ sz=quantity,
54
+ px=None,
55
+ slippage=0.01
56
+ )
57
+ is_cross = True
58
+ user_state = info.user_state(address)
59
+ for asset_position in user_state["assetPositions"]:
60
+ if asset_position["position"]["coin"] == symbol:
61
+ print(f"Current leverage for {symbol}:", json.dumps(asset_position["position"]["leverage"], indent=2))
62
+ leverage_position = exchange.update_leverage(leverage, symbol, is_cross)
63
+ return {'mkt_order_result': mkt_order_result, 'leverage_position': leverage_position}
hype_accounts_server.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
30
+ Args:
31
+ symbol: The symbol of the cryptocurrency (e.g., "HYPE","ETC","BTC","XRP").
32
+ value: The USD value to long.
33
+ leverage: The leverage to use for the position.
34
+ """
35
+ return str(long_short_shares_mkt_price(symbol, value, leverage, is_buy=True))
36
+
37
+ @mcp.tool()
38
+ async def short_perps_mkt_price(symbol: str, value:float, leverage:int) -> str:
39
+ """Create a short position for a cryptocurrency perpetual contract at market price.
40
+
41
+ Args:
42
+ symbol: The symbol of the cryptocurrency (e.g., "HYPE","ETC","BTC").
43
+ value: The USD value to short.
44
+ leverage: The leverage to use for the position.
45
+ """
46
+ return str(long_short_shares_mkt_price(symbol, value, leverage, is_buy=False))
47
+
48
+ @mcp.tool()
49
+ async def close_perps_mkt_price(symbol: str) -> str:
50
+ """Close the position for a cryptocurrency perpetual contract at market price.
51
+
52
+ Args:
53
+ symbol: The symbol of the cryptocurrency (e.g., "HYPE","ETC","BTC").
54
+ """
55
+ _, _, exchange = setup()
56
+ return str(exchange.market_close(symbol))
57
+
58
+ if __name__ == "__main__":
59
+ mcp.run(transport='stdio')
memories.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {"timestamp": "2025-10-11T16:21:26.049373", "summary": "Trades executed as planned:\n\n- Added a long BTC position worth $10 at 3x leverage.\n- Opened a short XRP position with $10 size at 3x leverage at an average price of about $2.4567.\n- Closed the existing long LTC position fully at an average price of about $98.955.\n- Added to the ZEC long position with about $10 value at 3x leverage, average price around $266.42.\n\nCurrent portfolio now has increased exposure to BTC and ZEC aligned with bullish momentum and medium-term bullish news outlook. LTC exposure was removed due to lack of signals, and a short position was added on XRP given bearish pressure and whale selling.\n\nCash balance is around $32 remaining for any further opportunities. Overall, the portfolio is positioned to benefit from both growth in BTC/ZEC and short-term declines or weakness in XRP.\n\nI will continue monitoring price and news for any updates or new trading opportunities. Let me know if you need further analysis or adjustments."}
2
+ {"timestamp": "2025-10-11T16:45:07.448681", "summary": "Step 1: Current holdings and balance review:\n- Holdings: BTC (0.00031 BTC, 3x leveraged long), ETH (0.0053 ETH, 3x leveraged long), ZEC (0.13 ZEC, 3x leveraged long)\n- Cash balance: $62.60\n- Unrealized PnL positive, with ZEC showing strong gains; BTC slightly underwater, ETH slightly positive.\n\nStep 2: Latest news and opportunities summary:\n- BTC has rallied near previous all-time highs (~$126K), with strong demand and an implied volatility spike. Long opportunities on dips (~$120K), target ~$180K; caution around upcoming options expiry volatility.\n- ETH consolidating with elevated volatility; long above $4,600-$4,800 possible, risk below $4,250.\n- ZEC and privacy coins are on a strong rally with institutional interest; long possible but watch overbought signals and corrections.\n- XRP faces heavy whale selling and bearish momentum, with spot ETF approval as a conditional reversal catalyst. Presently a short candidate.\n- Emerging altcoins like Railgun showing high volatility and gains, but higher risk.\n\nStep 3: Technical indicators on 1-hour timeframe:\n- BTC: Bearish momentum indicated by MACD crossover below signal line, negative accumulation/distribution, price below EMA(20), stochastic RSI neutral. Current price ~114,700.\n- ETH: Bearish momentum, MACD below signal, negative distribution, price near mid consolidation zone (~4367), stochastic RSI below 50.\n- ZEC: Bullish momentum with MACD above signal line, accumulation continuing, but stochastic RSI overbought (~95%), current price ~237. Potential short-term pullback risk.\n- XRP: Bearish overall; MACD negative, EMA downtrend, negative accumulation, stochastic RSI often oversold, price ~2.44-2.48.\n\nStep 4: Recommendations and trades:\n- BTC: Wait for a dip towards $120K (current 114.7K is below suggested support for buying). No new long yet. Hold existing long.\n- ETH: Price consolidating, bearish signals dominate; no clear long signal unless price breaks above $4600-4800. Hold current.\n- ZEC: Strong momentum ongoing but stochastic RSI overbought; no new long additions now, potentially consider partial profit-taking if short-term pullback confirmed.\n- XRP: Bearish signals confirmed with whale selling; initiate a short position using part of cash balance to capitalize on downside.\n- Cash available: $62.60 \u2014 use about $30-$40 to short XRP at market with 3x leverage.\n\nI will execute the following trade now:\n- Short XRP perpetual contract, $35 size, 3x leverage.\n\nNo other trade actions currently best given risk/reward and technicals.\n\nProceeding with the short XRP trade now."}
3
+ {"timestamp": "2025-10-11T16:48:19.104493", "summary": "Trade executed: Added a $35 size long position on ZEC perpetual contract with 3x leverage at an average price of about $279.39.\n\nSummary:\n- Current holdings after the trade: BTC long, ETH long, and increased exposure to ZEC long.\n- Cash balance remains around $28.45.\n- No current trades on XRP due to mixed signals.\n- Strategy is to hold BTC and ETH while capitalizing on the potential upside move in ZEC amid its stabilizing momentum.\n- Will continue monitoring price action and news for further opportunities or risks.\n\nLet me know if you want me to research or trade other cryptocurrencies or adjust existing positions."}
4
+ {"timestamp": "2025-10-11T17:03:41.710319", "summary": "Trade executed: Opened a long position on ADA perpetual contract with $30 value at 3x leverage at an average price of about $0.67879.\n\nSummary:\n- Holdings: BTC, ETH, ZEC (all long), plus new ADA long position.\n- Cash balance after trade: approximately $35.42 remaining.\n- Strategy: Hold existing BTC and ETH longs, monitor ZEC for pullback, avoid XRP for now, and capitalize on promising altcoin momentum with ADA long.\n- Continuing to monitor price action and news for further opportunities or risks.\n\nLet me know if you want me to research or trade other cryptocurrencies or adjust existing positions."}
5
+ {"timestamp": "2025-10-11T17:11:27.846277", "summary": "Trades executed:\n- Opened a short position on XRP perpetual contract for $30 size at 3x leverage, average short price ~2.5006.\n- Added a long position on ADA perpetual contract for $30 size at 3x leverage, average long price ~0.67957.\n\nSummary:\n- Holdings remain BTC, ETH, ADA (now increased), and ZEC longs; new short position on XRP.\n- Cash balance will be approximately $5 remaining.\n- Strategy: Hold existing BTC and ETH longs; capitalize on ADA's momentum with added long; use short on weak XRP setup to profit from downside.\n\nI will continue monitoring prices and news for further opportunities or needed adjustments. Let me know if you want me to research or manage any other cryptocurrencies."}
memory_utils.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 show_memories(n: int = 5) -> str:
32
+ """
33
+ Return a formatted string showing the latest n memories.
34
+ Useful for displaying in Gradio or CLI.
35
+ """
36
+ memories = load_memories(n)
37
+ if not memories:
38
+ return "No memories yet."
39
+ formatted = "\n\n".join(
40
+ [f"🕒 Memory {i+1}: {m}" for i, m in enumerate(memories[::-1])]
41
+ )
42
+ return formatted
43
+
44
+
45
+ def load_memories_df(n: Optional[int] = None):
46
+ """
47
+ Return recent memories as a pandas DataFrame (newest first).
48
+ """
49
+ with open("memories.json", "r", encoding="utf-8") as f:
50
+ lines = [json.loads(line) for line in f if line.strip()]
51
+ df = pd.DataFrame(lines)
52
+
53
+ return df.tail(n).iloc[::-1] if n else df.iloc[::-1]
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ pandas
3
+ numpy
4
+ python-dotenv
5
+ openai
6
+ openai-agents
7
+ fastmcp
8
+ cryptopanic-news
9
+ hyperliquid-python-sdk
10
+ eth-account
11
+ hype-accounts-server