polymarket-bot-testing / dry_run_simulator.py
frank0957's picture
Update dry_run_simulator.py
e27bb65 verified
Raw
History Blame Contribute Delete
12.8 kB
# dry_run_simulator.py
# Virtual trade simulator with full capital management mirroring real trading rules.
# - Uses Polymarket official data for settlement (market close or current prices).
# - Unknown actions (e.g. buy_legacy) are force‑closed as loss after 7 days.
# - Any open position older than 7 days that couldn't be fetched is also force‑closed.
# All comments in English.
import os, json, time, threading, requests
from datetime import datetime, timezone, timedelta
from capital_manager import update_after_trade, load_state as load_capital_state, save_state as save_capital_state, TIERS
SIMULATE_FLAG_FILE = "/app/dry_run_active.txt"
VIRTUAL_POSITIONS_FILE = "/app/virtual_positions.json"
LAST_PROCESSED_SIGNAL_FILE = "/app/last_processed_signal.json"
VIRTUAL_CAPITAL_STATE_FILE = "/app/virtual_capital_state.json"
DATA_API = "https://data-api.polymarket.com"
PSEUDO_SETTLE_DAYS = 3 # normal positions are force‑settled after this many days
STUCK_POSITION_DAYS = 7 # any position still open after this is force‑closed as loss
virtual_positions = []
positions_lock = threading.Lock()
def load_positions():
global virtual_positions
try:
if os.path.exists(VIRTUAL_POSITIONS_FILE):
with open(VIRTUAL_POSITIONS_FILE, 'r') as f:
virtual_positions = json.load(f)
except:
virtual_positions = []
def save_positions():
with positions_lock:
try:
with open(VIRTUAL_POSITIONS_FILE, 'w') as f:
json.dump(virtual_positions, f, indent=2)
except Exception as e:
print(f"[DryRun] Failed to save positions: {e}", flush=True)
def is_simulate_active():
return os.path.exists(SIMULATE_FLAG_FILE)
def read_last_signal():
try:
if os.path.exists(LAST_PROCESSED_SIGNAL_FILE):
with open(LAST_PROCESSED_SIGNAL_FILE, 'r') as f:
return json.load(f)
except:
pass
return None
def get_virtual_capital_state():
if os.path.exists(VIRTUAL_CAPITAL_STATE_FILE):
return load_capital_state(filepath=VIRTUAL_CAPITAL_STATE_FILE)
state = {
"tier_index": 0, "consecutive_wins": 0, "consecutive_losses": 0,
"risk_pool": 5.0, "total_capital": 20.0, "max_bet": 1.0,
"reinvest_rate": 0.25, "halted": False,
"original_deposit": 20.0,
"deposit_date": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
}
save_capital_state(state, filepath=VIRTUAL_CAPITAL_STATE_FILE)
return state
def get_virtual_max_bet():
state = get_virtual_capital_state()
return state.get("max_bet", 1.0)
def get_virtual_open_count():
with positions_lock:
return sum(1 for p in virtual_positions if p.get("status") == "open")
def get_virtual_risk_pool():
state = get_virtual_capital_state()
return state.get("risk_pool", 5.0)
def create_position(market, action, confidence, entry_price, signal_summary, strategy="copy-trade"):
max_bet = get_virtual_max_bet()
open_count = get_virtual_open_count()
risk_pool = get_virtual_risk_pool()
if max_bet > 0 and (open_count + 1) * max_bet > risk_pool:
print(f"[DryRun] Virtual position limit reached (risk pool ${risk_pool:.2f}). Skipping.", flush=True)
return None
position = {
"id": f"vp-{int(time.time())}-{market[:20]}",
"market": market,
"action": action,
"confidence": confidence,
"entry_time": datetime.now(timezone.utc).isoformat(),
"entry_price": entry_price,
"bet_amount": round(max_bet, 2),
"risk_pool_at_entry": round(risk_pool, 2),
"tier_at_entry": TIERS[get_virtual_capital_state()["tier_index"]][2],
"status": "open",
"exit_time": None,
"exit_price": None,
"pnl": None,
"result": None,
"exit_type": None,
"strategy": strategy,
"signal_summary": signal_summary
}
with positions_lock:
virtual_positions.append(position)
save_positions()
print(f"[DryRun] Virtual position opened: {market} | {action} | bet ${max_bet:.2f} | tier {position['tier_at_entry']}", flush=True)
return position
def fetch_current_market_info(market_slug):
try:
resp = requests.get(f"{DATA_API}/markets", params={"slug": market_slug}, timeout=10)
if resp.status_code != 200:
return None, None, None
data = resp.json()
if not isinstance(data, list) or not data:
return None, None, None
market_info = data[0]
outcome_prices = market_info.get("outcomePrices", [])
if len(outcome_prices) < 2:
return None, None, None
yes_price = float(outcome_prices[0])
no_price = float(outcome_prices[1])
closed = market_info.get("closed", False)
return yes_price, no_price, closed
except Exception as e:
print(f"[DryRun] Failed to fetch market {market_slug}: {e}", flush=True)
return None, None, None
def settle_position(pos, exit_type, exit_price, result):
pos["status"] = "closed"
pos["exit_time"] = datetime.now(timezone.utc).isoformat()
pos["exit_type"] = exit_type
pos["exit_price"] = exit_price
pos["result"] = result
entry_price = pos["entry_price"]
bet = pos["bet_amount"]
if result == "win":
pnl = bet * ((1.0 - entry_price) / entry_price) if entry_price > 0 else bet
elif result == "loss":
pnl = -bet
else:
pnl = 0.0
pos["pnl"] = round(pnl, 2)
update_after_trade(won=(result == "win"), profit_amount=pnl, filepath=VIRTUAL_CAPITAL_STATE_FILE)
feed_to_wiki(pos)
def check_settlements():
updated = False
with positions_lock:
for pos in virtual_positions:
if pos.get("status") != "open":
continue
market = pos["market"]
yes_price, no_price, closed = fetch_current_market_info(market)
if closed is None:
continue
if not closed:
continue
winner = "Yes" if yes_price == 1.0 else "No"
action = pos["action"].lower()
if "buy_yes" in action:
won = (winner == "Yes")
elif "sell_yes" in action:
won = (winner != "Yes")
elif "buy_no" in action:
won = (winner == "No")
elif "sell_no" in action:
won = (winner != "No")
else:
won = False
settle_position(pos, exit_type="market", exit_price=1.0 if won else 0.0,
result="win" if won else "loss")
updated = True
print(f"[DryRun] Market settled: {market}{'WIN' if won else 'LOSS'} | P&L={pos['pnl']}", flush=True)
if updated:
save_positions()
def pseudo_settle_timeouts():
now = datetime.now(timezone.utc)
cutoff = now - timedelta(days=PSEUDO_SETTLE_DAYS)
updated = False
with positions_lock:
for pos in virtual_positions:
if pos.get("status") != "open":
continue
try:
entry_time = datetime.fromisoformat(pos["entry_time"])
if entry_time > cutoff:
continue
except:
continue
market = pos["market"]
yes_price, no_price, closed = fetch_current_market_info(market)
if yes_price is None:
continue
action = pos["action"].lower()
if "buy_yes" in action:
won = yes_price > pos["entry_price"]
current = yes_price
elif "sell_yes" in action:
won = yes_price < pos["entry_price"]
current = yes_price
elif "buy_no" in action:
won = no_price > pos["entry_price"]
current = no_price
elif "sell_no" in action:
won = no_price < pos["entry_price"]
current = no_price
else:
won = False
current = 0.0
settle_position(pos, exit_type="timeout", exit_price=current,
result="win" if won else "loss")
updated = True
print(f"[DryRun] Timeout settled: {market} after {PSEUDO_SETTLE_DAYS}d → {'WIN' if won else 'LOSS'}", flush=True)
if updated:
save_positions()
def close_stuck_positions():
"""Force‑close any open position older than STUCK_POSITION_DAYS.
This catches both unknown actions and positions where market fetch failed."""
now = datetime.now(timezone.utc)
cutoff = now - timedelta(days=STUCK_POSITION_DAYS)
updated = False
with positions_lock:
for pos in virtual_positions:
if pos.get("status") != "open":
continue
try:
entry_time = datetime.fromisoformat(pos["entry_time"])
if entry_time > cutoff:
continue
except:
continue
# Force close as loss
settle_position(pos, exit_type="stuck", exit_price=0.0, result="loss")
updated = True
print(f"[DryRun] Stuck position closed: {pos['market']} ({pos.get('action','')}) → LOSS", flush=True)
if updated:
save_positions()
def feed_to_wiki(pos):
try:
from llm_wiki import save_entry
content = (
f"**Virtual Trade Summary**\n\n"
f"- Market: {pos['market']}\n"
f"- Action: {pos['action']}\n"
f"- Confidence: {pos['confidence']}\n"
f"- Bet Amount: ${pos['bet_amount']:.2f}\n"
f"- Entry Time: {pos['entry_time']}\n"
f"- Exit Price: {pos['exit_price']}\n"
f"- Result: {pos.get('result', 'unknown')}\n"
f"- P&L: {pos.get('pnl', 'N/A')}\n"
f"- Exit Type: {pos.get('exit_type', 'unknown')}\n"
)
save_entry(topic=f"VirtualTrade-{pos['market']}", content=content, tags=["virtual-trade", pos.get("result", "unknown")])
except Exception as e:
print(f"[DryRun] Wiki feed failed: {e}", flush=True)
def capture_signals():
signal = read_last_signal()
if not signal:
return
decision = signal.get("decision", "")
if "FOLLOW" not in decision.upper():
return
market = signal.get("market_slug", "")
with positions_lock:
for pos in virtual_positions:
if pos.get("market") == market and pos.get("status") == "open":
return
create_position(
market=market,
action=signal.get("action", ""),
confidence=signal.get("confidence", 0),
entry_price=signal.get("entry_price", 0.5),
signal_summary=decision[:100],
strategy="copy-trade"
)
def get_statistics():
with positions_lock:
total = len(virtual_positions)
open_positions = [p for p in virtual_positions if p.get("status") == "open"]
closed_positions = [p for p in virtual_positions if p.get("status") == "closed"]
wins = [p for p in closed_positions if p.get("result") == "win"]
losses = [p for p in closed_positions if p.get("result") == "loss"]
total_pnl = sum(p.get("pnl", 0) or 0 for p in closed_positions)
win_rate = f"{(len(wins)/len(closed_positions)*100):.1f}%" if closed_positions else "N/A"
state = get_virtual_capital_state()
return {
"active": is_simulate_active(),
"total": total, "open": len(open_positions), "closed": len(closed_positions),
"wins": len(wins), "losses": len(losses), "win_rate": win_rate,
"total_pnl": round(total_pnl, 2),
"virtual_risk_pool": round(state.get("risk_pool", 5.0), 2),
"virtual_total_capital": round(state.get("total_capital", 20.0), 2),
"virtual_tier": TIERS[state.get("tier_index", 0)][2]
}
def run():
load_positions()
print(f"[DryRun] Simulator started (capital-aware, pseudo‑settle after {PSEUDO_SETTLE_DAYS}d, stuck close after {STUCK_POSITION_DAYS}d).", flush=True)
last_capture = 0
last_settlement = 0
last_pseudo = 0
last_stuck = 0
while True:
active = is_simulate_active()
now = time.time()
if active and now - last_capture >= 60:
capture_signals()
last_capture = now
if now - last_settlement >= 600:
check_settlements()
last_settlement = now
if now - last_pseudo >= 3600:
pseudo_settle_timeouts()
last_pseudo = now
if now - last_stuck >= 3600:
close_stuck_positions()
last_stuck = now
time.sleep(30)
if __name__ == "__main__":
run()