mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-02 01:27:24 -04:00
Compare commits
4 Commits
v0.3.0-alp
...
v0.3.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| a16bac5d08 | |||
| 81b92e293a | |||
| b1b486dcc4 | |||
| 1bdfefae35 |
@@ -35,7 +35,7 @@ MAX_SIMULATION_DAYS=30
|
||||
AUTO_DOWNLOAD_PRICE_DATA=true
|
||||
|
||||
# Data Volume Configuration
|
||||
# Base directory for all persistent data (will contain data/, logs/, configs/ subdirectories)
|
||||
# Base directory for all persistent data (will contain data/ and configs/ subdirectories)
|
||||
# Use relative paths (./volumes) or absolute paths (/home/user/ai-trader-volumes)
|
||||
# Defaults to current directory (.) if not set
|
||||
VOLUME_PATH=.
|
||||
|
||||
30
DOCKER.md
30
DOCKER.md
@@ -154,10 +154,9 @@ docker-compose up
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
Docker Compose mounts three volumes for persistent data. By default, these are stored in the project directory:
|
||||
Docker Compose mounts two volumes for persistent data. By default, these are stored in the project directory:
|
||||
|
||||
- `./data:/app/data` - Price data and trading records
|
||||
- `./logs:/app/logs` - MCP service logs
|
||||
- `./configs:/app/configs` - Configuration files (allows editing configs without rebuilding)
|
||||
|
||||
### Custom Volume Location
|
||||
@@ -174,7 +173,6 @@ VOLUME_PATH=./volumes
|
||||
|
||||
This will store data in:
|
||||
- `/home/user/trading-data/data/`
|
||||
- `/home/user/trading-data/logs/`
|
||||
- `/home/user/trading-data/configs/`
|
||||
|
||||
**Note:** The directory structure is automatically created. You'll need to copy your existing configs:
|
||||
@@ -190,7 +188,7 @@ To reset all trading data:
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
rm -rf ${VOLUME_PATH:-.}/data/agent_data/* ${VOLUME_PATH:-.}/logs/*
|
||||
rm -rf ${VOLUME_PATH:-.}/data/agent_data/*
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
@@ -217,8 +215,7 @@ docker pull ghcr.io/xe138/ai-trader-server:latest
|
||||
```bash
|
||||
docker run --env-file .env \
|
||||
-v $(pwd)/data:/app/data \
|
||||
-v $(pwd)/logs:/app/logs \
|
||||
-p 8000-8003:8000-8003 \
|
||||
-p 8080:8080 \
|
||||
ghcr.io/xe138/ai-trader-server:latest
|
||||
```
|
||||
|
||||
@@ -234,9 +231,9 @@ docker pull ghcr.io/xe138/ai-trader-server:v1.0.0
|
||||
**Symptom:** Container exits immediately or errors about ports
|
||||
|
||||
**Solutions:**
|
||||
- Check ports 8000-8003 not already in use: `lsof -i :8000-8003`
|
||||
- View container logs: `docker-compose logs`
|
||||
- Check MCP service logs: `cat logs/math.log`
|
||||
- Check if API port 8080 is already in use: `lsof -i :8080`
|
||||
- Verify MCP services started by checking Docker logs for service startup messages
|
||||
|
||||
### Missing API Keys
|
||||
|
||||
@@ -258,12 +255,12 @@ docker pull ghcr.io/xe138/ai-trader-server:v1.0.0
|
||||
|
||||
### Permission Issues
|
||||
|
||||
**Symptom:** Cannot write to data or logs directories
|
||||
**Symptom:** Cannot write to data directory
|
||||
|
||||
**Solutions:**
|
||||
- Ensure directories writable: `chmod -R 755 data logs`
|
||||
- Ensure data directory is writable: `chmod -R 755 data`
|
||||
- Check volume mount permissions
|
||||
- May need to create directories first: `mkdir -p data logs`
|
||||
- May need to create directory first: `mkdir -p data`
|
||||
|
||||
### Container Keeps Restarting
|
||||
|
||||
@@ -298,13 +295,12 @@ docker buildx build --platform linux/amd64,linux/arm64 -t ai-trader-server .
|
||||
docker stats ai-trader-server
|
||||
```
|
||||
|
||||
### Access MCP Services Directly
|
||||
### Access API Directly
|
||||
|
||||
Services exposed on host:
|
||||
- Math: http://localhost:8000
|
||||
- Search: http://localhost:8001
|
||||
- Trade: http://localhost:8002
|
||||
- Price: http://localhost:8003
|
||||
API server exposed on host:
|
||||
- REST API: http://localhost:8080
|
||||
|
||||
MCP services run internally and are not exposed to the host.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p data logs data/agent_data
|
||||
RUN mkdir -p data data/agent_data
|
||||
|
||||
# Make entrypoint executable
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
@@ -29,6 +29,7 @@ from tools.deployment_config import (
|
||||
log_api_key_warning,
|
||||
get_deployment_mode
|
||||
)
|
||||
from agent.context_injector import ContextInjector
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
@@ -124,6 +125,9 @@ class BaseAgent:
|
||||
self.tools: Optional[List] = None
|
||||
self.model: Optional[ChatOpenAI] = None
|
||||
self.agent: Optional[Any] = None
|
||||
|
||||
# Context injector for MCP tools
|
||||
self.context_injector: Optional[ContextInjector] = None
|
||||
|
||||
# Data paths
|
||||
self.data_path = os.path.join(self.base_log_path, self.signature)
|
||||
@@ -169,16 +173,27 @@ class BaseAgent:
|
||||
print("⚠️ OpenAI base URL not set, using default")
|
||||
|
||||
try:
|
||||
# Create MCP client
|
||||
self.client = MultiServerMCPClient(self.mcp_config)
|
||||
# Create context injector for injecting signature and today_date into tool calls
|
||||
self.context_injector = ContextInjector(
|
||||
signature=self.signature,
|
||||
today_date=self.init_date # Will be updated per trading session
|
||||
)
|
||||
|
||||
# Create MCP client with interceptor
|
||||
self.client = MultiServerMCPClient(
|
||||
self.mcp_config,
|
||||
tool_interceptors=[self.context_injector]
|
||||
)
|
||||
|
||||
# Get tools
|
||||
self.tools = await self.client.get_tools()
|
||||
if not self.tools:
|
||||
raw_tools = await self.client.get_tools()
|
||||
if not raw_tools:
|
||||
print("⚠️ Warning: No MCP tools loaded. MCP services may not be running.")
|
||||
print(f" MCP configuration: {self.mcp_config}")
|
||||
self.tools = []
|
||||
else:
|
||||
print(f"✅ Loaded {len(self.tools)} MCP tools")
|
||||
print(f"✅ Loaded {len(raw_tools)} MCP tools")
|
||||
self.tools = raw_tools
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
f"❌ Failed to initialize MCP client: {e}\n"
|
||||
@@ -336,6 +351,10 @@ Summary:"""
|
||||
"""
|
||||
print(f"📈 Starting trading session: {today_date}")
|
||||
|
||||
# Update context injector with current trading date
|
||||
if self.context_injector:
|
||||
self.context_injector.today_date = today_date
|
||||
|
||||
# Clear conversation history for new trading day
|
||||
self.clear_conversation_history()
|
||||
|
||||
|
||||
55
agent/context_injector.py
Normal file
55
agent/context_injector.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Tool interceptor for injecting runtime context into MCP tool calls.
|
||||
|
||||
This interceptor automatically injects `signature` and `today_date` parameters
|
||||
into buy/sell tool calls to support concurrent multi-model simulations.
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Awaitable
|
||||
|
||||
|
||||
class ContextInjector:
|
||||
"""
|
||||
Intercepts tool calls to inject runtime context (signature, today_date).
|
||||
|
||||
Usage:
|
||||
interceptor = ContextInjector(signature="gpt-5", today_date="2025-10-01")
|
||||
client = MultiServerMCPClient(config, tool_interceptors=[interceptor])
|
||||
"""
|
||||
|
||||
def __init__(self, signature: str, today_date: str):
|
||||
"""
|
||||
Initialize context injector.
|
||||
|
||||
Args:
|
||||
signature: Model signature to inject
|
||||
today_date: Trading date to inject
|
||||
"""
|
||||
self.signature = signature
|
||||
self.today_date = today_date
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
request: Any, # MCPToolCallRequest
|
||||
handler: Callable[[Any], Awaitable[Any]]
|
||||
) -> Any: # MCPToolCallResult
|
||||
"""
|
||||
Intercept tool call and inject context parameters.
|
||||
|
||||
Args:
|
||||
request: Tool call request containing name and arguments
|
||||
handler: Async callable to execute the actual tool
|
||||
|
||||
Returns:
|
||||
Result from handler after injecting context
|
||||
"""
|
||||
# Inject signature and today_date for trade tools
|
||||
if request.name in ["buy", "sell"]:
|
||||
# Add signature and today_date to args if not present
|
||||
if "signature" not in request.args:
|
||||
request.args["signature"] = self.signature
|
||||
if "today_date" not in request.args:
|
||||
request.args["today_date"] = self.today_date
|
||||
|
||||
# Call the actual tool handler
|
||||
return await handler(request)
|
||||
@@ -52,10 +52,6 @@ class MCPServiceManager:
|
||||
}
|
||||
}
|
||||
|
||||
# Create logs directory
|
||||
self.log_dir = Path('logs')
|
||||
self.log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Set signal handlers
|
||||
signal.signal(signal.SIGINT, self.signal_handler)
|
||||
signal.signal(signal.SIGTERM, self.signal_handler)
|
||||
@@ -77,27 +73,23 @@ class MCPServiceManager:
|
||||
return False
|
||||
|
||||
try:
|
||||
# Start service process
|
||||
log_file = self.log_dir / f"{service_id}.log"
|
||||
|
||||
# Set PYTHONPATH to /app so services can import from tools module
|
||||
env = os.environ.copy()
|
||||
env['PYTHONPATH'] = str(Path.cwd())
|
||||
|
||||
with open(log_file, 'w') as f:
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, str(script_path)],
|
||||
stdout=f,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=Path.cwd(), # Use current working directory (/app)
|
||||
env=env # Pass environment with PYTHONPATH
|
||||
)
|
||||
|
||||
# Start service process (output goes to Docker logs)
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, str(script_path)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=Path.cwd(), # Use current working directory (/app)
|
||||
env=env # Pass environment with PYTHONPATH
|
||||
)
|
||||
|
||||
self.services[service_id] = {
|
||||
'process': process,
|
||||
'name': service_name,
|
||||
'port': port,
|
||||
'log_file': log_file
|
||||
'port': port
|
||||
}
|
||||
|
||||
print(f"✅ {service_name} service started (PID: {process.pid}, Port: {port})")
|
||||
@@ -167,15 +159,14 @@ class MCPServiceManager:
|
||||
print(f"✅ {service['name']} service running normally")
|
||||
else:
|
||||
print(f"❌ {service['name']} service failed to start")
|
||||
print(f" Please check logs: {service['log_file']}")
|
||||
print(f" Check Docker logs for details: docker logs ai-trader-server")
|
||||
|
||||
def print_service_info(self):
|
||||
"""Print service information"""
|
||||
print("\n📋 Service information:")
|
||||
for service_id, service in self.services.items():
|
||||
print(f" - {service['name']}: http://localhost:{service['port']} (PID: {service['process'].pid})")
|
||||
|
||||
print(f"\n📁 Log files location: {self.log_dir.absolute()}")
|
||||
|
||||
print("\n🛑 Press Ctrl+C to stop all services")
|
||||
|
||||
def keep_alive(self):
|
||||
|
||||
@@ -13,41 +13,45 @@ mcp = FastMCP("TradeTools")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def buy(symbol: str, amount: int) -> Dict[str, Any]:
|
||||
def buy(symbol: str, amount: int, signature: str = None, today_date: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Buy stock function
|
||||
|
||||
|
||||
This function simulates stock buying operations, including the following steps:
|
||||
1. Get current position and operation ID
|
||||
2. Get stock opening price for the day
|
||||
3. Validate buy conditions (sufficient cash)
|
||||
4. Update position (increase stock quantity, decrease cash)
|
||||
5. Record transaction to position.jsonl file
|
||||
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol, such as "AAPL", "MSFT", etc.
|
||||
amount: Buy quantity, must be a positive integer, indicating how many shares to buy
|
||||
|
||||
signature: Model signature (optional, will use config/env if not provided)
|
||||
today_date: Trading date (optional, will use config/env if not provided)
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]:
|
||||
- Success: Returns new position dictionary (containing stock quantity and cash balance)
|
||||
- Failure: Returns {"error": error message, ...} dictionary
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: Raised when SIGNATURE environment variable is not set
|
||||
|
||||
|
||||
Example:
|
||||
>>> result = buy("AAPL", 10)
|
||||
>>> print(result) # {"AAPL": 110, "MSFT": 5, "CASH": 5000.0, ...}
|
||||
"""
|
||||
# Step 1: Get environment variables and basic information
|
||||
# Get signature (model name) from environment variable, used to determine data storage path
|
||||
signature = get_config_value("SIGNATURE")
|
||||
# Get signature (model name) from parameter or fallback to config/env
|
||||
if signature is None:
|
||||
raise ValueError("SIGNATURE environment variable is not set")
|
||||
|
||||
# Get current trading date from environment variable
|
||||
today_date = get_config_value("TODAY_DATE")
|
||||
signature = get_config_value("SIGNATURE")
|
||||
if signature is None:
|
||||
raise ValueError("SIGNATURE not provided and environment variable is not set")
|
||||
|
||||
# Get current trading date from parameter or fallback to config/env
|
||||
if today_date is None:
|
||||
today_date = get_config_value("TODAY_DATE")
|
||||
|
||||
# Step 2: Get current latest position and operation ID
|
||||
# get_latest_position returns two values: position dictionary and current maximum operation ID
|
||||
@@ -104,41 +108,45 @@ def buy(symbol: str, amount: int) -> Dict[str, Any]:
|
||||
return new_position
|
||||
|
||||
@mcp.tool()
|
||||
def sell(symbol: str, amount: int) -> Dict[str, Any]:
|
||||
def sell(symbol: str, amount: int, signature: str = None, today_date: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Sell stock function
|
||||
|
||||
|
||||
This function simulates stock selling operations, including the following steps:
|
||||
1. Get current position and operation ID
|
||||
2. Get stock opening price for the day
|
||||
3. Validate sell conditions (position exists, sufficient quantity)
|
||||
4. Update position (decrease stock quantity, increase cash)
|
||||
5. Record transaction to position.jsonl file
|
||||
|
||||
|
||||
Args:
|
||||
symbol: Stock symbol, such as "AAPL", "MSFT", etc.
|
||||
amount: Sell quantity, must be a positive integer, indicating how many shares to sell
|
||||
|
||||
signature: Model signature (optional, will use config/env if not provided)
|
||||
today_date: Trading date (optional, will use config/env if not provided)
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]:
|
||||
- Success: Returns new position dictionary (containing stock quantity and cash balance)
|
||||
- Failure: Returns {"error": error message, ...} dictionary
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: Raised when SIGNATURE environment variable is not set
|
||||
|
||||
|
||||
Example:
|
||||
>>> result = sell("AAPL", 10)
|
||||
>>> print(result) # {"AAPL": 90, "MSFT": 5, "CASH": 15000.0, ...}
|
||||
"""
|
||||
# Step 1: Get environment variables and basic information
|
||||
# Get signature (model name) from environment variable, used to determine data storage path
|
||||
signature = get_config_value("SIGNATURE")
|
||||
# Get signature (model name) from parameter or fallback to config/env
|
||||
if signature is None:
|
||||
raise ValueError("SIGNATURE environment variable is not set")
|
||||
|
||||
# Get current trading date from environment variable
|
||||
today_date = get_config_value("TODAY_DATE")
|
||||
signature = get_config_value("SIGNATURE")
|
||||
if signature is None:
|
||||
raise ValueError("SIGNATURE not provided and environment variable is not set")
|
||||
|
||||
# Get current trading date from parameter or fallback to config/env
|
||||
if today_date is None:
|
||||
today_date = get_config_value("TODAY_DATE")
|
||||
|
||||
# Step 2: Get current latest position and operation ID
|
||||
# get_latest_position returns two values: position dictionary and current maximum operation ID
|
||||
|
||||
@@ -7,7 +7,6 @@ services:
|
||||
container_name: ai-trader-server
|
||||
volumes:
|
||||
- ${VOLUME_PATH:-.}/data:/app/data
|
||||
- ${VOLUME_PATH:-.}/logs:/app/logs
|
||||
# User configs mounted to /app/user-configs (default config baked into image)
|
||||
- ${VOLUME_PATH:-.}/configs:/app/user-configs
|
||||
environment:
|
||||
|
||||
@@ -112,10 +112,9 @@ docker-compose up
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
Docker Compose mounts three volumes for persistent data. By default, these are stored in the project directory:
|
||||
Docker Compose mounts two volumes for persistent data. By default, these are stored in the project directory:
|
||||
|
||||
- `./data:/app/data` - Price data and trading records
|
||||
- `./logs:/app/logs` - MCP service logs
|
||||
- `./configs:/app/configs` - Configuration files (allows editing configs without rebuilding)
|
||||
|
||||
### Custom Volume Location
|
||||
@@ -132,7 +131,6 @@ VOLUME_PATH=./volumes
|
||||
|
||||
This will store data in:
|
||||
- `/home/user/trading-data/data/`
|
||||
- `/home/user/trading-data/logs/`
|
||||
- `/home/user/trading-data/configs/`
|
||||
|
||||
**Note:** The directory structure is automatically created. You'll need to copy your existing configs:
|
||||
@@ -148,7 +146,7 @@ To reset all trading data:
|
||||
|
||||
```bash
|
||||
docker-compose down
|
||||
rm -rf ${VOLUME_PATH:-.}/data/agent_data/* ${VOLUME_PATH:-.}/logs/*
|
||||
rm -rf ${VOLUME_PATH:-.}/data/agent_data/*
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
@@ -175,8 +173,7 @@ docker pull ghcr.io/xe138/ai-trader-server:latest
|
||||
```bash
|
||||
docker run --env-file .env \
|
||||
-v $(pwd)/data:/app/data \
|
||||
-v $(pwd)/logs:/app/logs \
|
||||
-p 8000-8003:8000-8003 \
|
||||
-p 8080:8080 \
|
||||
ghcr.io/xe138/ai-trader-server:latest
|
||||
```
|
||||
|
||||
@@ -192,9 +189,9 @@ docker pull ghcr.io/xe138/ai-trader-server:v1.0.0
|
||||
**Symptom:** Container exits immediately or errors about ports
|
||||
|
||||
**Solutions:**
|
||||
- Check ports 8000-8003 not already in use: `lsof -i :8000-8003`
|
||||
- View container logs: `docker-compose logs`
|
||||
- Check MCP service logs: `cat logs/math.log`
|
||||
- Check if API port 8080 is already in use: `lsof -i :8080`
|
||||
- Verify MCP services started by checking Docker logs for service startup messages
|
||||
|
||||
### Missing API Keys
|
||||
|
||||
@@ -216,12 +213,12 @@ docker pull ghcr.io/xe138/ai-trader-server:v1.0.0
|
||||
|
||||
### Permission Issues
|
||||
|
||||
**Symptom:** Cannot write to data or logs directories
|
||||
**Symptom:** Cannot write to data directory
|
||||
|
||||
**Solutions:**
|
||||
- Ensure directories writable: `chmod -R 755 data logs`
|
||||
- Ensure data directory is writable: `chmod -R 755 data`
|
||||
- Check volume mount permissions
|
||||
- May need to create directories first: `mkdir -p data logs`
|
||||
- May need to create directory first: `mkdir -p data`
|
||||
|
||||
### Container Keeps Restarting
|
||||
|
||||
@@ -256,13 +253,12 @@ docker buildx build --platform linux/amd64,linux/arm64 -t ai-trader-server .
|
||||
docker stats ai-trader-server
|
||||
```
|
||||
|
||||
### Access MCP Services Directly
|
||||
### Access API Directly
|
||||
|
||||
Services exposed on host:
|
||||
- Math: http://localhost:8000
|
||||
- Search: http://localhost:8001
|
||||
- Trade: http://localhost:8002
|
||||
- Price: http://localhost:8003
|
||||
API server exposed on host:
|
||||
- REST API: http://localhost:8080
|
||||
|
||||
MCP services run internally and are not exposed to the host.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
|
||||
Reference in New Issue
Block a user