-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.py
More file actions
54 lines (44 loc) · 1.63 KB
/
Copy pathexecutor.py
File metadata and controls
54 lines (44 loc) · 1.63 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
"""Stage 3: risk-based position sizing.
Size is derived from risk, not from capital: the distance to the stop decides
how many shares a fixed dollar risk buys. A wide stop therefore produces a
small position and a tight stop a large one, so every open trade carries the
same expected loss if it fails.
"""
from typing import Optional
from config import (
ACCOUNT_EQUITY,
MAX_STOP_ATR_MULTIPLE,
RISK_PER_TRADE,
)
def execute_signal(signal: Optional[dict]) -> Optional[dict]:
"""Turn a signal into a sized position, or None if it fails risk checks."""
if signal is None:
return None
entry = signal["entry_price"]
stop = signal["stop_price"]
atr = signal["atr"]
stop_distance = entry - stop
if stop_distance <= 0:
return None
# A stop wider than one ATR means the setup is not tight enough to size
# sensibly -- the position would be too small to matter, or the risk too
# large to justify.
max_stop = MAX_STOP_ATR_MULTIPLE * atr
if stop_distance > max_stop:
print(
f"Trade skipped ({signal['ticker']}): stop distance "
f"{stop_distance:.2f} exceeds {MAX_STOP_ATR_MULTIPLE:g}x ATR {atr:.2f}"
)
return None
risk_budget = ACCOUNT_EQUITY * RISK_PER_TRADE
shares = int(risk_budget / stop_distance) # round down; never over-risk
if shares <= 0:
return None
return {
"ticker": signal["ticker"],
"shares": shares,
"entry_price": round(entry, 2),
"stop_price": round(stop, 2),
"total_cost": round(shares * entry, 2),
"risk_amount": round(shares * stop_distance, 2),
}