Stage 07.4.3.9 — Position flip flow
This commit is contained in:
@@ -170,7 +170,11 @@ class AutoTradeRunner:
|
|||||||
await cls._send_strong_signal_alert(state=state, payload=payload)
|
await cls._send_strong_signal_alert(state=state, payload=payload)
|
||||||
return
|
return
|
||||||
|
|
||||||
if event_type in {"paper_position_opened", "paper_position_closed"}:
|
if event_type in {
|
||||||
|
"paper_position_opened",
|
||||||
|
"paper_position_closed",
|
||||||
|
"paper_position_flipped",
|
||||||
|
}:
|
||||||
await cls._send_execution_alert(
|
await cls._send_execution_alert(
|
||||||
state=state,
|
state=state,
|
||||||
event_type=event_type,
|
event_type=event_type,
|
||||||
@@ -330,12 +334,16 @@ class AutoTradeRunner:
|
|||||||
f"{event_type}:"
|
f"{event_type}:"
|
||||||
f"{payload.get('symbol')}:"
|
f"{payload.get('symbol')}:"
|
||||||
f"{payload.get('side')}:"
|
f"{payload.get('side')}:"
|
||||||
|
f"{payload.get('old_side')}:"
|
||||||
|
f"{payload.get('new_side')}:"
|
||||||
f"{payload.get('entry_price')}:"
|
f"{payload.get('entry_price')}:"
|
||||||
f"{payload.get('exit_price')}:"
|
f"{payload.get('exit_price')}:"
|
||||||
|
f"{payload.get('new_entry_price')}:"
|
||||||
f"{payload.get('size')}:"
|
f"{payload.get('size')}:"
|
||||||
|
f"{payload.get('old_size')}:"
|
||||||
|
f"{payload.get('new_size')}:"
|
||||||
f"{payload.get('pnl')}"
|
f"{payload.get('pnl')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_execution_alert_text(
|
def _build_execution_alert_text(
|
||||||
cls,
|
cls,
|
||||||
@@ -378,6 +386,32 @@ class AutoTradeRunner:
|
|||||||
f"PnL: {pnl}"
|
f"PnL: {pnl}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if event_type == "paper_position_flipped":
|
||||||
|
old_side = str(payload.get("old_side") or "—")
|
||||||
|
new_side = str(payload.get("new_side") or side or "—")
|
||||||
|
|
||||||
|
entry_price = cls._format_price(payload.get("entry_price"))
|
||||||
|
exit_price = cls._format_price(payload.get("exit_price"))
|
||||||
|
new_entry_price = cls._format_price(payload.get("new_entry_price"))
|
||||||
|
old_size = cls._format_size(payload.get("old_size"))
|
||||||
|
new_size = cls._format_size(payload.get("new_size"))
|
||||||
|
pnl = cls._format_pnl(payload.get("pnl"))
|
||||||
|
|
||||||
|
old_icon = "🟢" if old_side == "LONG" else "🔴"
|
||||||
|
new_icon = "🟢" if new_side == "LONG" else "🔴"
|
||||||
|
|
||||||
|
return (
|
||||||
|
f"<b>🔁 Paper position flipped {old_icon} {old_side} → "
|
||||||
|
f"{new_icon} {new_side}</b>\n\n"
|
||||||
|
f"{symbol_text} · {leverage_text}\n\n"
|
||||||
|
f"Old entry: $ {entry_price}\n"
|
||||||
|
f"Exit: $ {exit_price}\n"
|
||||||
|
f"Old size: {old_size}\n\n"
|
||||||
|
f"New entry: $ {new_entry_price}\n"
|
||||||
|
f"New size: {new_size}\n\n"
|
||||||
|
f"PnL: {pnl}"
|
||||||
|
)
|
||||||
|
|
||||||
return "<b>📄 Paper execution event</b>"
|
return "<b>📄 Paper execution event</b>"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ class ExecutionEngine:
|
|||||||
if state.decision_status != "READY" or not state.is_signal_ready:
|
if state.decision_status != "READY" or not state.is_signal_ready:
|
||||||
return ExecutionDecision("NONE", False, "Сигнал ещё не готов к execution.")
|
return ExecutionDecision("NONE", False, "Сигнал ещё не готов к execution.")
|
||||||
|
|
||||||
if self._should_close_position(state):
|
if self._should_flip_position(state):
|
||||||
return self._close_position(state)
|
return self._flip_position(state)
|
||||||
|
|
||||||
if state.last_signal == "BUY":
|
if state.last_signal == "BUY":
|
||||||
return self._open_position_if_empty(state=state, side="LONG", action="OPEN_LONG")
|
return self._open_position_if_empty(state=state, side="LONG", action="OPEN_LONG")
|
||||||
@@ -102,6 +102,87 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
return ExecutionDecision(action, True, f"Paper ENTRY {side} открыта.")
|
return ExecutionDecision(action, True, f"Paper ENTRY {side} открыта.")
|
||||||
|
|
||||||
|
def _flip_position(self, state: AutoTradeState) -> ExecutionDecision:
|
||||||
|
position = type(self)._position
|
||||||
|
|
||||||
|
if position.side == "NONE":
|
||||||
|
self._sync_state_from_position(state)
|
||||||
|
return ExecutionDecision("NONE", False, "Нет позиции для flip.")
|
||||||
|
|
||||||
|
new_side = self._target_side_from_signal(state.last_signal)
|
||||||
|
if new_side is None:
|
||||||
|
return ExecutionDecision("NONE", False, "Нет направления для flip.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ticker = ExchangeService().get_price(state.symbol)
|
||||||
|
flip_price = ticker.price
|
||||||
|
except Exception as exc:
|
||||||
|
return ExecutionDecision("NONE", False, f"Ошибка получения цены для flip: {exc}")
|
||||||
|
|
||||||
|
now = self._now_time()
|
||||||
|
pnl = self._calculate_pnl(flip_price)
|
||||||
|
new_size = self._calculate_position_size(state)
|
||||||
|
|
||||||
|
old_side = position.side
|
||||||
|
old_entry_price = position.entry_price
|
||||||
|
old_size = position.size
|
||||||
|
old_leverage = position.leverage
|
||||||
|
old_opened_at = position.opened_at
|
||||||
|
|
||||||
|
type(self)._position = PositionState(
|
||||||
|
side=new_side,
|
||||||
|
symbol=state.symbol,
|
||||||
|
entry_price=flip_price,
|
||||||
|
size=new_size,
|
||||||
|
leverage=state.leverage,
|
||||||
|
unrealized_pnl_usd=0.0,
|
||||||
|
opened_at=now,
|
||||||
|
updated_at=now,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._sync_state_from_position(state)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"execution_type": "FLIP",
|
||||||
|
"action": f"FLIP_{old_side}_TO_{new_side}",
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"old_side": old_side,
|
||||||
|
"new_side": new_side,
|
||||||
|
"side": new_side,
|
||||||
|
"entry_price": old_entry_price,
|
||||||
|
"exit_price": flip_price,
|
||||||
|
"new_entry_price": flip_price,
|
||||||
|
"old_size": old_size,
|
||||||
|
"new_size": new_size,
|
||||||
|
"size": new_size,
|
||||||
|
"old_leverage": old_leverage,
|
||||||
|
"leverage": state.leverage,
|
||||||
|
"pnl": pnl,
|
||||||
|
"signal": state.last_signal,
|
||||||
|
"confidence": state.last_signal_confidence,
|
||||||
|
"repeat_count": state.last_signal_repeat_count,
|
||||||
|
"reason": state.last_signal_reason,
|
||||||
|
"opened_at": old_opened_at,
|
||||||
|
"closed_at": now,
|
||||||
|
"new_opened_at": now,
|
||||||
|
}
|
||||||
|
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type="paper_position_flipped",
|
||||||
|
message=f"Paper FLIP выполнен: {old_side} → {new_side} {state.symbol}",
|
||||||
|
screen="auto",
|
||||||
|
action="paper_execution",
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
EventBus.emit("paper_position_flipped", payload)
|
||||||
|
|
||||||
|
return ExecutionDecision(
|
||||||
|
f"FLIP_{old_side}_TO_{new_side}",
|
||||||
|
True,
|
||||||
|
f"Paper FLIP выполнен: {old_side} → {new_side}.",
|
||||||
|
)
|
||||||
|
|
||||||
def _close_position(self, state: AutoTradeState) -> ExecutionDecision:
|
def _close_position(self, state: AutoTradeState) -> ExecutionDecision:
|
||||||
position = type(self)._position
|
position = type(self)._position
|
||||||
|
|
||||||
@@ -151,7 +232,7 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
return ExecutionDecision("CLOSE", True, "Paper EXIT выполнена.")
|
return ExecutionDecision("CLOSE", True, "Paper EXIT выполнена.")
|
||||||
|
|
||||||
def _should_close_position(self, state: AutoTradeState) -> bool:
|
def _should_flip_position(self, state: AutoTradeState) -> bool:
|
||||||
position = type(self)._position
|
position = type(self)._position
|
||||||
|
|
||||||
if position.side == "NONE":
|
if position.side == "NONE":
|
||||||
@@ -165,6 +246,15 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _target_side_from_signal(self, signal: str | None) -> str | None:
|
||||||
|
if signal == "BUY":
|
||||||
|
return "LONG"
|
||||||
|
|
||||||
|
if signal == "SELL":
|
||||||
|
return "SHORT"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _update_unrealized_pnl(self, state: AutoTradeState) -> None:
|
def _update_unrealized_pnl(self, state: AutoTradeState) -> None:
|
||||||
position = type(self)._position
|
position = type(self)._position
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,12 @@
|
|||||||
- readable USD formatting
|
- readable USD formatting
|
||||||
- signal alerts separated from execution alerts
|
- signal alerts separated from execution alerts
|
||||||
|
|
||||||
|
#### 07.4.3.9 — Position flip flow ✅
|
||||||
|
- instant LONG ↔ SHORT reversal (FLIP)
|
||||||
|
- new EventBus event: paper_position_flipped
|
||||||
|
- unified execution alert for flip
|
||||||
|
- improved execution realism (no idle gap)
|
||||||
|
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
⏳ Grid Strategy
|
⏳ Grid Strategy
|
||||||
|
|||||||
@@ -169,6 +169,13 @@
|
|||||||
- readable USD formatting
|
- readable USD formatting
|
||||||
- signal alerts separated from execution alerts
|
- signal alerts separated from execution alerts
|
||||||
|
|
||||||
|
#### 07.4.3.9 — Position flip flow ✅
|
||||||
|
|
||||||
|
- instant LONG ↔ SHORT reversal (FLIP)
|
||||||
|
- new EventBus event: paper_position_flipped
|
||||||
|
- unified execution alert for flip
|
||||||
|
- improved execution realism (no idle gap)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
|
|||||||
90
docs/stages/07_4_3_9_position_flip_flow.md
Normal file
90
docs/stages/07_4_3_9_position_flip_flow.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Stage 07.4.3.9 --- Position Flip Flow
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This stage introduces **position flip logic** into the execution engine.
|
||||||
|
|
||||||
|
Instead of: - CLOSE → WAIT → OPEN
|
||||||
|
|
||||||
|
We now support: - **FLIP (instant reversal)**
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
### Before
|
||||||
|
|
||||||
|
- LONG + SELL → CLOSE
|
||||||
|
- Next cycle → OPEN SHORT
|
||||||
|
|
||||||
|
### Now
|
||||||
|
|
||||||
|
- LONG + SELL → **FLIP → SHORT (same cycle)**
|
||||||
|
- SHORT + BUY → **FLIP → LONG (same cycle)**
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Execution Types
|
||||||
|
|
||||||
|
Type Description
|
||||||
|
------- -------------------------------------------------
|
||||||
|
ENTRY Opening new position
|
||||||
|
EXIT Closing position
|
||||||
|
FLIP Closing + opening opposite position in one step
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## EventBus Events
|
||||||
|
|
||||||
|
### New event
|
||||||
|
|
||||||
|
- `paper_position_flipped`
|
||||||
|
|
||||||
|
Payload includes: - old_side - new_side - entry_price - exit_price -
|
||||||
|
new_entry_price - pnl - sizes and leverage
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Telegram Alerts
|
||||||
|
|
||||||
|
### Flip alert
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
🔁 Paper position flipped 🟢 LONG → 🔴 SHORT
|
||||||
|
|
||||||
|
Includes: - old entry - exit - new entry - pnl
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- Faster reaction to signals
|
||||||
|
- No idle state between positions
|
||||||
|
- Cleaner execution logic
|
||||||
|
- More realistic trading simulation
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
/debug_signal BUY 0.95 3
|
||||||
|
/debug_signal SELL 0.95 3
|
||||||
|
/debug_signal BUY 0.95 3
|
||||||
|
|
||||||
|
Expected: - ENTRY - FLIP - FLIP
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Flip happens only when:
|
||||||
|
- position exists
|
||||||
|
- opposite signal is READY
|
||||||
|
- Execution remains **paper-only**
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## Next Stage
|
||||||
|
|
||||||
|
07.4.3.10 --- Risk & Position Control
|
||||||
Reference in New Issue
Block a user