mirror of
https://github.com/d0zingcat/deploy.git
synced 2026-05-13 15:09:33 +00:00
476 lines
28 KiB
Python
476 lines
28 KiB
Python
from decimal import Decimal
|
|
from typing import List
|
|
|
|
import numpy as np
|
|
from sklearn.linear_model import LinearRegression
|
|
|
|
from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PriceType, TradeType
|
|
from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
|
|
from hummingbot.strategy_v2.controllers import ControllerBase, ControllerConfigBase
|
|
from hummingbot.strategy_v2.executors.data_types import ConnectorPair, PositionSummary
|
|
from hummingbot.strategy_v2.executors.order_executor.data_types import ExecutionStrategy, OrderExecutorConfig
|
|
from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig, TripleBarrierConfig
|
|
from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction, StopExecutorAction
|
|
|
|
|
|
class StatArbConfig(ControllerConfigBase):
|
|
"""
|
|
Configuration for a statistical arbitrage controller that trades two cointegrated assets.
|
|
"""
|
|
controller_type: str = "generic"
|
|
controller_name: str = "stat_arb"
|
|
candles_config: List[CandlesConfig] = []
|
|
connector_pair_dominant: ConnectorPair = ConnectorPair(connector_name="binance_perpetual", trading_pair="SOL-USDT")
|
|
connector_pair_hedge: ConnectorPair = ConnectorPair(connector_name="binance_perpetual", trading_pair="POPCAT-USDT")
|
|
interval: str = "1m"
|
|
lookback_period: int = 300
|
|
entry_threshold: Decimal = Decimal("2.0")
|
|
take_profit: Decimal = Decimal("0.0008")
|
|
tp_global: Decimal = Decimal("0.01")
|
|
sl_global: Decimal = Decimal("0.05")
|
|
min_amount_quote: Decimal = Decimal("10")
|
|
quoter_spread: Decimal = Decimal("0.0001")
|
|
quoter_cooldown: int = 30
|
|
quoter_refresh: int = 10
|
|
max_orders_placed_per_side: int = 2
|
|
max_orders_filled_per_side: int = 2
|
|
max_position_deviation: Decimal = Decimal("0.1")
|
|
pos_hedge_ratio: Decimal = Decimal("1.0")
|
|
leverage: int = 20
|
|
position_mode: PositionMode = PositionMode.HEDGE
|
|
|
|
@property
|
|
def triple_barrier_config(self) -> TripleBarrierConfig:
|
|
return TripleBarrierConfig(
|
|
take_profit=self.take_profit,
|
|
open_order_type=OrderType.LIMIT_MAKER,
|
|
take_profit_order_type=OrderType.LIMIT_MAKER,
|
|
)
|
|
|
|
def update_markets(self, markets: dict) -> dict:
|
|
"""Update markets dictionary with both trading pairs"""
|
|
# Add dominant pair
|
|
if self.connector_pair_dominant.connector_name not in markets:
|
|
markets[self.connector_pair_dominant.connector_name] = set()
|
|
markets[self.connector_pair_dominant.connector_name].add(self.connector_pair_dominant.trading_pair)
|
|
|
|
# Add hedge pair
|
|
if self.connector_pair_hedge.connector_name not in markets:
|
|
markets[self.connector_pair_hedge.connector_name] = set()
|
|
markets[self.connector_pair_hedge.connector_name].add(self.connector_pair_hedge.trading_pair)
|
|
|
|
return markets
|
|
|
|
|
|
class StatArb(ControllerBase):
|
|
"""
|
|
Statistical arbitrage controller that trades two cointegrated assets.
|
|
"""
|
|
|
|
def __init__(self, config: StatArbConfig, *args, **kwargs):
|
|
super().__init__(config, *args, **kwargs)
|
|
self.config = config
|
|
self.theoretical_dominant_quote = self.config.total_amount_quote * (1 / (1 + self.config.pos_hedge_ratio))
|
|
self.theoretical_hedge_quote = self.config.total_amount_quote * (self.config.pos_hedge_ratio / (1 + self.config.pos_hedge_ratio))
|
|
|
|
# Initialize processed data dictionary
|
|
self.processed_data = {
|
|
"dominant_price": None,
|
|
"hedge_price": None,
|
|
"spread": None,
|
|
"z_score": None,
|
|
"hedge_ratio": None,
|
|
"position_dominant": Decimal("0"),
|
|
"position_hedge": Decimal("0"),
|
|
"active_orders_dominant": [],
|
|
"active_orders_hedge": [],
|
|
"pair_pnl": Decimal("0"),
|
|
"signal": 0 # 0: no signal, 1: long dominant/short hedge, -1: short dominant/long hedge
|
|
}
|
|
|
|
# Setup candles config if not already set
|
|
if len(self.config.candles_config) == 0:
|
|
max_records = self.config.lookback_period + 20 # extra records for safety
|
|
self.max_records = max_records
|
|
self.config.candles_config = [
|
|
CandlesConfig(
|
|
connector=self.config.connector_pair_dominant.connector_name,
|
|
trading_pair=self.config.connector_pair_dominant.trading_pair,
|
|
interval=self.config.interval,
|
|
max_records=max_records
|
|
),
|
|
CandlesConfig(
|
|
connector=self.config.connector_pair_hedge.connector_name,
|
|
trading_pair=self.config.connector_pair_hedge.trading_pair,
|
|
interval=self.config.interval,
|
|
max_records=max_records
|
|
)
|
|
]
|
|
if "_perpetual" in self.config.connector_pair_dominant.connector_name:
|
|
connector = self.market_data_provider.get_connector(self.config.connector_pair_dominant.connector_name)
|
|
connector.set_position_mode(self.config.position_mode)
|
|
connector.set_leverage(self.config.connector_pair_dominant.trading_pair, self.config.leverage)
|
|
if "_perpetual" in self.config.connector_pair_hedge.connector_name:
|
|
connector = self.market_data_provider.get_connector(self.config.connector_pair_hedge.connector_name)
|
|
connector.set_position_mode(self.config.position_mode)
|
|
connector.set_leverage(self.config.connector_pair_hedge.trading_pair, self.config.leverage)
|
|
|
|
def determine_executor_actions(self) -> List[ExecutorAction]:
|
|
"""
|
|
The execution logic for the statistical arbitrage strategy.
|
|
Market Data Conditions: Signal is generated based on the z-score of the spread between the two assets.
|
|
If signal == 1 --> long dominant/short hedge
|
|
If signal == -1 --> short dominant/long hedge
|
|
Execution Conditions: If the signal is generated add position executors to quote from the dominant and hedge markets.
|
|
We compare the current position with the theoretical position for the dominant and hedge assets.
|
|
If the current position + the active placed amount is greater than the theoretical position, can't place more orders.
|
|
If the imbalance scaled pct is greater than the threshold, we avoid placing orders in the market passed on filtered_connector_pair.
|
|
If the pnl of total position is greater than the take profit or lower than the stop loss, we close the position.
|
|
"""
|
|
actions: List[ExecutorAction] = []
|
|
# Check global take profit and stop loss
|
|
if self.processed_data["pair_pnl_pct"] > self.config.tp_global or self.processed_data["pair_pnl_pct"] < -self.config.sl_global:
|
|
# Close all positions
|
|
for position in self.positions_held:
|
|
actions.extend(self.get_executors_to_reduce_position(position))
|
|
return actions
|
|
# Check the signal
|
|
elif self.processed_data["signal"] != 0:
|
|
actions.extend(self.get_executors_to_quote())
|
|
actions.extend(self.get_executors_to_reduce_position_on_opposite_signal())
|
|
|
|
# Get the executors to keep position after a cooldown is reached
|
|
actions.extend(self.get_executors_to_keep_position())
|
|
actions.extend(self.get_executors_to_refresh())
|
|
|
|
return actions
|
|
|
|
def get_executors_to_reduce_position_on_opposite_signal(self) -> List[ExecutorAction]:
|
|
if self.processed_data["signal"] == 1:
|
|
dominant_side, hedge_side = TradeType.SELL, TradeType.BUY
|
|
elif self.processed_data["signal"] == -1:
|
|
dominant_side, hedge_side = TradeType.BUY, TradeType.SELL
|
|
else:
|
|
return []
|
|
# Get executors to stop
|
|
dominant_active_executors_to_stop = self.filter_executors(self.executors_info, filter_func=lambda e: e.connector_name == self.config.connector_pair_dominant.connector_name and e.trading_pair == self.config.connector_pair_dominant.trading_pair and e.side == dominant_side)
|
|
hedge_active_executors_to_stop = self.filter_executors(self.executors_info, filter_func=lambda e: e.connector_name == self.config.connector_pair_hedge.connector_name and e.trading_pair == self.config.connector_pair_hedge.trading_pair and e.side == hedge_side)
|
|
stop_actions = [StopExecutorAction(controller_id=self.config.id, executor_id=executor.id, keep_position=False) for executor in dominant_active_executors_to_stop + hedge_active_executors_to_stop]
|
|
|
|
# Get order executors to reduce positions
|
|
reduce_actions: List[ExecutorAction] = []
|
|
for position in self.positions_held:
|
|
if position.connector_name == self.config.connector_pair_dominant.connector_name and position.trading_pair == self.config.connector_pair_dominant.trading_pair and position.side == dominant_side:
|
|
reduce_actions.extend(self.get_executors_to_reduce_position(position))
|
|
elif position.connector_name == self.config.connector_pair_hedge.connector_name and position.trading_pair == self.config.connector_pair_hedge.trading_pair and position.side == hedge_side:
|
|
reduce_actions.extend(self.get_executors_to_reduce_position(position))
|
|
return stop_actions + reduce_actions
|
|
|
|
def get_executors_to_keep_position(self) -> List[ExecutorAction]:
|
|
stop_actions: List[ExecutorAction] = []
|
|
for executor in self.processed_data["executors_dominant_filled"] + self.processed_data["executors_hedge_filled"]:
|
|
if self.market_data_provider.time() - executor.timestamp >= self.config.quoter_cooldown:
|
|
# Create a new executor to keep the position
|
|
stop_actions.append(StopExecutorAction(controller_id=self.config.id, executor_id=executor.id, keep_position=True))
|
|
return stop_actions
|
|
|
|
def get_executors_to_refresh(self) -> List[ExecutorAction]:
|
|
refresh_actions: List[ExecutorAction] = []
|
|
for executor in self.processed_data["executors_dominant_placed"] + self.processed_data["executors_hedge_placed"]:
|
|
if self.market_data_provider.time() - executor.timestamp >= self.config.quoter_refresh:
|
|
# Create a new executor to refresh the position
|
|
refresh_actions.append(StopExecutorAction(controller_id=self.config.id, executor_id=executor.id, keep_position=False))
|
|
return refresh_actions
|
|
|
|
def get_executors_to_quote(self) -> List[ExecutorAction]:
|
|
"""
|
|
Get Order Executor to quote from the dominant and hedge markets.
|
|
"""
|
|
actions: List[ExecutorAction] = []
|
|
trade_type_dominant = TradeType.BUY if self.processed_data["signal"] == 1 else TradeType.SELL
|
|
trade_type_hedge = TradeType.SELL if self.processed_data["signal"] == 1 else TradeType.BUY
|
|
|
|
# Analyze dominant active orders, max deviation and imbalance to create a new executor
|
|
if self.processed_data["dominant_gap"] > Decimal("0") and \
|
|
self.processed_data["filter_connector_pair"] != self.config.connector_pair_dominant and \
|
|
len(self.processed_data["executors_dominant_placed"]) < self.config.max_orders_placed_per_side and \
|
|
len(self.processed_data["executors_dominant_filled"]) < self.config.max_orders_filled_per_side:
|
|
# Create Position Executor for dominant asset
|
|
if trade_type_dominant == TradeType.BUY:
|
|
price = self.processed_data["min_price_dominant"] * (1 - self.config.quoter_spread)
|
|
else:
|
|
price = self.processed_data["max_price_dominant"] * (1 + self.config.quoter_spread)
|
|
dominant_executor_config = PositionExecutorConfig(
|
|
timestamp=self.market_data_provider.time(),
|
|
connector_name=self.config.connector_pair_dominant.connector_name,
|
|
trading_pair=self.config.connector_pair_dominant.trading_pair,
|
|
side=trade_type_dominant,
|
|
entry_price=price,
|
|
amount=self.config.min_amount_quote / self.processed_data["dominant_price"],
|
|
triple_barrier_config=self.config.triple_barrier_config,
|
|
leverage=self.config.leverage,
|
|
)
|
|
actions.append(CreateExecutorAction(controller_id=self.config.id, executor_config=dominant_executor_config))
|
|
|
|
# Analyze hedge active orders, max deviation and imbalance to create a new executor
|
|
if self.processed_data["hedge_gap"] > Decimal("0") and \
|
|
self.processed_data["filter_connector_pair"] != self.config.connector_pair_hedge and \
|
|
len(self.processed_data["executors_hedge_placed"]) < self.config.max_orders_placed_per_side and \
|
|
len(self.processed_data["executors_hedge_filled"]) < self.config.max_orders_filled_per_side:
|
|
# Create Position Executor for hedge asset
|
|
if trade_type_hedge == TradeType.BUY:
|
|
price = self.processed_data["min_price_hedge"] * (1 - self.config.quoter_spread)
|
|
else:
|
|
price = self.processed_data["max_price_hedge"] * (1 + self.config.quoter_spread)
|
|
hedge_executor_config = PositionExecutorConfig(
|
|
timestamp=self.market_data_provider.time(),
|
|
connector_name=self.config.connector_pair_hedge.connector_name,
|
|
trading_pair=self.config.connector_pair_hedge.trading_pair,
|
|
side=trade_type_hedge,
|
|
entry_price=price,
|
|
amount=self.config.min_amount_quote / self.processed_data["hedge_price"],
|
|
triple_barrier_config=self.config.triple_barrier_config,
|
|
leverage=self.config.leverage,
|
|
)
|
|
actions.append(CreateExecutorAction(controller_id=self.config.id, executor_config=hedge_executor_config))
|
|
return actions
|
|
|
|
def get_executors_to_reduce_position(self, position: PositionSummary) -> List[ExecutorAction]:
|
|
"""
|
|
Get Order Executor to reduce position.
|
|
"""
|
|
if position.amount > Decimal("0"):
|
|
# Close position
|
|
config = OrderExecutorConfig(
|
|
timestamp=self.market_data_provider.time(),
|
|
connector_name=position.connector_name,
|
|
trading_pair=position.trading_pair,
|
|
side=TradeType.BUY if position.side == TradeType.SELL else TradeType.SELL,
|
|
amount=position.amount,
|
|
position_action=PositionAction.CLOSE,
|
|
execution_strategy=ExecutionStrategy.MARKET,
|
|
leverage=self.config.leverage,
|
|
)
|
|
return [CreateExecutorAction(controller_id=self.config.id, executor_config=config)]
|
|
return []
|
|
|
|
async def update_processed_data(self):
|
|
"""
|
|
Update processed data with the latest market information and statistical calculations
|
|
needed for the statistical arbitrage strategy.
|
|
"""
|
|
# Stat arb analysis
|
|
spread, z_score = self.get_spread_and_z_score()
|
|
|
|
# Generate trading signal based on z-score
|
|
entry_threshold = float(self.config.entry_threshold)
|
|
if z_score > entry_threshold:
|
|
# Spread is too high, expect it to revert: long dominant, short hedge
|
|
signal = 1
|
|
dominant_side, hedge_side = TradeType.BUY, TradeType.SELL
|
|
elif z_score < -entry_threshold:
|
|
# Spread is too low, expect it to revert: short dominant, long hedge
|
|
signal = -1
|
|
dominant_side, hedge_side = TradeType.SELL, TradeType.BUY
|
|
else:
|
|
# No signal
|
|
signal = 0
|
|
dominant_side, hedge_side = None, None
|
|
|
|
# Current prices
|
|
dominant_price, hedge_price = self.get_pairs_prices()
|
|
|
|
# Get current positions stats by signal
|
|
positions_dominant = next((position for position in self.positions_held if position.connector_name == self.config.connector_pair_dominant.connector_name and position.trading_pair == self.config.connector_pair_dominant.trading_pair and (position.side == dominant_side or dominant_side is None)), None)
|
|
positions_hedge = next((position for position in self.positions_held if position.connector_name == self.config.connector_pair_hedge.connector_name and position.trading_pair == self.config.connector_pair_hedge.trading_pair and (position.side == hedge_side or hedge_side is None)), None)
|
|
# Get position stats
|
|
position_dominant_quote = positions_dominant.amount_quote if positions_dominant else Decimal("0")
|
|
position_hedge_quote = positions_hedge.amount_quote if positions_hedge else Decimal("0")
|
|
position_dominant_pnl_quote = positions_dominant.global_pnl_quote if positions_dominant else Decimal("0")
|
|
position_hedge_pnl_quote = positions_hedge.global_pnl_quote if positions_hedge else Decimal("0")
|
|
pair_pnl_pct = (position_dominant_pnl_quote + position_hedge_pnl_quote) / (position_dominant_quote + position_hedge_quote) if (position_dominant_quote + position_hedge_quote) != 0 else Decimal("0")
|
|
# Get active executors
|
|
executors_dominant_placed, executors_dominant_filled = self.get_executors_dominant()
|
|
executors_hedge_placed, executors_hedge_filled = self.get_executors_hedge()
|
|
min_price_dominant = Decimal(str(min([executor.config.entry_price for executor in executors_dominant_placed]))) if executors_dominant_placed else None
|
|
max_price_dominant = Decimal(str(max([executor.config.entry_price for executor in executors_dominant_placed]))) if executors_dominant_placed else None
|
|
min_price_hedge = Decimal(str(min([executor.config.entry_price for executor in executors_hedge_placed]))) if executors_hedge_placed else None
|
|
max_price_hedge = Decimal(str(max([executor.config.entry_price for executor in executors_hedge_placed]))) if executors_hedge_placed else None
|
|
|
|
active_amount_dominant = Decimal(str(sum([executor.filled_amount_quote for executor in executors_dominant_filled])))
|
|
active_amount_hedge = Decimal(str(sum([executor.filled_amount_quote for executor in executors_hedge_filled])))
|
|
|
|
# Compute imbalance based on the hedge ratio
|
|
dominant_gap = self.theoretical_dominant_quote - position_dominant_quote - active_amount_dominant
|
|
hedge_gap = self.theoretical_hedge_quote - position_hedge_quote - active_amount_hedge
|
|
imbalance = position_dominant_quote - position_hedge_quote
|
|
imbalance_scaled = position_dominant_quote - position_hedge_quote * self.config.pos_hedge_ratio
|
|
imbalance_scaled_pct = imbalance_scaled / position_dominant_quote if position_dominant_quote != Decimal("0") else Decimal("0")
|
|
filter_connector_pair = None
|
|
if imbalance_scaled_pct > self.config.max_position_deviation:
|
|
# Avoid placing orders in the dominant market
|
|
filter_connector_pair = self.config.connector_pair_dominant
|
|
elif imbalance_scaled_pct < -self.config.max_position_deviation:
|
|
# Avoid placing orders in the hedge market
|
|
filter_connector_pair = self.config.connector_pair_hedge
|
|
|
|
# Update processed data
|
|
self.processed_data.update({
|
|
"dominant_price": Decimal(str(dominant_price)),
|
|
"hedge_price": Decimal(str(hedge_price)),
|
|
"spread": Decimal(str(spread)),
|
|
"z_score": Decimal(str(z_score)),
|
|
"dominant_gap": Decimal(str(dominant_gap)),
|
|
"hedge_gap": Decimal(str(hedge_gap)),
|
|
"position_dominant_quote": position_dominant_quote,
|
|
"position_hedge_quote": position_hedge_quote,
|
|
"active_amount_dominant": active_amount_dominant,
|
|
"active_amount_hedge": active_amount_hedge,
|
|
"signal": signal,
|
|
# Store full dataframes for reference
|
|
"imbalance": Decimal(str(imbalance)),
|
|
"imbalance_scaled_pct": Decimal(str(imbalance_scaled_pct)),
|
|
"filter_connector_pair": filter_connector_pair,
|
|
"min_price_dominant": min_price_dominant if min_price_dominant is not None else Decimal(str(dominant_price)),
|
|
"max_price_dominant": max_price_dominant if max_price_dominant is not None else Decimal(str(dominant_price)),
|
|
"min_price_hedge": min_price_hedge if min_price_hedge is not None else Decimal(str(hedge_price)),
|
|
"max_price_hedge": max_price_hedge if max_price_hedge is not None else Decimal(str(hedge_price)),
|
|
"executors_dominant_filled": executors_dominant_filled,
|
|
"executors_hedge_filled": executors_hedge_filled,
|
|
"executors_dominant_placed": executors_dominant_placed,
|
|
"executors_hedge_placed": executors_hedge_placed,
|
|
"pair_pnl_pct": pair_pnl_pct,
|
|
})
|
|
|
|
def get_spread_and_z_score(self):
|
|
# Fetch candle data for both assets
|
|
dominant_df = self.market_data_provider.get_candles_df(
|
|
connector_name=self.config.connector_pair_dominant.connector_name,
|
|
trading_pair=self.config.connector_pair_dominant.trading_pair,
|
|
interval=self.config.interval,
|
|
max_records=self.max_records
|
|
)
|
|
|
|
hedge_df = self.market_data_provider.get_candles_df(
|
|
connector_name=self.config.connector_pair_hedge.connector_name,
|
|
trading_pair=self.config.connector_pair_hedge.trading_pair,
|
|
interval=self.config.interval,
|
|
max_records=self.max_records
|
|
)
|
|
|
|
if dominant_df.empty or hedge_df.empty:
|
|
self.logger().warning("Not enough candle data available for statistical analysis")
|
|
return
|
|
|
|
# Extract close prices
|
|
dominant_prices = dominant_df['close'].values
|
|
hedge_prices = hedge_df['close'].values
|
|
|
|
# Ensure we have enough data and both series have the same length
|
|
min_length = min(len(dominant_prices), len(hedge_prices))
|
|
if min_length < self.config.lookback_period:
|
|
self.logger().warning(
|
|
f"Not enough data points for analysis. Required: {self.config.lookback_period}, Available: {min_length}")
|
|
return
|
|
|
|
# Use the most recent data points
|
|
dominant_prices = dominant_prices[-self.config.lookback_period:]
|
|
hedge_prices = hedge_prices[-self.config.lookback_period:]
|
|
|
|
# Convert to numpy arrays
|
|
dominant_prices_np = np.array(dominant_prices, dtype=float)
|
|
hedge_prices_np = np.array(hedge_prices, dtype=float)
|
|
|
|
# Calculate percentage returns
|
|
dominant_pct_change = np.diff(dominant_prices_np) / dominant_prices_np[:-1]
|
|
hedge_pct_change = np.diff(hedge_prices_np) / hedge_prices_np[:-1]
|
|
|
|
# Convert to cumulative returns
|
|
dominant_cum_returns = np.cumprod(dominant_pct_change + 1)
|
|
hedge_cum_returns = np.cumprod(hedge_pct_change + 1)
|
|
|
|
# Normalize to start at 1
|
|
dominant_cum_returns = dominant_cum_returns / dominant_cum_returns[0] if len(dominant_cum_returns) > 0 else np.array([1.0])
|
|
hedge_cum_returns = hedge_cum_returns / hedge_cum_returns[0] if len(hedge_cum_returns) > 0 else np.array([1.0])
|
|
|
|
# Perform linear regression
|
|
dominant_cum_returns_reshaped = dominant_cum_returns.reshape(-1, 1)
|
|
reg = LinearRegression().fit(dominant_cum_returns_reshaped, hedge_cum_returns)
|
|
alpha = reg.intercept_
|
|
beta = reg.coef_[0]
|
|
self.processed_data.update({
|
|
"alpha": alpha,
|
|
"beta": beta,
|
|
})
|
|
|
|
# Calculate spread as percentage difference from predicted value
|
|
y_pred = alpha + beta * dominant_cum_returns
|
|
spread_pct = (hedge_cum_returns - y_pred) / y_pred * 100
|
|
|
|
# Calculate z-score
|
|
mean_spread = np.mean(spread_pct)
|
|
std_spread = np.std(spread_pct)
|
|
if std_spread == 0:
|
|
self.logger().warning("Standard deviation of spread is zero, cannot calculate z-score")
|
|
return
|
|
|
|
current_spread = spread_pct[-1]
|
|
current_z_score = (current_spread - mean_spread) / std_spread
|
|
|
|
return current_spread, current_z_score
|
|
|
|
def get_pairs_prices(self):
|
|
current_dominant_price = self.market_data_provider.get_price_by_type(
|
|
connector_name=self.config.connector_pair_dominant.connector_name,
|
|
trading_pair=self.config.connector_pair_dominant.trading_pair, price_type=PriceType.MidPrice)
|
|
|
|
current_hedge_price = self.market_data_provider.get_price_by_type(
|
|
connector_name=self.config.connector_pair_hedge.connector_name,
|
|
trading_pair=self.config.connector_pair_hedge.trading_pair, price_type=PriceType.MidPrice)
|
|
return current_dominant_price, current_hedge_price
|
|
|
|
def get_executors_dominant(self):
|
|
active_executors_dominant_placed = self.filter_executors(
|
|
self.executors_info,
|
|
filter_func=lambda e: e.connector_name == self.config.connector_pair_dominant.connector_name and e.trading_pair == self.config.connector_pair_dominant.trading_pair and e.is_active and not e.is_trading and e.type == "position_executor"
|
|
)
|
|
active_executors_dominant_filled = self.filter_executors(
|
|
self.executors_info,
|
|
filter_func=lambda e: e.connector_name == self.config.connector_pair_dominant.connector_name and e.trading_pair == self.config.connector_pair_dominant.trading_pair and e.is_active and e.is_trading and e.type == "position_executor"
|
|
)
|
|
return active_executors_dominant_placed, active_executors_dominant_filled
|
|
|
|
def get_executors_hedge(self):
|
|
active_executors_hedge_placed = self.filter_executors(
|
|
self.executors_info,
|
|
filter_func=lambda e: e.connector_name == self.config.connector_pair_hedge.connector_name and e.trading_pair == self.config.connector_pair_hedge.trading_pair and e.is_active and not e.is_trading and e.type == "position_executor"
|
|
)
|
|
active_executors_hedge_filled = self.filter_executors(
|
|
self.executors_info,
|
|
filter_func=lambda e: e.connector_name == self.config.connector_pair_hedge.connector_name and e.trading_pair == self.config.connector_pair_hedge.trading_pair and e.is_active and e.is_trading and e.type == "position_executor"
|
|
)
|
|
return active_executors_hedge_placed, active_executors_hedge_filled
|
|
|
|
def to_format_status(self) -> List[str]:
|
|
"""
|
|
Format the status of the controller for display.
|
|
"""
|
|
status_lines = []
|
|
status_lines.append(f"""
|
|
Dominant Pair: {self.config.connector_pair_dominant} | Hedge Pair: {self.config.connector_pair_hedge} |
|
|
Timeframe: {self.config.interval} | Lookback Period: {self.config.lookback_period} | Entry Threshold: {self.config.entry_threshold}
|
|
|
|
Positions targets:
|
|
Theoretical Dominant : {self.theoretical_dominant_quote} | Theoretical Hedge: {self.theoretical_hedge_quote} | Position Hedge Ratio: {self.config.pos_hedge_ratio}
|
|
Position Dominant : {self.processed_data['position_dominant_quote']:.2f} | Position Hedge: {self.processed_data['position_hedge_quote']:.2f} | Imbalance: {self.processed_data['imbalance']:.2f} | Imbalance Scaled: {self.processed_data['imbalance_scaled_pct']:.2f} %
|
|
|
|
Current Executors:
|
|
Active Orders Dominant : {len(self.processed_data['executors_dominant_placed'])} | Active Orders Hedge : {len(self.processed_data['executors_hedge_placed'])} |
|
|
Active Orders Dominant Filled: {len(self.processed_data['executors_dominant_filled'])} | Active Orders Hedge Filled: {len(self.processed_data['executors_hedge_filled'])}
|
|
|
|
Signal: {self.processed_data['signal']:.2f} | Z-Score: {self.processed_data['z_score']:.2f} | Spread: {self.processed_data['spread']:.2f}
|
|
Alpha : {self.processed_data['alpha']:.2f} | Beta: {self.processed_data['beta']:.2f}
|
|
Pair PnL PCT: {self.processed_data['pair_pnl_pct'] * 100:.2f} %
|
|
""")
|
|
return status_lines
|