forked from warproxxx/poly-maker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemergency_exit.py
More file actions
100 lines (80 loc) · 3.27 KB
/
Copy pathemergency_exit.py
File metadata and controls
100 lines (80 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""Emergency maker-exit logic for adverse directional exposure.
The normal market-maker loop tries to provide liquidity while managing inventory.
This module owns the sharper "get me out as a maker" path: when an existing YES
or NO position moves against us hard enough, cancel/avoid new buys and place an
aggressive post-only sell at the front of the book.
"""
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal
from polymarket_adapter import Position, Quote, TokenConfig
from quote_engine import TargetOrder
@dataclass(frozen=True)
class EmergencyExitSignal:
triggered: bool
reason: str = ""
position_return: Decimal = Decimal("0")
midpoint_move_bps: Decimal = Decimal("0")
adverse_imbalance: Decimal = Decimal("0")
class EmergencyExitEngine:
def __init__(self, config) -> None:
self.config = config
def evaluate(
self,
token: TokenConfig,
quote: Quote,
position: Position,
previous_mid: Decimal | None,
imbalance: Decimal,
) -> EmergencyExitSignal:
if not self.config.emergency_exit_enabled or position.size <= 0 or quote.mid <= 0:
return EmergencyExitSignal(False)
position_return = Decimal("0")
if position.avg_price > 0:
position_return = (quote.mid - position.avg_price) / position.avg_price
midpoint_move_bps = Decimal("0")
if previous_mid and previous_mid > 0:
midpoint_move_bps = (quote.mid - previous_mid) / previous_mid * Decimal("10000")
adverse_imbalance = -imbalance
reasons: list[str] = []
if position_return <= -self.config.emergency_exit_loss_fraction:
reasons.append(f"loss={position_return * Decimal('100'):.2f}%")
if midpoint_move_bps <= -self.config.emergency_exit_midpoint_move_bps:
reasons.append(f"mid_move={midpoint_move_bps:.2f}bps")
if adverse_imbalance >= self.config.emergency_exit_imbalance_threshold:
reasons.append(f"adverse_imbalance={adverse_imbalance:.2f}")
if not reasons:
return EmergencyExitSignal(
False,
position_return=position_return,
midpoint_move_bps=midpoint_move_bps,
adverse_imbalance=adverse_imbalance,
)
return EmergencyExitSignal(
True,
reason=";".join(reasons),
position_return=position_return,
midpoint_move_bps=midpoint_move_bps,
adverse_imbalance=adverse_imbalance,
)
def build_maker_exit_order(
self,
token: TokenConfig,
quote: Quote,
position: Position,
signal: EmergencyExitSignal,
) -> TargetOrder | None:
if not signal.triggered or position.size <= 0 or quote.bid <= 0:
return None
raw_price = quote.bid + token.tick_size
price = max(self.config.min_price, min(self.config.max_price, raw_price))
shares = min(position.size, self.config.emergency_exit_max_shares)
if shares < token.min_order_size:
return None
return TargetOrder(
token=token,
side="SELL",
price=price,
shares=shares,
reason=f"emergency_exit:{signal.reason}",
)