mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
463 lines
17 KiB
Python
463 lines
17 KiB
Python
# ==============================================================================
|
|
# COPYRIGHT NO ONE EVERYWHERE LLC (WYOMING HOLDING COMPANY)
|
|
# PROJECT: SOVEREIGN STACK
|
|
# This artifact is entirely proprietary and cryptographically proven.
|
|
# Open-Source usage requires explicit permission from Brandon Scott Schneider.
|
|
# ==============================================================================
|
|
"""
|
|
Paper Trading Agent - Hutter 8-Hour Stress Test
|
|
|
|
Single agent: 50 USDC starting capital
|
|
Runs for 8 hours (simulated or real-time)
|
|
Fragments trades across pools
|
|
Tracks profitability, fragments, recoveries
|
|
Survives or fails to 0 balance
|
|
|
|
Multi-instance: parallel runs with different seeds
|
|
Find which agents survive longest
|
|
|
|
DAG Record: Complete history of every trade, decision, and state change
|
|
Allows replay and analysis of what caused survival/failure
|
|
Live market prediction to guide trading decisions
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
import random
|
|
import os
|
|
import hashlib
|
|
import math
|
|
from datetime import datetime, timedelta
|
|
from dataclasses import dataclass, asdict, field
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
PHI = 1.618033988749895
|
|
SIMULATION_SPEED = 100 # 1 simulated second = 1/100 real seconds (for faster testing)
|
|
|
|
# ============================================================================
|
|
# Pool Model
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class Pool:
|
|
name: str
|
|
token_a: str
|
|
token_b: str
|
|
reserve_a: float
|
|
reserve_b: float
|
|
fee_bps: int = 25
|
|
|
|
def swap(self, amount_in: float, is_a_to_b: bool, slippage_variance: float = 0.5) -> tuple:
|
|
"""Execute swap with variance (real pools move)"""
|
|
fee_rate = 1.0 - (self.fee_bps / 10000)
|
|
amount_in_after_fee = amount_in * fee_rate
|
|
|
|
# Add slippage variance
|
|
variance = random.gauss(1.0, slippage_variance / 100)
|
|
amount_in_after_fee *= variance
|
|
|
|
if is_a_to_b:
|
|
k = self.reserve_a * self.reserve_b
|
|
new_reserve_a = self.reserve_a + amount_in_after_fee
|
|
new_reserve_b = k / new_reserve_a
|
|
amount_out = self.reserve_b - new_reserve_b
|
|
self.reserve_a = new_reserve_a
|
|
self.reserve_b = new_reserve_b
|
|
else:
|
|
k = self.reserve_a * self.reserve_b
|
|
new_reserve_b = self.reserve_b + amount_in_after_fee
|
|
new_reserve_a = k / new_reserve_b
|
|
amount_out = self.reserve_a - new_reserve_a
|
|
self.reserve_a = new_reserve_a
|
|
self.reserve_b = new_reserve_b
|
|
|
|
fee_paid = amount_in * (self.fee_bps / 10000)
|
|
return max(0, amount_out), fee_paid
|
|
|
|
def get_price(self, is_a_to_b: bool) -> float:
|
|
if is_a_to_b:
|
|
return self.reserve_b / self.reserve_a
|
|
else:
|
|
return self.reserve_a / self.reserve_b
|
|
|
|
|
|
# ============================================================================
|
|
# DAG Node for immutable history
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class DAGNode:
|
|
"""Directed Acyclic Graph node for trade history"""
|
|
node_id: str
|
|
timestamp: int # Seconds since start
|
|
event_type: str # "trade", "recovery", "checkpoint", "prediction", "failure"
|
|
parent_hash: Optional[str] # Hash of previous node (chain)
|
|
data: Dict = field(default_factory=dict)
|
|
hash: Optional[str] = None # Computed hash
|
|
|
|
def compute_hash(self) -> str:
|
|
"""SHA256 hash of this node (immutable)"""
|
|
content = json.dumps({
|
|
"node_id": self.node_id,
|
|
"timestamp": self.timestamp,
|
|
"event_type": self.event_type,
|
|
"parent_hash": self.parent_hash,
|
|
"data": self.data
|
|
}, sort_keys=True)
|
|
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
|
|
|
|
@dataclass
|
|
class PaperTraderState:
|
|
agent_id: int
|
|
session_id: str
|
|
balance_usdc: float
|
|
balance_sol: float = 10.0
|
|
balance_kot: float = 0.0
|
|
|
|
lifetime_profit: float = 0.0
|
|
lifetime_loss: float = 0.0
|
|
num_trades: int = 0
|
|
successful_fragments: int = 0
|
|
failed_fragments: int = 0
|
|
recovery_attempts: int = 0
|
|
recovery_successes: int = 0
|
|
|
|
went_negative_at: Optional[int] = None
|
|
lowest_balance: float = 0.0
|
|
time_alive_seconds: int = 0
|
|
|
|
trade_history: List[Dict] = field(default_factory=list)
|
|
dag_nodes: List[DAGNode] = field(default_factory=list)
|
|
dag_heads: List[str] = field(default_factory=list)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"agent_id": self.agent_id,
|
|
"session_id": self.session_id,
|
|
"balance_usdc": self.balance_usdc,
|
|
"balance_sol": self.balance_sol,
|
|
"lifetime_profit": self.lifetime_profit,
|
|
"lifetime_loss": self.lifetime_loss,
|
|
"num_trades": self.num_trades,
|
|
"went_negative_at": self.went_negative_at,
|
|
"lowest_balance": self.lowest_balance,
|
|
"time_alive_seconds": self.time_alive_seconds,
|
|
"recovery_success_rate": f"{self.recovery_successes}/{self.recovery_attempts}",
|
|
"trade_history": self.trade_history[-10:],
|
|
"num_dag_nodes": len(self.dag_nodes),
|
|
"dag_heads": self.dag_heads[-3:] # Last 3 heads
|
|
}
|
|
|
|
|
|
class PaperTrader:
|
|
def __init__(self, agent_id: int, session_id: str, starting_usdc: float = 50.0):
|
|
self.agent_id = agent_id
|
|
self.session_id = session_id
|
|
self.state = PaperTraderState(
|
|
agent_id=agent_id,
|
|
session_id=session_id,
|
|
balance_usdc=starting_usdc
|
|
)
|
|
|
|
self.pools = [
|
|
Pool("SOL/USDC", "SOL", "USDC", reserve_a=50000, reserve_b=1250000),
|
|
Pool("USDC/BTC", "USDC", "BTC", reserve_a=2000000, reserve_b=50),
|
|
]
|
|
|
|
self.log_file = f"/tmp/paper_trader_{session_id}_agent_{agent_id}.log"
|
|
self.checkpoint_file = f"/tmp/paper_trader_{session_id}_agent_{agent_id}.json"
|
|
self.dag_file = f"/tmp/paper_trader_{session_id}_agent_{agent_id}_dag.json"
|
|
|
|
self.price_history = {"SOL/USDC": [], "USDC/BTC": []}
|
|
self.last_dag_node_hash = None
|
|
|
|
def log(self, message: str):
|
|
"""Log with timestamp"""
|
|
ts = datetime.now().isoformat()
|
|
line = f"[{ts}] {message}\n"
|
|
with open(self.log_file, "a") as f:
|
|
f.write(line)
|
|
|
|
def _add_dag_node(self, event_type: str, data: Dict):
|
|
"""Add immutable DAG node to history"""
|
|
node = DAGNode(
|
|
node_id=f"{self.agent_id}_{len(self.state.dag_nodes)}",
|
|
timestamp=self.state.time_alive_seconds,
|
|
event_type=event_type,
|
|
parent_hash=self.last_dag_node_hash,
|
|
data=data
|
|
)
|
|
node.hash = node.compute_hash()
|
|
self.state.dag_nodes.append(node)
|
|
self.last_dag_node_hash = node.hash
|
|
|
|
if node.hash not in self.state.dag_heads:
|
|
self.state.dag_heads.append(node.hash)
|
|
|
|
def predict_price_direction(self, lookback: int = 5) -> Tuple[str, float]:
|
|
"""
|
|
Predict next price direction using:
|
|
1. Momentum (rate of change)
|
|
2. Mean reversion (deviation from moving average)
|
|
3. Volatility (price variance)
|
|
|
|
Returns: (predicted_direction, confidence_0_to_1)
|
|
"""
|
|
sol_prices = self.price_history.get("SOL/USDC", [])
|
|
|
|
if len(sol_prices) < lookback:
|
|
return "UNCERTAIN", 0.0
|
|
|
|
recent = sol_prices[-lookback:]
|
|
|
|
# 1. Momentum: slope of price
|
|
momentum = (recent[-1] - recent[0]) / recent[0] if recent[0] > 0 else 0
|
|
|
|
# 2. Mean reversion: deviation from moving average
|
|
ma = sum(recent) / len(recent)
|
|
deviation = (recent[-1] - ma) / ma if ma > 0 else 0
|
|
|
|
# 3. Volatility: std dev of returns
|
|
returns = [(recent[i] - recent[i - 1]) / recent[i - 1] for i in range(1, len(recent)) if recent[i - 1] > 0]
|
|
volatility = math.sqrt(sum(r ** 2 for r in returns) / len(returns)) if returns else 0
|
|
|
|
# Combine signals
|
|
momentum_signal = 1.0 if momentum > 0.005 else (-1.0 if momentum < -0.005 else 0.0)
|
|
mr_signal = -1.0 if abs(deviation) > 0.02 else 0.0
|
|
vol_signal = -0.5 if volatility > 0.05 else 0.5
|
|
|
|
total_signal = momentum_signal + mr_signal + vol_signal
|
|
direction = "UP" if total_signal > 0.5 else ("DOWN" if total_signal < -0.5 else "UNCERTAIN")
|
|
confidence = min(1.0, abs(total_signal) / 3.0)
|
|
|
|
return direction, confidence
|
|
|
|
def execute_trade(self, amount_usdc: float, num_fragments: int = 3) -> float:
|
|
"""Execute fragmented trade, return net profit/loss"""
|
|
if amount_usdc <= 0 or amount_usdc > self.state.balance_usdc:
|
|
return 0.0
|
|
|
|
# Predict direction before trade
|
|
predicted_dir, confidence = self.predict_price_direction()
|
|
self._add_dag_node("prediction", {
|
|
"direction": predicted_dir,
|
|
"confidence": confidence,
|
|
"price": self.pools[0].get_price(True)
|
|
})
|
|
|
|
amount_per_fragment = amount_usdc / num_fragments
|
|
total_output_sol = 0.0
|
|
confirmed_fragments = {}
|
|
|
|
self._add_dag_node("trade_start", {
|
|
"amount_usdc": amount_usdc,
|
|
"fragments": num_fragments,
|
|
"price_when_executed": self.pools[0].get_price(True)
|
|
})
|
|
|
|
# Execute fragments (85% success rate)
|
|
for i in range(num_fragments):
|
|
success = random.random() < 0.85
|
|
|
|
if success:
|
|
try:
|
|
output, fee = self.pools[0].swap(amount_per_fragment, is_a_to_b=True)
|
|
total_output_sol += output
|
|
confirmed_fragments[i] = output
|
|
self.state.successful_fragments += 1
|
|
|
|
self._add_dag_node("fragment_success", {
|
|
"fragment_id": i,
|
|
"sent_usdc": amount_per_fragment,
|
|
"received_sol": output,
|
|
"fee": fee
|
|
})
|
|
except Exception:
|
|
self.state.failed_fragments += 1
|
|
self._add_dag_node("fragment_fail", {
|
|
"fragment_id": i,
|
|
"reason": "swap_exception"
|
|
})
|
|
else:
|
|
self.state.failed_fragments += 1
|
|
self._add_dag_node("fragment_fail", {
|
|
"fragment_id": i,
|
|
"reason": "timeout"
|
|
})
|
|
|
|
# Recovery
|
|
num_lost = num_fragments - len(confirmed_fragments)
|
|
if num_lost > 0 and len(confirmed_fragments) > 0:
|
|
self.state.recovery_attempts += 1
|
|
self._add_dag_node("recovery_attempt", {
|
|
"confirmed": len(confirmed_fragments),
|
|
"lost": num_lost,
|
|
"threshold": num_fragments * 0.5
|
|
})
|
|
|
|
if num_lost <= num_fragments * 0.5:
|
|
mean_confirmed = sum(confirmed_fragments.values()) / len(confirmed_fragments)
|
|
for i in range(num_fragments):
|
|
if i not in confirmed_fragments:
|
|
recovered = mean_confirmed * (PHI ** (random.random() - 0.5))
|
|
total_output_sol += recovered
|
|
self.state.recovery_successes += 1
|
|
|
|
self._add_dag_node("recovery_success", {
|
|
"recovered_fragments": num_lost,
|
|
"method": "phi_scale"
|
|
})
|
|
|
|
# Convert SOL back to USDC
|
|
current_price = self.pools[0].get_price(False)
|
|
output_usdc = total_output_sol * current_price
|
|
|
|
profit = output_usdc - amount_usdc
|
|
|
|
if profit > 0:
|
|
self.state.lifetime_profit += profit
|
|
else:
|
|
self.state.lifetime_loss += abs(profit)
|
|
|
|
self.state.balance_usdc += profit
|
|
self.state.balance_sol -= amount_usdc / self.pools[0].get_price(True)
|
|
|
|
if self.state.balance_usdc < self.state.lowest_balance:
|
|
self.state.lowest_balance = self.state.balance_usdc
|
|
if self.state.went_negative_at is None and self.state.balance_usdc < 0:
|
|
self.state.went_negative_at = self.state.time_alive_seconds
|
|
self._add_dag_node("went_negative", {
|
|
"balance": self.state.balance_usdc,
|
|
"timestamp": self.state.time_alive_seconds
|
|
})
|
|
|
|
self.state.trade_history.append({
|
|
"timestamp": self.state.time_alive_seconds,
|
|
"amount": amount_usdc,
|
|
"fragments": num_fragments,
|
|
"output_usdc": output_usdc,
|
|
"profit": profit,
|
|
"balance_after": self.state.balance_usdc,
|
|
"predicted_direction": predicted_dir,
|
|
"confidence": confidence
|
|
})
|
|
|
|
self.state.num_trades += 1
|
|
return profit
|
|
|
|
def save_checkpoint(self):
|
|
"""Save state to file"""
|
|
with open(self.checkpoint_file, "w") as f:
|
|
json.dump(self.state.to_dict(), f, indent=2)
|
|
|
|
def save_dag(self):
|
|
"""Save DAG to file for analysis"""
|
|
dag_data = {
|
|
"agent_id": self.agent_id,
|
|
"session_id": self.session_id,
|
|
"num_nodes": len(self.state.dag_nodes),
|
|
"dag_heads": self.state.dag_heads,
|
|
"nodes": [
|
|
{
|
|
"node_id": n.node_id,
|
|
"timestamp": n.timestamp,
|
|
"event_type": n.event_type,
|
|
"parent_hash": n.parent_hash,
|
|
"hash": n.hash,
|
|
"data": n.data
|
|
}
|
|
for n in self.state.dag_nodes[-100:] # Keep last 100 nodes
|
|
]
|
|
}
|
|
with open(self.dag_file, "w") as f:
|
|
json.dump(dag_data, f, indent=2)
|
|
|
|
def run_for_duration(self, duration_seconds: int = 28800): # 8 hours = 28800 seconds
|
|
"""Run trading agent for specified duration"""
|
|
self.log(f"Starting paper trader agent {self.agent_id} with {self.state.balance_usdc:.2f} USDC")
|
|
self._add_dag_node("agent_start", {
|
|
"starting_balance_usdc": self.state.balance_usdc,
|
|
"starting_balance_sol": self.state.balance_sol,
|
|
"duration_seconds": duration_seconds
|
|
})
|
|
|
|
start_time = time.time()
|
|
last_checkpoint = start_time
|
|
last_dag_save = start_time
|
|
|
|
while True:
|
|
elapsed = time.time() - start_time
|
|
self.state.time_alive_seconds = int(elapsed * SIMULATION_SPEED)
|
|
|
|
# Record price
|
|
current_price = self.pools[0].get_price(True)
|
|
self.price_history["SOL/USDC"].append(current_price)
|
|
|
|
# Stop if out of USDC
|
|
if self.state.balance_usdc < 0.1:
|
|
self.log(f"❌ STOPPED: Balance too low ({self.state.balance_usdc:.2f})")
|
|
self._add_dag_node("agent_stop", {
|
|
"reason": "balance_depleted",
|
|
"final_balance": self.state.balance_usdc
|
|
})
|
|
break
|
|
|
|
# Stop if duration exceeded
|
|
if self.state.time_alive_seconds >= duration_seconds:
|
|
self.log(f"✅ SURVIVED: Completed {duration_seconds}s with {self.state.balance_usdc:.2f} USDC")
|
|
self._add_dag_node("agent_complete", {
|
|
"reason": "duration_exceeded",
|
|
"final_balance": self.state.balance_usdc,
|
|
"total_trades": self.state.num_trades
|
|
})
|
|
break
|
|
|
|
# Execute trade (probability based on prediction confidence)
|
|
pred_dir, confidence = self.predict_price_direction()
|
|
should_trade = (confidence > 0.4) or (random.random() < 0.3)
|
|
|
|
if should_trade:
|
|
risk_amount = max(0.1, self.state.balance_usdc * random.uniform(0.01, 0.05))
|
|
self.execute_trade(risk_amount, num_fragments=random.randint(1, 3))
|
|
|
|
# Checkpoint every 10 minutes (real time)
|
|
if time.time() - last_checkpoint > 600:
|
|
self.save_checkpoint()
|
|
last_checkpoint = time.time()
|
|
self.log(f"Checkpoint: Balance={self.state.balance_usdc:.2f}, Trades={self.state.num_trades}")
|
|
|
|
# Save DAG every 5 minutes
|
|
if time.time() - last_dag_save > 300:
|
|
self.save_dag()
|
|
last_dag_save = time.time()
|
|
|
|
time.sleep(0.01 / SIMULATION_SPEED)
|
|
|
|
# Final checkpoint and DAG
|
|
self.save_checkpoint()
|
|
self.save_dag()
|
|
self.log(f"Agent {self.agent_id} FINAL STATE: "
|
|
f"USDC={self.state.balance_usdc:.2f}, "
|
|
f"Profit={self.state.lifetime_profit:.2f}, "
|
|
f"Trades={self.state.num_trades}, "
|
|
f"Recovery Rate={self.state.recovery_successes}/{self.state.recovery_attempts}")
|
|
|
|
return self.state
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
if len(sys.argv) < 3:
|
|
print("Usage: python paper_trader_agent.py <agent_id> <session_id> [duration_seconds]")
|
|
sys.exit(1)
|
|
|
|
agent_id = int(sys.argv[1])
|
|
session_id = sys.argv[2]
|
|
duration = int(sys.argv[3]) if len(sys.argv) > 3 else 28800
|
|
|
|
trader = PaperTrader(agent_id, session_id, starting_usdc=50.0)
|
|
final_state = trader.run_for_duration(duration)
|
|
|
|
print(json.dumps(final_state.to_dict(), indent=2))
|