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