feat: capitale virtuale $1000 USDC, PnL tracking realistico con fee
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from src.live.signal_engine import SignalEngine
|
||||
|
||||
LOG_DIR = Path(__file__).resolve().parents[2] / "data" / "paper_trades"
|
||||
INSTRUMENT = "ETH_USDC-PERPETUAL"
|
||||
TRAIN_INSTRUMENT = "ETH-PERPETUAL" # storico più lungo per training
|
||||
TRAIN_INSTRUMENT = "ETH-PERPETUAL"
|
||||
CURRENCY = "USDC"
|
||||
RESOLUTION = "15"
|
||||
LEVERAGE = 3
|
||||
@@ -22,6 +22,7 @@ HOLD_BARS = 3
|
||||
POLL_SECONDS = 60
|
||||
LOOKBACK_DAYS = 60
|
||||
TRAIN_LOOKBACK_DAYS = 365
|
||||
VIRTUAL_CAPITAL = 1000.0 # simula capitale reale, ignora balance testnet
|
||||
|
||||
|
||||
class PaperTrader:
|
||||
@@ -29,10 +30,12 @@ class PaperTrader:
|
||||
self.client = CerberoClient()
|
||||
self.engine = SignalEngine(bb_w=14, sq_thr=0.8, ml_thr=0.70)
|
||||
|
||||
self.virtual_capital = VIRTUAL_CAPITAL
|
||||
self.in_position = False
|
||||
self.position_entry_time: datetime | None = None
|
||||
self.position_direction: str | None = None
|
||||
self.position_entry_price: float = 0
|
||||
self.position_size: float = 0
|
||||
self.bars_held = 0
|
||||
self.last_bar_ts: int = 0
|
||||
|
||||
@@ -52,9 +55,11 @@ class PaperTrader:
|
||||
|
||||
def save_status(self):
|
||||
status = {
|
||||
"virtual_capital": round(self.virtual_capital, 2),
|
||||
"in_position": self.in_position,
|
||||
"direction": self.position_direction,
|
||||
"entry_price": self.position_entry_price,
|
||||
"position_size": self.position_size,
|
||||
"entry_time": self.position_entry_time.isoformat() if self.position_entry_time else None,
|
||||
"bars_held": self.bars_held,
|
||||
"last_update": datetime.now(timezone.utc).isoformat(),
|
||||
@@ -91,10 +96,8 @@ class PaperTrader:
|
||||
def open_position(self, direction: str, signal: dict):
|
||||
ticker = self.client.get_ticker(INSTRUMENT)
|
||||
price = ticker["last_price"]
|
||||
account = self.client.get_account_summary()
|
||||
equity = account["equity"]
|
||||
|
||||
notional = equity * POSITION_PCT
|
||||
notional = self.virtual_capital * POSITION_PCT * LEVERAGE
|
||||
amount = round(notional / price, 3)
|
||||
amount = max(amount, 0.001)
|
||||
|
||||
@@ -104,7 +107,8 @@ class PaperTrader:
|
||||
"side": side,
|
||||
"amount": amount,
|
||||
"price": price,
|
||||
"equity": equity,
|
||||
"virtual_capital": round(self.virtual_capital, 2),
|
||||
"notional": round(notional, 2),
|
||||
"signal": signal,
|
||||
})
|
||||
|
||||
@@ -120,6 +124,7 @@ class PaperTrader:
|
||||
self.in_position = True
|
||||
self.position_direction = side
|
||||
self.position_entry_price = price
|
||||
self.position_size = amount
|
||||
self.position_entry_time = datetime.now(timezone.utc)
|
||||
self.bars_held = 0
|
||||
self.log("OPENED", {"order_result": result})
|
||||
@@ -134,27 +139,43 @@ class PaperTrader:
|
||||
exit_price = ticker["last_price"]
|
||||
|
||||
if self.position_direction == "buy":
|
||||
pnl_pct = (exit_price - self.position_entry_price) / self.position_entry_price * 100
|
||||
trade_pnl = (exit_price - self.position_entry_price) * self.position_size
|
||||
else:
|
||||
pnl_pct = (self.position_entry_price - exit_price) / self.position_entry_price * 100
|
||||
trade_pnl = (self.position_entry_price - exit_price) * self.position_size
|
||||
|
||||
fee = self.position_size * (self.position_entry_price + exit_price) * 0.001
|
||||
net_pnl = trade_pnl - fee
|
||||
pnl_pct = net_pnl / self.virtual_capital * 100
|
||||
|
||||
self.log("CLOSING", {
|
||||
"reason": reason,
|
||||
"entry_price": self.position_entry_price,
|
||||
"exit_price": exit_price,
|
||||
"size": self.position_size,
|
||||
"trade_pnl": round(trade_pnl, 2),
|
||||
"fee": round(fee, 2),
|
||||
"net_pnl": round(net_pnl, 2),
|
||||
"pnl_pct": round(pnl_pct, 3),
|
||||
"bars_held": self.bars_held,
|
||||
"capital_before": round(self.virtual_capital, 2),
|
||||
})
|
||||
|
||||
try:
|
||||
result = self.client.close_position(INSTRUMENT)
|
||||
self.log("CLOSED", {"result": result, "pnl_pct": round(pnl_pct, 3)})
|
||||
self.virtual_capital += net_pnl
|
||||
self.log("CLOSED", {
|
||||
"result": result,
|
||||
"net_pnl": round(net_pnl, 2),
|
||||
"pnl_pct": round(pnl_pct, 3),
|
||||
"virtual_capital": round(self.virtual_capital, 2),
|
||||
})
|
||||
except Exception as e:
|
||||
self.log("CLOSE_FAILED", {"error": str(e)})
|
||||
|
||||
self.in_position = False
|
||||
self.position_direction = None
|
||||
self.position_entry_price = 0
|
||||
self.position_size = 0
|
||||
self.position_entry_time = None
|
||||
self.bars_held = 0
|
||||
|
||||
@@ -215,7 +236,8 @@ class PaperTrader:
|
||||
|
||||
account = self.client.get_account_summary()
|
||||
self.log("STARTUP", {
|
||||
"equity": account["equity"],
|
||||
"virtual_capital": self.virtual_capital,
|
||||
"testnet_equity": account["equity"],
|
||||
"testnet": account.get("testnet", True),
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user