Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ Thumbs.db

# Project specific
*.log
*.csv
.cache/
logs/

# Development/Testing files (not for commit)
.dev/
Expand All @@ -65,4 +67,4 @@ Thumbs.db
.swarm/
memory/
.mcp.json
claude-flow
claude-flowlogs/
12 changes: 10 additions & 2 deletions dr_manhattan/base/exchange_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,9 @@ def calculate_delta(positions: Dict[str, float]) -> DeltaInfo:
"""
Calculate delta (position imbalance) from positions.

For binary markets: delta = first_outcome - second_outcome (signed).
Positive delta means long first outcome, negative means long second outcome.

Args:
positions: Dict mapping outcome name to position size

Expand All @@ -957,10 +960,15 @@ def calculate_delta(positions: Dict[str, float]) -> DeltaInfo:
position_values = list(positions.values())
max_pos = max(position_values)
min_pos = min(position_values)
delta = max_pos - min_pos

# Signed delta: first outcome - second outcome
if len(position_values) >= 2:
delta = position_values[0] - position_values[1]
else:
delta = position_values[0]

max_outcome = None
if delta > 0:
if delta != 0:
max_outcome = max(positions, key=positions.get)

return DeltaInfo(
Expand Down
31 changes: 31 additions & 0 deletions dr_manhattan/base/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ def __init__(
max_delta: float = 20.0,
check_interval: float = 5.0,
track_fills: bool = True,
enable_csv_logging: bool = False,
log_dir: str = "logs",
):
"""
Initialize strategy.
Expand All @@ -69,6 +71,8 @@ def __init__(
max_delta: Maximum position imbalance before reducing exposure
check_interval: Seconds between strategy ticks
track_fills: Enable order fill tracking
enable_csv_logging: Enable CSV logging of strategy execution
log_dir: Directory to store CSV log files
"""
self.exchange = exchange
self.client = ExchangeClient(exchange, track_fills=track_fills)
Expand All @@ -77,6 +81,8 @@ def __init__(
self.order_size = order_size
self.max_delta = max_delta
self.check_interval = check_interval
self.enable_csv_logging = enable_csv_logging
self.log_dir = log_dir

# Market data (populated by setup())
self.market: Optional[Market] = None
Expand All @@ -85,6 +91,7 @@ def __init__(

# Runtime state
self.is_running = False
self.csv_logger = None

# Cached state (updated each tick)
self._positions: Dict[str, float] = {}
Expand Down Expand Up @@ -130,6 +137,19 @@ def setup(self) -> bool:
# Load initial positions
self._positions = self.client.fetch_positions_dict_for_market(self.market)

# Initialize CSV logger if enabled
if self.enable_csv_logging:
from ..utils.csv_logger import CSVLogger

strategy_name = self.__class__.__name__
self.csv_logger = CSVLogger(
strategy_name=strategy_name,
market_id=self.market_id,
outcomes=outcomes,
log_dir=self.log_dir,
)
logger.info(f"CSV logging enabled: {self.csv_logger.get_filepath()}")

self._log_trader_profile()
self._log_market_info()
return True
Expand Down Expand Up @@ -650,6 +670,17 @@ def run(self, duration_minutes: Optional[int] = None):
break

self.on_tick()

# Log to CSV if enabled
if self.csv_logger:
self.refresh_state()
self.csv_logger.log_snapshot(
nav=self._nav,
positions=self._positions,
orders=self._open_orders,
delta=self.delta,
)

time.sleep(self.check_interval)

except KeyboardInterrupt:
Expand Down
120 changes: 120 additions & 0 deletions dr_manhattan/utils/csv_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""CSV logger for strategy execution tracking."""

import csv
from datetime import datetime
from pathlib import Path
from typing import Dict, List

from ..models.nav import NAV
from ..models.order import Order


class CSVLogger:
"""
Logs strategy execution data to CSV file.

Records NAV, positions, delta, and orders at each tick.
File format: logs/{strategy_name}_{market_id}_{timestamp}.csv
"""

def __init__(
self,
strategy_name: str,
market_id: str,
outcomes: List[str],
log_dir: str = "logs",
):
"""
Initialize CSV logger.

Args:
strategy_name: Name of the strategy
market_id: Market ID being traded
outcomes: List of outcome names
log_dir: Directory to store log files
"""
self.strategy_name = strategy_name
self.market_id = market_id
self.outcomes = outcomes
self.log_dir = Path(log_dir)

# Create logs directory if it doesn't exist
self.log_dir.mkdir(parents=True, exist_ok=True)

# Generate filename with timestamp
start_time = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{strategy_name}_{market_id[:8]}_{start_time}.csv"
self.filepath = self.log_dir / filename

# Initialize CSV file with headers
self._write_header()

def _write_header(self):
"""Write CSV header row."""
headers = [
"timestamp",
"nav",
"cash",
"positions_value",
"delta",
"num_open_orders",
]

# Add dynamic outcome columns
for outcome in self.outcomes:
# Sanitize outcome name for column header
safe_name = outcome.replace(" ", "_").replace(",", "").lower()[:20]
headers.append(f"{safe_name}_qty")
headers.append(f"{safe_name}_value")

with open(self.filepath, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(headers)

def log_snapshot(
self,
nav: NAV,
positions: Dict[str, float],
orders: List[Order],
delta: float,
):
"""
Log current state snapshot.

Args:
nav: NAV object with breakdown
positions: Dict mapping outcome to position size
orders: List of open orders
delta: Current delta value
"""
row = [
datetime.now().isoformat(),
round(nav.nav, 2),
round(nav.cash, 2),
round(nav.positions_value, 2),
round(delta, 2),
len(orders),
]

# Add position data for each outcome
for outcome in self.outcomes:
position_size = positions.get(outcome, 0.0)

# Find position value from NAV breakdown
position_value = 0.0
for pos_breakdown in nav.positions:
if pos_breakdown.outcome == outcome:
position_value = pos_breakdown.value
break

row.append(round(position_size, 2))
row.append(round(position_value, 2))

# Write row to CSV
with open(self.filepath, "a", newline="") as f:
writer = csv.writer(f)
writer.writerow(row)

def get_filepath(self) -> str:
"""Get the full path to the CSV file."""
return str(self.filepath.absolute())
1 change: 1 addition & 0 deletions dr_manhattan/web/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Web dashboard for strategy monitoring."""
Loading
Loading