- Change file mode from 644 to 755 for all project files - Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet" - Rename Miner.extra_meta_data to extra_metadata for consistency
797 lines
26 KiB
Python
Executable File
797 lines
26 KiB
Python
Executable File
"""Market making commands for AITBC CLI"""
|
|
|
|
import click
|
|
import json
|
|
import uuid
|
|
import httpx
|
|
from pathlib import Path
|
|
from typing import Optional, Dict, Any, List
|
|
from datetime import datetime, timedelta
|
|
from ..utils import output, error, success, warning
|
|
|
|
|
|
@click.group()
|
|
def market_maker():
|
|
"""Market making bot management commands"""
|
|
pass
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--exchange", required=True, help="Exchange name")
|
|
@click.option("--pair", required=True, help="Trading pair symbol (e.g., AITBC/BTC)")
|
|
@click.option("--spread", type=float, default=0.005, help="Bid-ask spread (as percentage)")
|
|
@click.option("--depth", type=float, default=1000000, help="Order book depth amount")
|
|
@click.option("--max-order-size", type=float, default=1000, help="Maximum order size")
|
|
@click.option("--min-order-size", type=float, default=10, help="Minimum order size")
|
|
@click.option("--target-inventory", type=float, default=50000, help="Target inventory balance")
|
|
@click.option("--rebalance-threshold", type=float, default=0.1, help="Inventory rebalance threshold")
|
|
@click.option("--description", help="Bot description")
|
|
@click.pass_context
|
|
def create(ctx, exchange: str, pair: str, spread: float, depth: float, max_order_size: float, min_order_size: float, target_inventory: float, rebalance_threshold: float, description: Optional[str]):
|
|
"""Create a new market making bot"""
|
|
|
|
# Generate unique bot ID
|
|
bot_id = f"mm_{exchange.lower()}_{pair.replace('/', '_')}_{str(uuid.uuid4())[:8]}"
|
|
|
|
# Create bot configuration
|
|
bot_config = {
|
|
"bot_id": bot_id,
|
|
"exchange": exchange,
|
|
"pair": pair,
|
|
"status": "stopped",
|
|
"strategy": "basic_market_making",
|
|
"config": {
|
|
"spread": spread,
|
|
"depth": depth,
|
|
"max_order_size": max_order_size,
|
|
"min_order_size": min_order_size,
|
|
"target_inventory": target_inventory,
|
|
"rebalance_threshold": rebalance_threshold
|
|
},
|
|
"performance": {
|
|
"total_trades": 0,
|
|
"total_volume": 0.0,
|
|
"total_profit": 0.0,
|
|
"inventory_value": 0.0,
|
|
"orders_placed": 0,
|
|
"orders_filled": 0
|
|
},
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"last_updated": None,
|
|
"description": description or f"Market making bot for {pair} on {exchange}",
|
|
"current_orders": [],
|
|
"inventory": {
|
|
"base_asset": 0.0,
|
|
"quote_asset": target_inventory
|
|
}
|
|
}
|
|
|
|
# Store bot configuration
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
bots_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Load existing bots
|
|
bots = {}
|
|
if bots_file.exists():
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
# Add new bot
|
|
bots[bot_id] = bot_config
|
|
|
|
# Save bots
|
|
with open(bots_file, 'w') as f:
|
|
json.dump(bots, f, indent=2)
|
|
|
|
success(f"Market making bot created: {bot_id}")
|
|
output({
|
|
"bot_id": bot_id,
|
|
"exchange": exchange,
|
|
"pair": pair,
|
|
"status": "created",
|
|
"spread": spread,
|
|
"depth": depth,
|
|
"created_at": bot_config["created_at"]
|
|
})
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Bot ID to configure")
|
|
@click.option("--spread", type=float, help="New bid-ask spread")
|
|
@click.option("--depth", type=float, help="New order book depth")
|
|
@click.option("--max-order-size", type=float, help="New maximum order size")
|
|
@click.option("--target-inventory", type=float, help="New target inventory")
|
|
@click.option("--rebalance-threshold", type=float, help="New rebalance threshold")
|
|
@click.pass_context
|
|
def config(ctx, bot_id: str, spread: Optional[float], depth: Optional[float], max_order_size: Optional[float], target_inventory: Optional[float], rebalance_threshold: Optional[float]):
|
|
"""Configure market making bot parameters"""
|
|
|
|
# Load bots
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
if not bots_file.exists():
|
|
error("No market making bots found.")
|
|
return
|
|
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
if bot_id not in bots:
|
|
error(f"Bot '{bot_id}' not found.")
|
|
return
|
|
|
|
bot = bots[bot_id]
|
|
|
|
# Update configuration
|
|
config_updates = {}
|
|
if spread is not None:
|
|
bot["config"]["spread"] = spread
|
|
config_updates["spread"] = spread
|
|
if depth is not None:
|
|
bot["config"]["depth"] = depth
|
|
config_updates["depth"] = depth
|
|
if max_order_size is not None:
|
|
bot["config"]["max_order_size"] = max_order_size
|
|
config_updates["max_order_size"] = max_order_size
|
|
if target_inventory is not None:
|
|
bot["config"]["target_inventory"] = target_inventory
|
|
config_updates["target_inventory"] = target_inventory
|
|
if rebalance_threshold is not None:
|
|
bot["config"]["rebalance_threshold"] = rebalance_threshold
|
|
config_updates["rebalance_threshold"] = rebalance_threshold
|
|
|
|
if not config_updates:
|
|
error("No configuration updates provided.")
|
|
return
|
|
|
|
# Update timestamp
|
|
bot["last_updated"] = datetime.utcnow().isoformat()
|
|
|
|
# Save bots
|
|
with open(bots_file, 'w') as f:
|
|
json.dump(bots, f, indent=2)
|
|
|
|
success(f"Bot '{bot_id}' configuration updated")
|
|
output({
|
|
"bot_id": bot_id,
|
|
"config_updates": config_updates,
|
|
"updated_at": bot["last_updated"]
|
|
})
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Bot ID to start")
|
|
@click.option("--dry-run", is_flag=True, help="Run in simulation mode without real orders")
|
|
@click.pass_context
|
|
def start(ctx, bot_id: str, dry_run: bool):
|
|
"""Start a market making bot"""
|
|
|
|
# Load bots
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
if not bots_file.exists():
|
|
error("No market making bots found.")
|
|
return
|
|
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
if bot_id not in bots:
|
|
error(f"Bot '{bot_id}' not found.")
|
|
return
|
|
|
|
bot = bots[bot_id]
|
|
|
|
# Check if bot is already running
|
|
if bot["status"] == "running":
|
|
warning(f"Bot '{bot_id}' is already running.")
|
|
return
|
|
|
|
# Update bot status
|
|
bot["status"] = "running" if not dry_run else "simulation"
|
|
bot["started_at"] = datetime.utcnow().isoformat()
|
|
bot["last_updated"] = datetime.utcnow().isoformat()
|
|
bot["dry_run"] = dry_run
|
|
|
|
# Initialize performance tracking for this run
|
|
bot["current_run"] = {
|
|
"started_at": bot["started_at"],
|
|
"orders_placed": 0,
|
|
"orders_filled": 0,
|
|
"total_volume": 0.0,
|
|
"total_profit": 0.0
|
|
}
|
|
|
|
# Save bots
|
|
with open(bots_file, 'w') as f:
|
|
json.dump(bots, f, indent=2)
|
|
|
|
mode = "simulation" if dry_run else "live"
|
|
success(f"Bot '{bot_id}' started in {mode} mode")
|
|
output({
|
|
"bot_id": bot_id,
|
|
"status": bot["status"],
|
|
"mode": mode,
|
|
"started_at": bot["started_at"],
|
|
"exchange": bot["exchange"],
|
|
"pair": bot["pair"]
|
|
})
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Bot ID to stop")
|
|
@click.pass_context
|
|
def stop(ctx, bot_id: str):
|
|
"""Stop a market making bot"""
|
|
|
|
# Load bots
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
if not bots_file.exists():
|
|
error("No market making bots found.")
|
|
return
|
|
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
if bot_id not in bots:
|
|
error(f"Bot '{bot_id}' not found.")
|
|
return
|
|
|
|
bot = bots[bot_id]
|
|
|
|
# Check if bot is running
|
|
if bot["status"] not in ["running", "simulation"]:
|
|
warning(f"Bot '{bot_id}' is not currently running.")
|
|
return
|
|
|
|
# Update bot status
|
|
bot["status"] = "stopped"
|
|
bot["stopped_at"] = datetime.utcnow().isoformat()
|
|
bot["last_updated"] = datetime.utcnow().isoformat()
|
|
|
|
# Cancel all current orders (simulation)
|
|
bot["current_orders"] = []
|
|
|
|
# Save bots
|
|
with open(bots_file, 'w') as f:
|
|
json.dump(bots, f, indent=2)
|
|
|
|
success(f"Bot '{bot_id}' stopped")
|
|
output({
|
|
"bot_id": bot_id,
|
|
"status": "stopped",
|
|
"stopped_at": bot["stopped_at"],
|
|
"final_performance": bot.get("current_run", {})
|
|
})
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", help="Specific bot ID to check")
|
|
@click.option("--exchange", help="Filter by exchange")
|
|
@click.option("--pair", help="Filter by trading pair")
|
|
@click.pass_context
|
|
def performance(ctx, bot_id: Optional[str], exchange: Optional[str], pair: Optional[str]):
|
|
"""Get performance metrics for market making bots"""
|
|
|
|
# Load bots
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
if not bots_file.exists():
|
|
error("No market making bots found.")
|
|
return
|
|
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
# Filter bots
|
|
performance_data = {}
|
|
|
|
for current_bot_id, bot in bots.items():
|
|
if bot_id and current_bot_id != bot_id:
|
|
continue
|
|
if exchange and bot["exchange"] != exchange:
|
|
continue
|
|
if pair and bot["pair"] != pair:
|
|
continue
|
|
|
|
# Calculate performance metrics
|
|
perf = bot.get("performance", {})
|
|
current_run = bot.get("current_run", {})
|
|
|
|
bot_performance = {
|
|
"bot_id": current_bot_id,
|
|
"exchange": bot["exchange"],
|
|
"pair": bot["pair"],
|
|
"status": bot["status"],
|
|
"created_at": bot["created_at"],
|
|
"total_trades": perf.get("total_trades", 0),
|
|
"total_volume": perf.get("total_volume", 0.0),
|
|
"total_profit": perf.get("total_profit", 0.0),
|
|
"orders_placed": perf.get("orders_placed", 0),
|
|
"orders_filled": perf.get("orders_filled", 0),
|
|
"fill_rate": (perf.get("orders_filled", 0) / max(perf.get("orders_placed", 1), 1)) * 100,
|
|
"current_inventory": bot.get("inventory", {}),
|
|
"current_orders": len(bot.get("current_orders", [])),
|
|
"strategy": bot.get("strategy", "unknown"),
|
|
"config": bot.get("config", {})
|
|
}
|
|
|
|
# Add current run data if available
|
|
if current_run:
|
|
bot_performance["current_run"] = current_run
|
|
if "started_at" in current_run:
|
|
start_time = datetime.fromisoformat(current_run["started_at"].replace('Z', '+00:00'))
|
|
runtime = datetime.utcnow() - start_time
|
|
bot_performance["run_time_hours"] = runtime.total_seconds() / 3600
|
|
|
|
performance_data[current_bot_id] = bot_performance
|
|
|
|
if not performance_data:
|
|
error("No market making bots found matching the criteria.")
|
|
return
|
|
|
|
output({
|
|
"performance_data": performance_data,
|
|
"total_bots": len(performance_data),
|
|
"generated_at": datetime.utcnow().isoformat()
|
|
})
|
|
|
|
|
|
@market_maker.command()
|
|
@click.pass_context
|
|
def list(ctx):
|
|
"""List all market making bots"""
|
|
|
|
# Load bots
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
if not bots_file.exists():
|
|
warning("No market making bots found.")
|
|
return
|
|
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
# Format bot list
|
|
bot_list = []
|
|
for bot_id, bot in bots.items():
|
|
bot_info = {
|
|
"bot_id": bot_id,
|
|
"exchange": bot["exchange"],
|
|
"pair": bot["pair"],
|
|
"status": bot["status"],
|
|
"strategy": bot.get("strategy", "unknown"),
|
|
"created_at": bot["created_at"],
|
|
"last_updated": bot.get("last_updated"),
|
|
"total_trades": bot.get("performance", {}).get("total_trades", 0),
|
|
"current_orders": len(bot.get("current_orders", []))
|
|
}
|
|
bot_list.append(bot_info)
|
|
|
|
output({
|
|
"market_makers": bot_list,
|
|
"total_bots": len(bot_list),
|
|
"running_bots": len([b for b in bot_list if b["status"] in ["running", "simulation"]]),
|
|
"stopped_bots": len([b for b in bot_list if b["status"] == "stopped"])
|
|
})
|
|
|
|
|
|
@market_maker.command()
|
|
@click.argument("bot_id")
|
|
@click.pass_context
|
|
def status(ctx, bot_id: str):
|
|
"""Get detailed status of a specific market making bot"""
|
|
|
|
# Load bots
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
if not bots_file.exists():
|
|
error("No market making bots found.")
|
|
return
|
|
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
if bot_id not in bots:
|
|
error(f"Bot '{bot_id}' not found.")
|
|
return
|
|
|
|
bot = bots[bot_id]
|
|
|
|
# Calculate uptime if running
|
|
uptime_hours = None
|
|
if bot["status"] in ["running", "simulation"] and "started_at" in bot:
|
|
start_time = datetime.fromisoformat(bot["started_at"].replace('Z', '+00:00'))
|
|
uptime = datetime.utcnow() - start_time
|
|
uptime_hours = uptime.total_seconds() / 3600
|
|
|
|
output({
|
|
"bot_id": bot_id,
|
|
"exchange": bot["exchange"],
|
|
"pair": bot["pair"],
|
|
"status": bot["status"],
|
|
"strategy": bot.get("strategy", "unknown"),
|
|
"config": bot.get("config", {}),
|
|
"performance": bot.get("performance", {}),
|
|
"inventory": bot.get("inventory", {}),
|
|
"current_orders": bot.get("current_orders", []),
|
|
"created_at": bot["created_at"],
|
|
"last_updated": bot.get("last_updated"),
|
|
"started_at": bot.get("started_at"),
|
|
"stopped_at": bot.get("stopped_at"),
|
|
"uptime_hours": uptime_hours,
|
|
"dry_run": bot.get("dry_run", False),
|
|
"description": bot.get("description")
|
|
})
|
|
|
|
|
|
@market_maker.command()
|
|
@click.argument("bot_id")
|
|
@click.pass_context
|
|
def remove(ctx, bot_id: str):
|
|
"""Remove a market making bot"""
|
|
|
|
# Load bots
|
|
bots_file = Path.home() / ".aitbc" / "market_makers.json"
|
|
if not bots_file.exists():
|
|
error("No market making bots found.")
|
|
return
|
|
|
|
with open(bots_file, 'r') as f:
|
|
bots = json.load(f)
|
|
|
|
if bot_id not in bots:
|
|
error(f"Bot '{bot_id}' not found.")
|
|
return
|
|
|
|
bot = bots[bot_id]
|
|
|
|
# Check if bot is running
|
|
if bot["status"] in ["running", "simulation"]:
|
|
error(f"Cannot remove bot '{bot_id}' while it is running. Stop it first.")
|
|
return
|
|
|
|
# Remove bot
|
|
del bots[bot_id]
|
|
|
|
# Save bots
|
|
with open(bots_file, 'w') as f:
|
|
json.dump(bots, f, indent=2)
|
|
|
|
success(f"Market making bot '{bot_id}' removed")
|
|
output({
|
|
"bot_id": bot_id,
|
|
"status": "removed",
|
|
"exchange": bot["exchange"],
|
|
"pair": bot["pair"]
|
|
})
|
|
|
|
|
|
@click.group()
|
|
def market_maker():
|
|
"""Market making operations"""
|
|
pass
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--exchange", required=True, help="Exchange name (e.g., Binance, Coinbase)")
|
|
@click.option("--pair", required=True, help="Trading pair (e.g., AITBC/BTC)")
|
|
@click.option("--spread", type=float, default=0.001, help="Bid-ask spread (as percentage)")
|
|
@click.option("--depth", type=int, default=5, help="Order book depth levels")
|
|
@click.option("--base-balance", type=float, help="Base asset balance for market making")
|
|
@click.option("--quote-balance", type=float, help="Quote asset balance for market making")
|
|
@click.option("--min-order-size", type=float, help="Minimum order size")
|
|
@click.option("--max-order-size", type=float, help="Maximum order size")
|
|
@click.option("--strategy", default="simple", help="Market making strategy")
|
|
@click.pass_context
|
|
def create(ctx, exchange: str, pair: str, spread: float, depth: int,
|
|
base_balance: Optional[float], quote_balance: Optional[float],
|
|
min_order_size: Optional[float], max_order_size: Optional[float],
|
|
strategy: str):
|
|
"""Create a new market making bot"""
|
|
config = ctx.obj['config']
|
|
|
|
bot_config = {
|
|
"exchange": exchange,
|
|
"pair": pair,
|
|
"spread": spread,
|
|
"depth": depth,
|
|
"strategy": strategy,
|
|
"status": "created"
|
|
}
|
|
|
|
if base_balance is not None:
|
|
bot_config["base_balance"] = base_balance
|
|
if quote_balance is not None:
|
|
bot_config["quote_balance"] = quote_balance
|
|
if min_order_size is not None:
|
|
bot_config["min_order_size"] = min_order_size
|
|
if max_order_size is not None:
|
|
bot_config["max_order_size"] = max_order_size
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(
|
|
f"{config.coordinator_url}/api/v1/market-maker/create",
|
|
json=bot_config,
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
success(f"Market maker bot created for '{pair}' on '{exchange}'!")
|
|
success(f"Bot ID: {result.get('bot_id')}")
|
|
output(result, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to create market maker: {response.status_code}")
|
|
if response.text:
|
|
error(f"Error details: {response.text}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Market maker bot ID")
|
|
@click.option("--spread", type=float, help="New bid-ask spread")
|
|
@click.option("--depth", type=int, help="New order book depth")
|
|
@click.option("--base-balance", type=float, help="New base asset balance")
|
|
@click.option("--quote-balance", type=float, help="New quote asset balance")
|
|
@click.option("--min-order-size", type=float, help="New minimum order size")
|
|
@click.option("--max-order-size", type=float, help="New maximum order size")
|
|
@click.option("--strategy", help="New market making strategy")
|
|
@click.pass_context
|
|
def config(ctx, bot_id: str, spread: Optional[float], depth: Optional[int],
|
|
base_balance: Optional[float], quote_balance: Optional[float],
|
|
min_order_size: Optional[float], max_order_size: Optional[float],
|
|
strategy: Optional[str]):
|
|
"""Configure market maker bot settings"""
|
|
config = ctx.obj['config']
|
|
|
|
updates = {}
|
|
if spread is not None:
|
|
updates["spread"] = spread
|
|
if depth is not None:
|
|
updates["depth"] = depth
|
|
if base_balance is not None:
|
|
updates["base_balance"] = base_balance
|
|
if quote_balance is not None:
|
|
updates["quote_balance"] = quote_balance
|
|
if min_order_size is not None:
|
|
updates["min_order_size"] = min_order_size
|
|
if max_order_size is not None:
|
|
updates["max_order_size"] = max_order_size
|
|
if strategy is not None:
|
|
updates["strategy"] = strategy
|
|
|
|
if not updates:
|
|
error("No configuration updates provided")
|
|
return
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(
|
|
f"{config.coordinator_url}/api/v1/market-maker/config/{bot_id}",
|
|
json=updates,
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
success(f"Market maker {bot_id} configured successfully!")
|
|
output(result, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to configure market maker: {response.status_code}")
|
|
if response.text:
|
|
error(f"Error details: {response.text}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Market maker bot ID")
|
|
@click.option("--dry-run", is_flag=True, help="Test run without executing real trades")
|
|
@click.pass_context
|
|
def start(ctx, bot_id: str, dry_run: bool):
|
|
"""Start market maker bot"""
|
|
config = ctx.obj['config']
|
|
|
|
start_data = {
|
|
"dry_run": dry_run
|
|
}
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(
|
|
f"{config.coordinator_url}/api/v1/market-maker/start/{bot_id}",
|
|
json=start_data,
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
mode = " (dry run)" if dry_run else ""
|
|
success(f"Market maker {bot_id} started{mode}!")
|
|
output(result, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to start market maker: {response.status_code}")
|
|
if response.text:
|
|
error(f"Error details: {response.text}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Market maker bot ID")
|
|
@click.pass_context
|
|
def stop(ctx, bot_id: str):
|
|
"""Stop market maker bot"""
|
|
config = ctx.obj['config']
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.post(
|
|
f"{config.coordinator_url}/api/v1/market-maker/stop/{bot_id}",
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
success(f"Market maker {bot_id} stopped!")
|
|
output(result, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to stop market maker: {response.status_code}")
|
|
if response.text:
|
|
error(f"Error details: {response.text}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", help="Specific bot ID to check")
|
|
@click.option("--exchange", help="Filter by exchange")
|
|
@click.option("--pair", help="Filter by trading pair")
|
|
@click.option("--status", help="Filter by status (running, stopped, created)")
|
|
@click.pass_context
|
|
def performance(ctx, bot_id: Optional[str], exchange: Optional[str],
|
|
pair: Optional[str], status: Optional[str]):
|
|
"""Get market maker performance analytics"""
|
|
config = ctx.obj['config']
|
|
|
|
params = {}
|
|
if bot_id:
|
|
params["bot_id"] = bot_id
|
|
if exchange:
|
|
params["exchange"] = exchange
|
|
if pair:
|
|
params["pair"] = pair
|
|
if status:
|
|
params["status"] = status
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.get(
|
|
f"{config.coordinator_url}/api/v1/market-maker/performance",
|
|
params=params,
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
performance_data = response.json()
|
|
success("Market maker performance:")
|
|
output(performance_data, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to get performance data: {response.status_code}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", help="Specific bot ID to list")
|
|
@click.option("--exchange", help="Filter by exchange")
|
|
@click.option("--pair", help="Filter by trading pair")
|
|
@click.option("--status", help="Filter by status")
|
|
@click.pass_context
|
|
def list(ctx, bot_id: Optional[str], exchange: Optional[str],
|
|
pair: Optional[str], status: Optional[str]):
|
|
"""List market maker bots"""
|
|
config = ctx.obj['config']
|
|
|
|
params = {}
|
|
if bot_id:
|
|
params["bot_id"] = bot_id
|
|
if exchange:
|
|
params["exchange"] = exchange
|
|
if pair:
|
|
params["pair"] = pair
|
|
if status:
|
|
params["status"] = status
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.get(
|
|
f"{config.coordinator_url}/api/v1/market-maker/list",
|
|
params=params,
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
bots = response.json()
|
|
success("Market maker bots:")
|
|
output(bots, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to list market makers: {response.status_code}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Market maker bot ID")
|
|
@click.option("--hours", type=int, default=24, help="Hours of history to retrieve")
|
|
@click.pass_context
|
|
def history(ctx, bot_id: str, hours: int):
|
|
"""Get market maker trading history"""
|
|
config = ctx.obj['config']
|
|
|
|
params = {
|
|
"hours": hours
|
|
}
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.get(
|
|
f"{config.coordinator_url}/api/v1/market-maker/history/{bot_id}",
|
|
params=params,
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
history_data = response.json()
|
|
success(f"Market maker {bot_id} history (last {hours} hours):")
|
|
output(history_data, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to get market maker history: {response.status_code}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.option("--bot-id", required=True, help="Market maker bot ID")
|
|
@click.pass_context
|
|
def status(ctx, bot_id: str):
|
|
"""Get market maker bot status"""
|
|
config = ctx.obj['config']
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.get(
|
|
f"{config.coordinator_url}/api/v1/market-maker/status/{bot_id}",
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
status_data = response.json()
|
|
success(f"Market maker {bot_id} status:")
|
|
output(status_data, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to get market maker status: {response.status_code}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|
|
|
|
|
|
@market_maker.command()
|
|
@click.pass_context
|
|
def strategies(ctx):
|
|
"""List available market making strategies"""
|
|
config = ctx.obj['config']
|
|
|
|
try:
|
|
with httpx.Client() as client:
|
|
response = client.get(
|
|
f"{config.coordinator_url}/api/v1/market-maker/strategies",
|
|
timeout=10
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
strategies = response.json()
|
|
success("Available market making strategies:")
|
|
output(strategies, ctx.obj['output_format'])
|
|
else:
|
|
error(f"Failed to list strategies: {response.status_code}")
|
|
except Exception as e:
|
|
error(f"Network error: {e}")
|