TradingAgents/tradingagents/dataflows/discovery/performance/position_tracker.py

198 lines
6.4 KiB
Python

"""
Position Tracker Module
Monitors positions continuously with dynamic price history tracking.
Maintains complete price time-series and calculates real-time metrics.
"""
import json
from datetime import datetime
from pathlib import Path
from tradingagents.utils.logger import get_logger
logger = get_logger(__name__)
from typing import Any, Dict, List, Optional
class PositionTracker:
"""
Dynamic position tracking system that monitors positions continuously.
Maintains complete price history and calculates real-time metrics.
"""
def __init__(self, data_dir: str = "data"):
"""
Initialize PositionTracker.
Args:
data_dir: Root directory for position storage (default: "data")
"""
self.data_dir = Path(data_dir)
self.positions_dir = self.data_dir / "positions"
self.positions_dir.mkdir(parents=True, exist_ok=True)
def create_position(self, recommendation: Dict[str, Any]) -> Dict[str, Any]:
"""
Create a new position dictionary from a recommendation.
Args:
recommendation: Recommendation dict with at minimum:
- ticker: Stock ticker
- entry_price: Entry price for the position
- recommendation_date: Date of recommendation
- scanner: Source scanner
- strategy: Strategy name
- pipeline: Pipeline identifier
- confidence: Confidence score (0-1)
- shares: Number of shares to buy
Returns:
Position dictionary with initialized structure
"""
now = datetime.utcnow()
position = {
"ticker": recommendation.get("ticker"),
"entry_price": recommendation.get("entry_price"),
"recommendation_date": recommendation.get("recommendation_date"),
"pipeline": recommendation.get("pipeline"),
"scanner": recommendation.get("scanner"),
"strategy": recommendation.get("strategy"),
"confidence": recommendation.get("confidence"),
"shares": recommendation.get("shares"),
"created_at": now.isoformat(),
"status": "open",
"price_history": [
{
"timestamp": now.isoformat(),
"price": recommendation.get("entry_price"),
"return_pct": 0.0,
"hours_held": 0.0,
"days_held": 0.0,
}
],
"metrics": {
"peak_return": 0.0,
"current_return": 0.0,
"current_price": recommendation.get("entry_price"),
"days_held": 0.0,
"status": "open",
},
}
return position
def update_position_price(
self,
position: Dict[str, Any],
new_price: float,
timestamp: Optional[str] = None,
) -> Dict[str, Any]:
"""
Update position with new price point and recalculate metrics.
Args:
position: Position dictionary to update
new_price: New price to add to history
timestamp: ISO timestamp for price (default: current UTC time)
Returns:
Updated position dictionary
"""
if timestamp is None:
timestamp = datetime.utcnow().isoformat()
# Convert timestamp to datetime if it's a string
if isinstance(timestamp, str):
price_time = datetime.fromisoformat(timestamp)
else:
price_time = timestamp
# Get entry time from recommendation_date or created_at
if isinstance(position["recommendation_date"], str):
entry_time = datetime.fromisoformat(position["recommendation_date"])
else:
entry_time = datetime.fromisoformat(position["created_at"])
# Calculate time differences
time_diff = price_time - entry_time
hours_held = time_diff.total_seconds() / 3600
days_held = time_diff.total_seconds() / (3600 * 24)
# Calculate returns
entry_price = position["entry_price"]
return_pct = ((new_price - entry_price) / entry_price) * 100
# Create price history entry
price_entry = {
"timestamp": timestamp,
"price": new_price,
"return_pct": return_pct,
"hours_held": hours_held,
"days_held": days_held,
}
# Add to price history
position["price_history"].append(price_entry)
# Update metrics
position["metrics"]["current_price"] = new_price
position["metrics"]["current_return"] = return_pct
position["metrics"]["days_held"] = days_held
# Update peak return if current return is higher
if return_pct > position["metrics"]["peak_return"]:
position["metrics"]["peak_return"] = return_pct
return position
def save_position(self, position: Dict[str, Any]) -> str:
"""
Save position to JSON file.
Creates file: {ticker}_{created_at_timestamp}.json
Args:
position: Position dictionary to save
Returns:
Path to saved file
"""
ticker = position["ticker"]
created_at = position["created_at"]
# Parse created_at to create a filename-safe timestamp
created_dt = datetime.fromisoformat(created_at)
timestamp_str = created_dt.strftime("%Y%m%d_%H%M%S")
filename = f"{ticker}_{timestamp_str}.json"
filepath = self.positions_dir / filename
with open(filepath, "w") as f:
json.dump(position, f, indent=2)
return str(filepath)
def load_all_open_positions(self) -> List[Dict[str, Any]]:
"""
Load all positions with status="open" from disk.
Returns:
List of position dictionaries
"""
open_positions = []
if not self.positions_dir.exists():
return open_positions
for filepath in self.positions_dir.glob("*.json"):
try:
with open(filepath, "r") as f:
position = json.load(f)
if position.get("status") == "open":
open_positions.append(position)
except (json.JSONDecodeError, IOError) as e:
# Log error but continue loading other positions
logger.error(f"Error loading position from {filepath}: {e}")
return open_positions