File: //proc/self/root/opt/trading-bot/allocator.py
import logging
from config import settings
from risk_engine import RiskEngine
logger = logging.getLogger(__name__)
class Allocator:
def __init__(self, risk_engine: RiskEngine):
self.risk_engine = risk_engine
def calculate_target_positions(self, signals: list, current_positions: list, portfolio_state: dict) -> dict:
targets = {}
drawdown_pct = portfolio_state.get("drawdown_pct", 0.0)
throttle = self.risk_engine.get_drawdown_throttle(drawdown_pct)
total_max_alloc = settings.MAX_EXPOSURE_USD * throttle
if not signals:
return targets
for signal in signals:
action = signal.get("action", "NEUTRAL")
leverage = signal.get("leverage", 1.0)
edge = signal.get("edge_bps", 999.0)
# Additional safety: Reduce allocation for lower-confidence signals
conf_multiplier = 1.0
if edge < (settings.FEE_BPS + settings.SLIPPAGE_BPS) * 3:
conf_multiplier = 0.5 # Scale down if edge is thin
alloc_per_signal = min(total_max_alloc / len(signals), settings.MAX_PER_ASSET_EXPOSURE_USD) * leverage * conf_multiplier
if action == "LONG_BASIS":
spot_symbol = signal.get("spot_symbol")
perp_symbol = signal.get("perp_symbol")
spot_price = signal.get("spot_price")
perp_price = signal.get("perp_price")
if spot_price and perp_price:
targets[spot_symbol] = alloc_per_signal / spot_price
targets[perp_symbol] = -alloc_per_signal / perp_price
elif action == "LONG":
symbol = signal.get("symbol") or signal.get("spot_symbol") or (f"{signal.get('asset')}/USDC" if signal.get('asset') else None)
price = signal.get("price") or signal.get("spot_price")
if symbol and price:
targets[symbol] = alloc_per_signal / price
elif action == "SHORT":
# For high yield, we use perps for shorting
asset = signal.get("asset")
symbol = f"{asset}/USDC:USDC" if asset else None
price = signal.get("price") or signal.get("perp_price")
if symbol and price:
targets[symbol] = -alloc_per_signal / price
elif action == "CLOSE":
# Only close for specific symbols provided in signal
symbols_to_close = []
if signal.get("symbol"): symbols_to_close.append(signal.get("symbol"))
if signal.get("spot_symbol"): symbols_to_close.append(signal.get("spot_symbol"))
if signal.get("perp_symbol"): symbols_to_close.append(signal.get("perp_symbol"))
for s in symbols_to_close:
targets[s] = 0.0
return targets
def get_target_qty(self, signal: dict, current_equity: float, available_cash: float = None) -> float:
"""Calculates target quantity for a single signal based on current equity and available cash."""
action = signal.get("action", "NEUTRAL")
leverage = float(signal.get("leverage", 1.0))
price = signal.get("price") or signal.get("spot_price") or signal.get("perp_price")
if action == "CLOSE" or not price:
return 0.0
# Cap leverage at 2x to prevent over-allocation
leverage = min(leverage, 2.0)
# Target a specific $ amount per strategy, but cap by current equity
target_usd = max(0.0, min(settings.MAX_PER_ASSET_EXPOSURE_USD, current_equity * leverage))
# Confidence-weighted sizing (strategies can express conviction)
confidence = signal.get("confidence", 1.0)
target_usd *= max(0.1, min(1.0, confidence))
# Cap by available cash (keep 5% reserve for fees/slippage)
if available_cash is not None and available_cash > 0:
cash_cap = available_cash * 0.95
target_usd = min(target_usd, cash_cap)
elif available_cash is not None:
return 0.0 # No cash available
qty = target_usd / float(price)
return -qty if action == "SHORT" else qty