Includes core prod + GREEN/BLUE subsystems: - prod/ (BLUE harness, configs, scripts, docs) - nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved) - adaptive_exit/ (AEM engine + models/bucket_assignments.pkl) - Observability/ (EsoF advisor, TUI, dashboards) - external_factors/ (EsoF producer) - mc_forewarning_qlabs_fork/ (MC regime/envelope) Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
174 lines
5.8 KiB
Python
Executable File
174 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
DOLPHIN Paper Trading Session
|
|
==============================
|
|
Brief paper trading run using clean architecture.
|
|
"""
|
|
|
|
import sys
|
|
sys.path.insert(0, '/mnt/dolphinng5_predict/prod/clean_arch')
|
|
sys.path.insert(0, '/mnt/dolphinng5_predict')
|
|
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s [%(levelname)s] %(message)s'
|
|
)
|
|
logger = logging.getLogger("PaperTrade")
|
|
|
|
from adapters.hazelcast_feed import HazelcastDataFeed, MarketSnapshot
|
|
|
|
|
|
class SimplePaperTrader:
|
|
"""Simple paper trader for demonstration."""
|
|
|
|
def __init__(self, capital: float = 10000.0):
|
|
self.capital = capital
|
|
self.position = 0.0 # BTC quantity
|
|
self.entry_price = 0.0
|
|
self.trades = []
|
|
self.start_time = datetime.utcnow()
|
|
|
|
def on_snapshot(self, snapshot: MarketSnapshot):
|
|
"""Process market snapshot and decide to trade."""
|
|
# Use velocity divergence as signal (non-zero in current data)
|
|
signal = snapshot.velocity_divergence or 0.0
|
|
|
|
# Simple mean-reversion strategy on velocity divergence
|
|
BUY_THRESHOLD = -0.01 # Buy when velocity divergence is very negative
|
|
SELL_THRESHOLD = 0.01 # Sell when velocity divergence is very positive
|
|
|
|
if signal > BUY_THRESHOLD and self.position == 0:
|
|
# Buy signal
|
|
size = 0.001 # 0.001 BTC
|
|
self.position = size
|
|
self.entry_price = snapshot.price
|
|
self.trades.append({
|
|
'time': datetime.utcnow(),
|
|
'side': 'BUY',
|
|
'size': size,
|
|
'price': snapshot.price,
|
|
'signal': signal
|
|
})
|
|
logger.info(f"🟢 BUY {size} BTC @ ${snapshot.price:,.2f} (signal: {signal:.4f})")
|
|
|
|
elif signal < SELL_THRESHOLD and self.position > 0:
|
|
# Sell signal
|
|
pnl = self.position * (snapshot.price - self.entry_price)
|
|
self.trades.append({
|
|
'time': datetime.utcnow(),
|
|
'side': 'SELL',
|
|
'size': self.position,
|
|
'price': snapshot.price,
|
|
'signal': signal,
|
|
'pnl': pnl
|
|
})
|
|
logger.info(f"🔴 SELL {self.position} BTC @ ${snapshot.price:,.2f} (signal: {signal:.4f}, PnL: ${pnl:+.2f})")
|
|
self.position = 0.0
|
|
self.entry_price = 0.0
|
|
|
|
def get_status(self) -> dict:
|
|
"""Get current trading status."""
|
|
current_price = self.trades[-1]['price'] if self.trades else 0
|
|
unrealized = self.position * (current_price - self.entry_price) if self.position > 0 else 0
|
|
realized = sum(t.get('pnl', 0) for t in self.trades)
|
|
|
|
return {
|
|
'trades': len(self.trades),
|
|
'position': self.position,
|
|
'unrealized_pnl': unrealized,
|
|
'realized_pnl': realized,
|
|
'total_pnl': unrealized + realized
|
|
}
|
|
|
|
|
|
async def paper_trade(duration_seconds: int = 60):
|
|
"""Run paper trading for specified duration."""
|
|
logger.info("=" * 60)
|
|
logger.info("🐬 DOLPHIN PAPER TRADING SESSION")
|
|
logger.info("=" * 60)
|
|
logger.info(f"Duration: {duration_seconds}s")
|
|
logger.info("")
|
|
|
|
# Setup
|
|
feed = HazelcastDataFeed({
|
|
'hazelcast': {'cluster': 'dolphin', 'host': 'localhost:5701'}
|
|
})
|
|
|
|
trader = SimplePaperTrader(capital=10000.0)
|
|
|
|
# Connect
|
|
logger.info("Connecting to Hazelcast...")
|
|
if not await feed.connect():
|
|
logger.error("Failed to connect!")
|
|
return
|
|
|
|
logger.info("✓ Connected. Starting trading loop...")
|
|
logger.info("")
|
|
|
|
# Trading loop
|
|
start_time = datetime.utcnow()
|
|
iteration = 0
|
|
|
|
try:
|
|
while (datetime.utcnow() - start_time).total_seconds() < duration_seconds:
|
|
iteration += 1
|
|
|
|
# Get latest snapshot
|
|
snapshot = await feed.get_latest_snapshot("BTCUSDT")
|
|
|
|
if snapshot:
|
|
trader.on_snapshot(snapshot)
|
|
|
|
# Log status every 5 iterations
|
|
if iteration % 5 == 0:
|
|
status = trader.get_status()
|
|
pos_str = f"Position: {status['position']:.4f} BTC" if status['position'] > 0 else "Position: FLAT"
|
|
pnl_str = f"PnL: ${status['total_pnl']:+.2f}"
|
|
logger.info(f"[{iteration}] {pos_str} | {pnl_str} | Price: ${snapshot.price:,.2f}")
|
|
|
|
# Wait before next iteration (sub-5 second for faster updates)
|
|
await asyncio.sleep(1.0)
|
|
|
|
except KeyboardInterrupt:
|
|
logger.info("\n⚠️ Interrupted by user")
|
|
|
|
# Cleanup
|
|
await feed.disconnect()
|
|
|
|
# Final report
|
|
logger.info("")
|
|
logger.info("=" * 60)
|
|
logger.info("📊 FINAL REPORT")
|
|
logger.info("=" * 60)
|
|
|
|
status = trader.get_status()
|
|
logger.info(f"Total Trades: {status['trades']}")
|
|
logger.info(f"Final Position: {status['position']:.6f} BTC")
|
|
logger.info(f"Realized PnL: ${status['realized_pnl']:+.2f}")
|
|
logger.info(f"Unrealized PnL: ${status['unrealized_pnl']:+.2f}")
|
|
logger.info(f"Total PnL: ${status['total_pnl']:+.2f}")
|
|
|
|
if trader.trades:
|
|
logger.info("")
|
|
logger.info("Trade History:")
|
|
for t in trader.trades:
|
|
pnl_str = f" (${t.get('pnl', 0):+.2f})" if 'pnl' in t else ""
|
|
logger.info(f" {t['side']:4} {t['size']:.4f} @ ${t['price']:,.2f}{pnl_str}")
|
|
|
|
logger.info("=" * 60)
|
|
logger.info("✅ Paper trading session complete")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--duration', type=int, default=30, help='Trading duration in seconds')
|
|
args = parser.parse_args()
|
|
|
|
asyncio.run(paper_trade(args.duration))
|