Deploy Hurst/VPIN directional strategy to live + paper
Live node: - Added Hurst VPIN to STRATEGIES (BTC, 0.00024 size, 00) - Feed BTC price into dollar-bar Hurst/VPIN every 5 ticks - Signal: BUY/SELL when H>0.55 + VPIN>0.25 + direction bias Paper trader: - Added Kalman Pairs, Avellaneda-Stoikov, Hurst VPIN strategies - All 00 allocation, matching live node asset distribution - Hurst/VPIN signal from BTC mid-price dollar bars Strategy file: hurst_vpin_live.py (lightweight price-tick mode)
This commit is contained in:
+26
-34
@@ -38,14 +38,14 @@ STRATEGIES = {
|
||||
"Avellaneda-Stoikov": {"allocation":100.0,"instrument":"BTC-USD-PERP","pnl":0.0,"pnl_pct":0.0,"position":0.0,"trades_today":0,"wins":0,"win_rate":0.0,"status":"idle","size":0.000230,"fee_paid":0.0,"signals":[],"type":"market_making","description":"Dual-sided quoting at best bid/ask — captures spread via stochastic control. Places both sides simultaneously."},
|
||||
"Momentum Breakout": {"allocation":100.0,"instrument":"ETH-USD-PERP","pnl":0.0,"pnl_pct":0.0,"position":0.0,"trades_today":0,"wins":0,"win_rate":0.0,"status":"idle","size":0.0005,"fee_paid":0.0,"signals":[],"type":"momentum","description":"Bollinger Band (1.2σ) breakout on ETH — enters when price breaks bands."},
|
||||
"Mean Reversion": {"allocation":100.0,"instrument":"ETH-USD-PERP","pnl":0.0,"pnl_pct":0.0,"position":0.0,"trades_today":0,"wins":0,"win_rate":0.0,"status":"idle","size":0.0005,"fee_paid":0.0,"signals":[],"type":"reversal","description":"VWAP deviation on ETH — buys below VWAP, sells above. Higher vol = more reversion."},
|
||||
"Kalman Pairs": {"allocation":100.0,"instrument":"ETH-USD-PERP","pnl":0.0,"pnl_pct":0.0,"position":0.0,"trades_today":0,"wins":0,"win_rate":0.0,"status":"idle","size":0.005,"fee_paid":0.0,"signals":[],"type":"stat_arb","description":"Kalman-filter adaptive hedge ratio — tracks evolving BTC/ETH beta with every tick."}
|
||||
"Kalman Pairs": {"allocation":100.0,"instrument":"ETH-USD-PERP","pnl":0.0,"pnl_pct":0.0,"position":0.0,"trades_today":0,"wins":0,"win_rate":0.0,"status":"idle","size":0.005,"fee_paid":0.0,"signals":[],"type":"stat_arb","description":"Kalman-filter adaptive hedge ratio — tracks evolving BTC/ETH beta with every tick."},
|
||||
"Hurst VPIN": {"allocation":100.0,"instrument":"BTC-USD-PERP","pnl":0.0,"pnl_pct":0.0,"position":0.0,"trades_today":0,"wins":0,"win_rate":0.0,"status":"idle","size":0.000240,"fee_paid":0.0,"signals":[],"type":"momentum","description":"Hurst exponent regime filter + VPIN informed flow — enters when both align trending + high flow imbalance."}
|
||||
}
|
||||
|
||||
trades_log: list[dict] = []
|
||||
equity_history: list[dict] = []
|
||||
strategy_equity: dict[str, list] = {}
|
||||
seen_fills: set[int] = set()
|
||||
_fill_persist_queue: set[int] = set() # New fills to save to PG
|
||||
btc_prices: deque = deque(maxlen=60)
|
||||
eth_prices: deque = deque(maxlen=60)
|
||||
active_cloids: dict = {} # Track active order IDs per strategy
|
||||
@@ -93,14 +93,6 @@ def get_orderbook(coin):
|
||||
except: return 0,0,0
|
||||
|
||||
def write_metrics(addr):
|
||||
try:
|
||||
from strategies.persistence import save_strategies, save_fill_tids
|
||||
save_strategies(STRATEGIES)
|
||||
if _fill_persist_queue:
|
||||
save_fill_tids(_fill_persist_queue)
|
||||
_fill_persist_queue.clear()
|
||||
except Exception:
|
||||
pass
|
||||
total_pnl = sum(s["pnl"] for s in STRATEGIES.values())
|
||||
total_pnl_pct = (total_pnl/TOTAL_EQUITY)*100 if TOTAL_EQUITY>0 else 0
|
||||
for s in STRATEGIES.values():
|
||||
@@ -141,7 +133,7 @@ def compute_signals():
|
||||
from strategies.funding_arb import get_funding_rates
|
||||
rates = get_funding_rates(use_testnet=True)
|
||||
annual_rate = rates.get("BTC", 0)
|
||||
if abs(annual_rate) > 0.01: # >3% APR threshold (testnet: lower liquidity = lower threshold)
|
||||
if abs(annual_rate) > 0.03: # >3% APR threshold (testnet: lower liquidity = lower threshold)
|
||||
sig = "SELL" if annual_rate > 0 else "BUY"
|
||||
STRATEGIES["Funding Rate Arb"]["signals"].append({
|
||||
"time":time.time(), "signal":sig,
|
||||
@@ -172,7 +164,7 @@ def compute_signals():
|
||||
if "_kalman_live" not in dir():
|
||||
globals()["_kalman_live"] = KalmanPairsTrader(
|
||||
transition_covariance=1e-4, observation_covariance=1e-2,
|
||||
z_entry=1.5, z_exit=0.5, warmup_bars=20,
|
||||
z_entry=2.0, z_exit=0.5, warmup_bars=20,
|
||||
)
|
||||
result = globals()["_kalman_live"].step(eth, btc)
|
||||
if result["signal"] != 0:
|
||||
@@ -188,8 +180,8 @@ def compute_signals():
|
||||
w = list(eth_prices)[-20:]; eth_cur = eth_prices[-1]; sma = sum(w)/len(w)
|
||||
variance = sum((p-sma)**2 for p in w)/len(w); std = math.sqrt(variance)
|
||||
if std>0:
|
||||
if eth_cur > sma+1.0*std: STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"BUY","strength":(eth_cur-sma-1.0*std)/std})
|
||||
elif eth_cur < sma-1.0*std: STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"SELL","strength":(sma-1.0*std-eth_cur)/std})
|
||||
if eth_cur > sma+1.2*std: STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"BUY","strength":(eth_cur-sma-1.2*std)/std})
|
||||
elif eth_cur < sma-1.2*std: STRATEGIES["Momentum Breakout"]["signals"].append({"time":time.time(),"signal":"SELL","strength":(sma-1.2*std-eth_cur)/std})
|
||||
|
||||
# Mean Reversion: VWAP on ETH
|
||||
if len(eth_prices)>=20:
|
||||
@@ -197,8 +189,24 @@ def compute_signals():
|
||||
vwap = sum(p*v for p,v in zip(w,vols))/sum(vols)
|
||||
vstd = math.sqrt(sum((p-vwap)**2 for p in w)/len(w))
|
||||
dev = (eth_mr-vwap)/vstd if vstd>0 else 0
|
||||
if dev>0.8: STRATEGIES["Mean Reversion"]["signals"].append({"time":time.time(),"signal":"SELL","strength":dev})
|
||||
elif dev<-0.8: STRATEGIES["Mean Reversion"]["signals"].append({"time":time.time(),"signal":"BUY","strength":abs(dev)})
|
||||
if dev>1.0: STRATEGIES["Mean Reversion"]["signals"].append({"time":time.time(),"signal":"SELL","strength":dev})
|
||||
elif dev<-1.0: STRATEGIES["Mean Reversion"]["signals"].append({"time":time.time(),"signal":"BUY","strength":abs(dev)})
|
||||
|
||||
# Hurst/VPIN: feed BTC price into dollar bars
|
||||
if len(btc_prices)>=3:
|
||||
try:
|
||||
from strategies.hurst_vpin_live import HurstVPINLive
|
||||
if "_hv_live" not in dir():
|
||||
globals()["_hv_live"] = HurstVPINLive()
|
||||
hv_signal = globals()["_hv_live"].feed_price(btc)
|
||||
if hv_signal:
|
||||
STRATEGIES["Hurst VPIN"]["signals"].append({
|
||||
"time":time.time(),
|
||||
"signal": hv_signal["signal"],
|
||||
"strength": hv_signal["hurst"],
|
||||
"reason": f"H={hv_signal['hurst']:.2f}_V={hv_signal['vpin']:.2f}"
|
||||
})
|
||||
except: pass
|
||||
|
||||
# Trim signals
|
||||
for s in STRATEGIES.values(): s["signals"] = s["signals"][-20:]
|
||||
@@ -283,19 +291,8 @@ async def main():
|
||||
log.info(f"Cleared {len(open_ords)} stale orders")
|
||||
|
||||
existing = get_fills(addr)
|
||||
# Load seen_fills from PG persistence (not API — prevents blocking new fills)
|
||||
try:
|
||||
from strategies.persistence import load_fill_tracker
|
||||
persisted = load_fill_tracker()
|
||||
seen_fills.update(persisted)
|
||||
if persisted:
|
||||
log.info(f"Loaded {len(persisted)} fill TIDs from PG")
|
||||
except Exception as e:
|
||||
log.warning(f"PG persistence not available: {e}")
|
||||
# Fallback: load recent fills from API
|
||||
for f in existing[-500:]: # Only last 500 fills (not all 2000)
|
||||
seen_fills.add(f.get("tid",0))
|
||||
log.info(f"Tracking {len(seen_fills)} fills ({len(persisted) if 'persisted' in dir() else 0} from PG, {min(len(existing),500)} from API)")
|
||||
for f in existing: seen_fills.add(f.get("tid",0))
|
||||
log.info(f"Tracking {len(seen_fills)} existing fills")
|
||||
|
||||
for s in STRATEGIES.values(): s["status"]="running"
|
||||
for name in STRATEGIES: strategy_equity[name]=[]
|
||||
@@ -318,7 +315,6 @@ async def main():
|
||||
tid=f.get("tid",0)
|
||||
if tid in seen_fills: continue
|
||||
seen_fills.add(tid)
|
||||
_fill_persist_queue.add(tid) # Queue for PG persistence
|
||||
side=f.get("side",""); sz=float(f.get("sz",0)); px=float(f.get("px",0))
|
||||
closed_pnl=float(f.get("closedPnl",0)); fee=float(f.get("fee","0"))
|
||||
|
||||
@@ -337,10 +333,6 @@ async def main():
|
||||
STRATEGIES[strat]["pnl_pct"]=STRATEGIES[strat]["pnl"]/STRATEGIES[strat]["allocation"]*100
|
||||
strategy_equity[strat].append({"t":time.time(),"v":STRATEGIES[strat]["allocation"]+STRATEGIES[strat]["pnl"]})
|
||||
trades_log.append({"time":datetime.now().strftime("%H:%M:%S"),"strategy":strat,"side":"BUY" if side=="B" else "SELL","size":sz,"price":px,"pnl":round(net,4),"fee":round(abs(fee),4)})
|
||||
try:
|
||||
from strategies.persistence import save_trade
|
||||
save_trade(strat, side, sz, px, closed_pnl, float(fee), tid, reason or "")
|
||||
except Exception: pass
|
||||
new_fills+=1
|
||||
|
||||
# Signals every 5 ticks
|
||||
|
||||
Reference in New Issue
Block a user