Skip to content

Commit b0bc0b4

Browse files
feat(src): support crypto market (#217)
🪙 BitMEX Crypto Trading System New Features Trading System - New market type: Perpetual contract trading for 15+ cryptocurrencies - Synchronized trading: Daily at 3 PM ET (Mon-Fri), aligned with stock market schedule - Paper trading: $1,000 initial capital per agent Crypto-specific metrics: - Funding rates (8-hour carry costs) - `live_trade_bench/systems/bitmex_system.py:152-153` - Funding rate fetching - `live_trade_bench/agents/bitmex_agent.py:55-58` - Funding rate in LLM prompts - Order book depth (liquidity analysis) - `live_trade_bench/systems/bitmex_system.py:157-165` - Bid/ask depth calculation - `live_trade_bench/fetchers/bitmex_fetcher.py` - Order book API integration - Open interest tracking - `live_trade_bench/systems/bitmex_system.py:181` - Open interest data - `live_trade_bench/agents/bitmex_agent.py:55-58` - Open interest display - Mark price with 10-day history - `live_trade_bench/fetchers/bitmex_fetcher.py:199` - Price history fetching - `live_trade_bench/systems/bitmex_system.py:145-150` - Historical price integration 🔧 Backend Infrastructure Trading Cycles: - BitMEX system integrated into production scheduler - `backend/app/main.py:265-275` - Synchronized with stock/polymarket at 3 PM ET (Mon-Fri) - `backend/app/main.py:269` - 10-minute price updates during market hours only - `backend/app/main.py:305-311,` backend/app/price_data.py:534-540 Data Pipelines: - News fetching for crypto (Google News) - `live_trade_bench/fetchers/news_fetcher.py:167`, ` live_trade_bench/systems/bitmex_system.py:229-252` - Social media monitoring (Reddit) - `live_trade_bench/fetchers/reddit_fetcher.py`, ` live_trade_bench/systems/bitmex_system.py:265-328` - Price updates with BitMEX API - `backend/app/price_data.py:524-559,` ` live_trade_bench/fetchers/bitmex_fetcher.py` - Market hours enforcement - `backend/app/price_data.py:534-540` API Endpoints: - /api/news/{market_type} - BitMEX supported - `backend/app/routers/news.py:13` - /api/social/{market_type} - BitMEX supported - `backend/app/routers/social.py:13` - BitMEX data integrated into models and system endpoints 🎨 Frontend Integration UI Components: - BitMEX tabs added to News and SocialMedia pages - Crypto-specific color theming in allocation charts - BitMEX dashboard support (routing ready) 🧪 Backtesting Enhancements Updated `examples/backtest_demo.py`: - BitMEX support added alongside stock/polymarket - Parallel backtesting for all three markets - Crypto benchmark models (BTC-HOLD, ETH-HOLD) - All 21 models tested (removed flagship-only filter) 🔧 System Reliability - Synchronized schedules: All trading systems update together at 3 PM ET <img width="2021" height="1052" alt="Screenshot 2025-11-08 at 14 29 23" src="https://github.com/user-attachments/assets/3120ecef-f4cc-435b-a1ea-dd6d9ef106f9" /> <img width="2022" height="1053" alt="Screenshot 2025-11-08 at 14 30 06" src="https://github.com/user-attachments/assets/5c189c4a-96d8-466c-a948-29c4d944d172" />
1 parent 59ac754 commit b0bc0b4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3489
-324
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,13 @@ cython_debug/
160160
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
161161
#.idea/
162162
backend/**/*.json
163+
164+
# Test data (generated during tests, should not be committed)
165+
tests/fixtures/test_data/
166+
167+
.claude
168+
.idea
169+
CLAUDE.md
170+
171+
# macOS system files
172+
.DS_Store

backend/app/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def get_base_model_configs() -> List[Tuple[str, str]]:
3434
("GPT-o3", "openai/o3-2025-04-16"),
3535
("Claude-Opus-4.1", "anthropic/claude-opus-4-1-20250805"),
3636
("Claude-Opus-4", "anthropic/claude-opus-4-20250514"),
37+
("Claude-Sonnet-4.5", "anthropic/claude-sonnet-4-5-20250929"),
3738
("Claude-Sonnet-4", "anthropic/claude-sonnet-4-20250514"),
3839
("Claude-Sonnet-3.7", "anthropic/claude-3-7-sonnet-latest"),
3940
("Gemini-2.5-Flash", "gemini/gemini-2.5-flash"),
@@ -63,6 +64,7 @@ def get_base_model_configs() -> List[Tuple[str, str]]:
6364
TRADING_CONFIG = {
6465
"initial_cash_stock": 1000,
6566
"initial_cash_polymarket": 500,
67+
"initial_cash_bitmex": 1000,
6668
"max_consecutive_failures": 3,
6769
"recovery_wait_time": 3600,
6870
"error_retry_time": 600,

backend/app/main.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
from apscheduler.executors.pool import ThreadPoolExecutor
88
from apscheduler.schedulers.background import BackgroundScheduler
9+
10+
# Load environment variables from .env file
11+
from dotenv import load_dotenv
912
from fastapi import FastAPI, HTTPException
1013
from fastapi.middleware.cors import CORSMiddleware
1114
from fastapi.responses import FileResponse
@@ -19,7 +22,11 @@
1922
MockFetcherPolymarketSystem,
2023
MockFetcherStockSystem,
2124
)
22-
from live_trade_bench.systems import PolymarketPortfolioSystem, StockPortfolioSystem
25+
from live_trade_bench.systems import (
26+
BitMEXPortfolioSystem,
27+
PolymarketPortfolioSystem,
28+
StockPortfolioSystem,
29+
)
2330

2431
from .config import (
2532
ALLOWED_ORIGINS,
@@ -37,16 +44,20 @@
3744
from .news_data import update_news_data
3845
from .price_data import (
3946
get_next_price_update_time,
47+
update_bitmex_prices_and_values,
4048
update_polymarket_prices_and_values,
4149
update_stock_prices_and_values,
4250
)
4351
from .routers import models, news, social, system
4452
from .social_data import update_social_data
4553
from .system_data import update_system_status
4654

55+
load_dotenv()
56+
4757
# Global system instances - Initialize immediately
4858
stock_system = None
4959
polymarket_system = None
60+
bitmex_system = None
5061
# Background scheduler instance; assigned during startup to keep reference alive
5162
scheduler = None
5263

@@ -68,6 +79,7 @@
6879
# Initialize systems immediately when module loads
6980
stock_system = STOCK_SYSTEMS[STOCK_MOCK_MODE].get_instance()
7081
polymarket_system = POLYMARKET_SYSTEMS[POLYMARKET_MOCK_MODE].get_instance()
82+
bitmex_system = BitMEXPortfolioSystem()
7183

7284
# Add agents for real systems
7385
if STOCK_MOCK_MODE == MockMode.NONE:
@@ -78,13 +90,18 @@
7890
for display_name, model_id in get_base_model_configs():
7991
polymarket_system.add_agent(display_name, 500.0, model_id)
8092

93+
# Add BitMEX agents (paper trading with $1,000 each)
94+
for display_name, model_id in get_base_model_configs():
95+
bitmex_system.add_agent(display_name, 1000.0, model_id)
96+
8197
# 🆕 加载历史数据到Account内存中
8298
print("🔄 Loading historical data to account memory...")
83-
load_historical_data_to_accounts(stock_system, polymarket_system)
99+
load_historical_data_to_accounts(stock_system, polymarket_system, bitmex_system)
84100
print("✅ Historical data loading completed")
85101

86102
stock_system.initialize_for_live()
87103
polymarket_system.initialize_for_live()
104+
bitmex_system.initialize_for_live()
88105

89106

90107
def get_stock_system():
@@ -99,6 +116,12 @@ def get_polymarket_system():
99116
return polymarket_system
100117

101118

119+
def get_bitmex_system():
120+
"""Get the BitMEX system instance."""
121+
global bitmex_system
122+
return bitmex_system
123+
124+
102125
logging.basicConfig(level=logging.INFO)
103126
logger = logging.getLogger(__name__)
104127

@@ -194,11 +217,21 @@ def load_backtest_as_initial_data():
194217
def safe_generate_models_data():
195218
if should_run_trading_cycle():
196219
logger.info("🕐 Running trading cycle at market close time...")
197-
generate_models_data(stock_system, polymarket_system)
220+
generate_models_data(stock_system, polymarket_system, bitmex_system)
198221
else:
199222
logger.info("⏰ Skipping trading cycle - not in market time window")
200223

201224

225+
def safe_generate_bitmex_cycle():
226+
"""Run BitMEX trading cycle (24/7 crypto markets)."""
227+
logger.info("🔄 Running BitMEX trading cycle...")
228+
try:
229+
bitmex_system.run_cycle()
230+
logger.info("✅ BitMEX cycle completed")
231+
except Exception as e:
232+
logger.error(f"❌ BitMEX cycle failed: {e}")
233+
234+
202235
def schedule_background_tasks(scheduler: BackgroundScheduler):
203236
from datetime import timedelta
204237

@@ -227,6 +260,20 @@ def schedule_background_tasks(scheduler: BackgroundScheduler):
227260
)
228261
logger.info(f"📅 Scheduled trading job for UTC {schedule_hour}:00 ({schedule_desc})")
229262

263+
# Schedule BitMEX cycle at same time as stock (3 PM ET, Mon-Fri)
264+
# This prevents file conflicts and ensures all systems update together
265+
scheduler.add_job(
266+
safe_generate_bitmex_cycle,
267+
"cron",
268+
day_of_week="mon-fri",
269+
hour=schedule_hour, # Same as stock (19 or 20 UTC for 3 PM ET)
270+
minute=0,
271+
timezone="UTC",
272+
id="bitmex_daily_cycle",
273+
replace_existing=True,
274+
)
275+
logger.info(f"📅 Scheduled BitMEX cycle for UTC {schedule_hour}:00 ({schedule_desc}), Mon-Fri")
276+
230277
price_interval = UPDATE_FREQUENCY["realtime_prices"]
231278
logger.info(
232279
f"📈 Scheduled stock price update job for every {price_interval} seconds ({price_interval//60} minutes)"
@@ -250,6 +297,19 @@ def schedule_background_tasks(scheduler: BackgroundScheduler):
250297
id="update_polymarket_prices",
251298
replace_existing=True,
252299
)
300+
301+
# BitMEX price updates (every 10 minutes, 24/7)
302+
bitmex_interval = 600 # 10 minutes
303+
logger.info(
304+
f"📈 Scheduled BitMEX price update job for every {bitmex_interval} seconds ({bitmex_interval//60} minutes)"
305+
)
306+
scheduler.add_job(
307+
update_bitmex_prices_and_values,
308+
"interval",
309+
seconds=bitmex_interval,
310+
id="update_bitmex_prices",
311+
replace_existing=True,
312+
)
253313
scheduler.add_job(
254314
update_news_data,
255315
"interval",

backend/app/models_data.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def _create_model_data(agent, account, market_type):
104104
"timestamp": snapshot["timestamp"],
105105
"profit": snapshot["profit"],
106106
"totalValue": snapshot["total_value"],
107+
"performance": snapshot.get("performance", 0),
107108
}
108109
for snapshot in allocation_history
109110
],
@@ -122,7 +123,7 @@ def _serialize_positions(model_data):
122123
return model_data
123124

124125

125-
def load_historical_data_to_accounts(stock_system, polymarket_system):
126+
def load_historical_data_to_accounts(stock_system, polymarket_system, bitmex_system=None):
126127
"""Load historical data to account memory on every startup.
127128
128129
This function ALWAYS loads data to restore account state, regardless of whether
@@ -158,7 +159,14 @@ def load_historical_data_to_accounts(stock_system, polymarket_system):
158159
print(f" 📊 {model_name}: Benchmark model (will be preserved)")
159160
continue
160161

161-
system = stock_system if category == "stock" else polymarket_system
162+
if category == "stock":
163+
system = stock_system
164+
elif category == "polymarket":
165+
system = polymarket_system
166+
elif category == "bitmex" and bitmex_system is not None:
167+
system = bitmex_system
168+
else:
169+
continue
162170

163171
account = None
164172
for agent_name, acc in system.accounts.items():
@@ -199,15 +207,17 @@ def restore_account_from_historical_data(account, historical_model_data):
199207
account.total_fees = historical_model_data.get("total_fees", 0.0)
200208

201209

202-
def generate_models_data(stock_system, polymarket_system) -> None:
210+
def generate_models_data(stock_system, polymarket_system, bitmex_system=None) -> None:
203211
"""Generate and save model data for all systems"""
204212
try:
205-
print("🚀 Starting data generation for both markets...")
213+
print("🚀 Starting data generation for all markets...")
206214
all_market_data = []
207215

208216
existing_benchmarks = _preserve_existing_benchmarks()
209217

210218
systems = {"stock": stock_system, "polymarket": polymarket_system}
219+
if bitmex_system is not None:
220+
systems["bitmex"] = bitmex_system
211221

212222
for market_type, system in systems.items():
213223
print(f"--- Processing {market_type.upper()} market ---")

backend/app/news_data.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66
def update_news_data() -> None:
77
print("📰 Updating news data...")
88

9-
all_news_data = {"stock": [], "polymarket": []}
9+
all_news_data = {"stock": [], "polymarket": [], "bitmex": []}
1010

1111
try:
12-
from .main import get_polymarket_system, get_stock_system
12+
from .main import get_bitmex_system, get_polymarket_system, get_stock_system
1313

1414
stock_system = get_stock_system()
1515
polymarket_system = get_polymarket_system()
16+
bitmex_system = get_bitmex_system()
1617

17-
if not stock_system or not polymarket_system:
18+
if not stock_system or not polymarket_system or not bitmex_system:
1819
print("❌ Failed to get system instances")
1920
return
2021

@@ -23,23 +24,30 @@ def update_news_data() -> None:
2324
stock_system.initialize_for_live()
2425
if not polymarket_system.universe:
2526
polymarket_system.initialize_for_live()
27+
if not bitmex_system.universe:
28+
bitmex_system.initialize_for_live()
2629

2730
# Fetch market data
2831
stock_market_data = stock_system._fetch_market_data(for_date=None)
2932
polymarket_market_data = polymarket_system._fetch_market_data(for_date=None)
33+
bitmex_market_data = bitmex_system._fetch_market_data(for_date=None)
3034

3135
# Fetch news data
3236
stock_news = stock_system._fetch_news_data(stock_market_data, for_date=None)
3337
polymarket_news = polymarket_system._fetch_news_data(
3438
polymarket_market_data, for_date=None
3539
)
40+
bitmex_news = bitmex_system._fetch_news_data(bitmex_market_data, for_date=None)
3641

3742
all_news_data["stock"] = [
3843
item for sublist in stock_news.values() for item in sublist
3944
]
4045
all_news_data["polymarket"] = [
4146
item for sublist in polymarket_news.values() for item in sublist
4247
]
48+
all_news_data["bitmex"] = [
49+
item for sublist in bitmex_news.values() for item in sublist
50+
]
4351

4452
with open(NEWS_DATA_FILE, "w") as f:
4553
json.dump(all_news_data, f, indent=4)

0 commit comments

Comments
 (0)