-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathposition_tracker.py
More file actions
204 lines (169 loc) · 7.54 KB
/
position_tracker.py
File metadata and controls
204 lines (169 loc) · 7.54 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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
"""
Position Tracker Module
Tracks open positions and manages trailing stops for profit maximization.
"""
from typing import Dict, List, Optional
from datetime import datetime
import pandas as pd
from logger import trading_logger, system_logger
import config
class PositionTracker:
"""
Tracks positions and manages trailing stops to maximize profits.
"""
def __init__(self):
"""Initialize position tracker."""
self.tracked_positions = {} # symbol -> position_data
def add_position(self, symbol: str, entry_price: float, quantity: int,
atr: float, initial_stop: float):
"""
Add a new position to track.
Args:
symbol: Stock symbol
entry_price: Entry price
quantity: Position size
atr: Average True Range at entry
initial_stop: Initial stop loss price
"""
# Calculate partial profit target (2x ATR)
partial_target = entry_price + (atr * 2.0)
self.tracked_positions[symbol] = {
'entry_price': entry_price,
'quantity': quantity,
'original_quantity': quantity, # Track original size
'atr': atr,
'initial_stop': initial_stop,
'trailing_stop': initial_stop,
'highest_price': entry_price,
'trailing_active': False,
'partial_target': partial_target,
'partial_taken': False, # Track if partial profit taken
'entry_time': datetime.now(),
'last_update': datetime.now()
}
trading_logger.info(
f"Tracking {symbol}: Entry=${entry_price:.2f}, "
f"Qty={quantity}, ATR=${atr:.2f}, Stop=${initial_stop:.2f}, "
f"Partial Target=${partial_target:.2f}"
)
def update_position(self, symbol: str, current_price: float) -> Optional[Dict]:
"""
Update position and trailing stop based on current price.
Args:
symbol: Stock symbol
current_price: Current market price
Returns:
Dictionary with stop update info, or None if no changes
"""
if symbol not in self.tracked_positions:
return None
pos = self.tracked_positions[symbol]
pos['last_update'] = datetime.now()
# Update highest price seen
if current_price > pos['highest_price']:
pos['highest_price'] = current_price
# Calculate unrealized P&L
unrealized_pnl = (current_price - pos['entry_price']) * pos['quantity']
pnl_in_atr = (current_price - pos['entry_price']) / pos['atr']
# Check for partial profit target (2x ATR) - take 50% off
if not pos['partial_taken'] and current_price >= pos['partial_target']:
pos['partial_taken'] = True
partial_qty = pos['original_quantity'] // 2 # Sell 50%
trading_logger.info(
f"🎯 {symbol}: Partial target HIT at ${current_price:.2f} "
f"(Target: ${pos['partial_target']:.2f}, +{pnl_in_atr:.1f} ATR)"
)
return {
'symbol': symbol,
'partial_target_hit': True,
'partial_qty': partial_qty,
'exit_price': current_price,
'partial_pnl': (current_price - pos['entry_price']) * partial_qty
}
# Activate trailing stop if profitable by 1x ATR
activation_threshold = pos['entry_price'] + pos['atr']
if not pos['trailing_active'] and current_price >= activation_threshold:
pos['trailing_active'] = True
trading_logger.info(
f"🎯 {symbol}: Trailing stop ACTIVATED at ${current_price:.2f} "
f"(+{pnl_in_atr:.1f} ATR profit)"
)
# Update trailing stop if active
if pos['trailing_active']:
# Trail at 2x ATR behind highest price
new_trailing_stop = pos['highest_price'] - (pos['atr'] * config.ATR_STOP_MULTIPLIER)
# Only move stop UP, never down
if new_trailing_stop > pos['trailing_stop']:
old_stop = pos['trailing_stop']
pos['trailing_stop'] = new_trailing_stop
# Calculate locked-in profit
locked_profit = (pos['trailing_stop'] - pos['entry_price']) * pos['quantity']
trading_logger.info(
f"📈 {symbol}: Trailing stop raised ${old_stop:.2f} → ${new_trailing_stop:.2f} "
f"(Locked profit: ${locked_profit:.2f})"
)
return {
'symbol': symbol,
'old_stop': old_stop,
'new_stop': new_trailing_stop,
'highest_price': pos['highest_price'],
'locked_profit': locked_profit,
'unrealized_pnl': unrealized_pnl
}
# Check if stop hit
if current_price <= pos['trailing_stop']:
trading_logger.warning(
f"🛑 {symbol}: Trailing stop HIT at ${current_price:.2f} "
f"(Stop: ${pos['trailing_stop']:.2f})"
)
return {
'symbol': symbol,
'stop_hit': True,
'exit_price': current_price,
'stop_price': pos['trailing_stop'],
'pnl': unrealized_pnl
}
return None
def get_position_info(self, symbol: str) -> Optional[Dict]:
"""Get detailed position information."""
if symbol not in self.tracked_positions:
return None
pos = self.tracked_positions[symbol]
return {
'symbol': symbol,
'entry_price': pos['entry_price'],
'quantity': pos['quantity'],
'current_stop': pos['trailing_stop'],
'highest_price': pos['highest_price'],
'trailing_active': pos['trailing_active'],
'profit_locked': (pos['trailing_stop'] - pos['entry_price']) * pos['quantity'] if pos['trailing_active'] else 0,
'entry_time': pos['entry_time']
}
def update_quantity(self, symbol: str, new_quantity: int):
"""
Update position quantity after partial exit.
Args:
symbol: Stock symbol
new_quantity: New position size
"""
if symbol in self.tracked_positions:
old_qty = self.tracked_positions[symbol]['quantity']
self.tracked_positions[symbol]['quantity'] = new_quantity
trading_logger.info(
f"Updated {symbol} quantity: {old_qty} → {new_quantity} shares"
)
def remove_position(self, symbol: str):
"""Remove a position from tracking."""
if symbol in self.tracked_positions:
del self.tracked_positions[symbol]
trading_logger.info(f"Stopped tracking {symbol}")
def get_all_positions(self) -> List[str]:
"""Get list of all tracked symbols."""
return list(self.tracked_positions.keys())
def get_summary(self) -> Dict:
"""Get summary of all tracked positions."""
return {
'total_positions': len(self.tracked_positions),
'trailing_active_count': sum(1 for p in self.tracked_positions.values() if p['trailing_active']),
'symbols': list(self.tracked_positions.keys())
}