Skip to content

Commit 9b30a77

Browse files
yingwangclaude
andcommitted
Fix split adjustment in both strategies: equity=cash+mv, date-aware history
- Apply same BKNG 1:25 split fix to generate_site.py (Multi-Factor) - Compute equity as cash + sum(corrected market values) instead of blindly adding adjustment to Alpaca's equity - Leave cash and buying_power from Alpaca unchanged - Only adjust portfolio_history for dates after the split stock buy date Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 83b3339 commit 9b30a77

3 files changed

Lines changed: 137 additions & 88 deletions

File tree

generate_site.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,20 @@ def generate_trade_history():
133133
return _parse_local_trade_logs()
134134

135135

136+
# Stock splits not yet reflected in Alpaca paper trading.
137+
STOCK_SPLITS = {
138+
"BKNG": 25, # 1:25 split, June 2024
139+
}
140+
141+
142+
def _adjust_for_splits(symbol, qty, avg_entry_price, cost_basis):
143+
"""Adjust position data for stock splits Alpaca hasn't accounted for."""
144+
ratio = STOCK_SPLITS.get(symbol)
145+
if ratio and avg_entry_price > 0:
146+
return qty * ratio, avg_entry_price / ratio, cost_basis
147+
return qty, avg_entry_price, cost_basis
148+
149+
136150
def _fetch_trades_from_alpaca(api_key, secret_key):
137151
"""Pull trade history and current positions from Alpaca API."""
138152
try:
@@ -173,18 +187,29 @@ def _fetch_trades_from_alpaca(api_key, secret_key):
173187
# Current positions
174188
positions = []
175189
for p in api.list_positions():
190+
qty = float(p.qty)
191+
avg_entry = float(p.avg_entry_price)
192+
cost = float(p.cost_basis)
193+
current = float(p.current_price)
194+
195+
qty, avg_entry, cost = _adjust_for_splits(p.symbol, qty, avg_entry, cost)
196+
197+
market_value = qty * current
198+
total_pl = market_value - cost
199+
total_pl_pct = (total_pl / cost * 100) if cost else 0
200+
176201
positions.append({
177202
"symbol": p.symbol,
178-
"qty": float(p.qty),
203+
"qty": qty,
179204
"side": p.side,
180-
"current_price": float(p.current_price),
181-
"market_value": float(p.market_value),
182-
"avg_entry_price": float(p.avg_entry_price),
183-
"cost_basis": float(p.cost_basis),
205+
"current_price": current,
206+
"market_value": round(market_value, 2),
207+
"avg_entry_price": round(avg_entry, 2),
208+
"cost_basis": round(cost, 2),
184209
"today_pl_pct": float(p.unrealized_intraday_plpc) * 100 if hasattr(p, 'unrealized_intraday_plpc') and p.unrealized_intraday_plpc else 0,
185210
"today_pl": float(p.unrealized_intraday_pl) if hasattr(p, 'unrealized_intraday_pl') and p.unrealized_intraday_pl else 0,
186-
"total_pl_pct": float(p.unrealized_plpc) * 100,
187-
"total_pl": float(p.unrealized_pl),
211+
"total_pl_pct": round(total_pl_pct, 3),
212+
"total_pl": round(total_pl, 2),
188213
})
189214

190215
# Recent orders (last 100 filled orders)
@@ -216,6 +241,29 @@ def _fetch_trades_from_alpaca(api_key, secret_key):
216241
logger.info("Fetched %d orders across %d rebalance dates from Alpaca",
217242
len(filled_orders), len(rebalances))
218243

244+
# Adjust account equity and portfolio history for split corrections
245+
equity_adjustment = 0
246+
for p in api.list_positions():
247+
if p.symbol in STOCK_SPLITS:
248+
ratio = STOCK_SPLITS[p.symbol]
249+
equity_adjustment += float(p.qty) * (ratio - 1) * float(p.current_price)
250+
251+
if equity_adjustment:
252+
total_mv = sum(p["market_value"] for p in positions)
253+
account_info["equity"] = round(account_info["cash"] + total_mv, 2)
254+
logger.info("Adjusted account equity by +$%.2f for stock splits", equity_adjustment)
255+
split_buy_date = None
256+
for reb in rebalances:
257+
for t in reb.get("trades", []):
258+
if t["symbol"] in STOCK_SPLITS and t["side"] == "buy":
259+
d = reb["date"]
260+
if split_buy_date is None or d < split_buy_date:
261+
split_buy_date = d
262+
if split_buy_date:
263+
for h in portfolio_history:
264+
if h["equity"] is not None and h["date"] > split_buy_date:
265+
h["equity"] = round(h["equity"] + equity_adjustment, 2)
266+
219267
# Fetch SPY benchmark aligned to portfolio history dates
220268
spy_history = []
221269
valid_hist = [h for h in portfolio_history if h["equity"]]

generate_site_lgbm.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,9 @@ def _fetch_trades_from_alpaca(api_key, secret_key):
269269
equity_adjustment += raw_qty * (ratio - 1) * current
270270

271271
if equity_adjustment:
272-
account_info["equity"] = round(account_info["equity"] + equity_adjustment, 2)
273-
account_info["buying_power"] = round(account_info["buying_power"] + equity_adjustment, 2)
272+
# Recompute equity = cash + sum of corrected market values
273+
total_mv = sum(p["market_value"] for p in positions)
274+
account_info["equity"] = round(account_info["cash"] + total_mv, 2)
274275
logger.info("Adjusted account equity by +$%.2f for stock splits", equity_adjustment)
275276
# Find earliest buy date of any split-affected stock
276277
split_buy_date = None

site/lgbm/data/trades.json

Lines changed: 79 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
{
22
"account": {
3-
"equity": 104448.17,
3+
"equity": 104910.85,
44
"cash": -49207.86,
5-
"buying_power": 44789.19
5+
"buying_power": 32444.67
66
},
77
"positions": [
88
{
99
"symbol": "ADBE",
1010
"qty": 31.0,
1111
"side": "long",
12-
"current_price": 242.74,
13-
"market_value": 7524.94,
12+
"current_price": 244.0,
13+
"market_value": 7564.0,
1414
"avg_entry_price": 242.41,
1515
"cost_basis": 7514.71,
16-
"today_pl_pct": -0.661,
17-
"today_pl": -50.065,
18-
"total_pl_pct": 0.136,
19-
"total_pl": 10.23
16+
"today_pl_pct": -0.145,
17+
"today_pl": -11.005,
18+
"total_pl_pct": 0.656,
19+
"total_pl": 49.29
2020
},
2121
{
2222
"symbol": "AMT",
@@ -35,79 +35,79 @@
3535
"symbol": "AVGO",
3636
"qty": 21.0,
3737
"side": "long",
38-
"current_price": 343.8,
39-
"market_value": 7219.8,
38+
"current_price": 344.9626,
39+
"market_value": 7244.21,
4040
"avg_entry_price": 307.09,
4141
"cost_basis": 6448.83,
42-
"today_pl_pct": 9.341000000000001,
43-
"today_pl": 616.77,
44-
"total_pl_pct": 11.955,
45-
"total_pl": 770.97
42+
"today_pl_pct": 9.71,
43+
"today_pl": 641.1846,
44+
"total_pl_pct": 12.334,
45+
"total_pl": 795.38
4646
},
4747
{
4848
"symbol": "BKNG",
4949
"qty": 75.0,
5050
"side": "long",
51-
"current_price": 178.5,
52-
"market_value": 13387.5,
51+
"current_price": 178.06,
52+
"market_value": 13354.5,
5353
"avg_entry_price": 169.34,
5454
"cost_basis": 12700.41,
55-
"today_pl_pct": 1.311,
56-
"today_pl": 6.93,
57-
"total_pl_pct": 5.41,
58-
"total_pl": 687.09
55+
"today_pl_pct": 1.061,
56+
"today_pl": 5.61,
57+
"total_pl_pct": 5.15,
58+
"total_pl": 654.09
5959
},
6060
{
6161
"symbol": "C",
6262
"qty": 94.0,
6363
"side": "long",
64-
"current_price": 117.13,
65-
"market_value": 11010.22,
64+
"current_price": 119.5,
65+
"market_value": 11233.0,
6666
"avg_entry_price": 115.64,
6767
"cost_basis": 10870.16,
68-
"today_pl_pct": -0.196,
69-
"today_pl": -21.62,
70-
"total_pl_pct": 1.288,
71-
"total_pl": 140.06
68+
"today_pl_pct": 1.823,
69+
"today_pl": 201.16,
70+
"total_pl_pct": 3.338,
71+
"total_pl": 362.84
7272
},
7373
{
7474
"symbol": "CRM",
7575
"qty": 37.0,
7676
"side": "long",
77-
"current_price": 182.96,
78-
"market_value": 6769.52,
77+
"current_price": 186.35,
78+
"market_value": 6894.95,
7979
"avg_entry_price": 183.71,
8080
"cost_basis": 6797.37,
81-
"today_pl_pct": -1.119,
82-
"today_pl": -76.59,
83-
"total_pl_pct": -0.41,
84-
"total_pl": -27.85
81+
"today_pl_pct": 0.713,
82+
"today_pl": 48.84,
83+
"total_pl_pct": 1.436,
84+
"total_pl": 97.58
8585
},
8686
{
8787
"symbol": "CRWD",
8888
"qty": 14.0,
8989
"side": "long",
90-
"current_price": 431.7584,
91-
"market_value": 6044.62,
90+
"current_price": 434.0,
91+
"market_value": 6076.0,
9292
"avg_entry_price": 394.11,
9393
"cost_basis": 5517.54,
94-
"today_pl_pct": 8.315999999999999,
95-
"today_pl": 464.0776,
96-
"total_pl_pct": 9.553,
97-
"total_pl": 527.08
94+
"today_pl_pct": 8.878,
95+
"today_pl": 495.46,
96+
"total_pl_pct": 10.122,
97+
"total_pl": 558.46
9898
},
9999
{
100100
"symbol": "DDOG",
101101
"qty": 115.0,
102102
"side": "long",
103-
"current_price": 119.9181,
104-
"market_value": 13790.58,
103+
"current_price": 119.71,
104+
"market_value": 13766.65,
105105
"avg_entry_price": 116.53,
106106
"cost_basis": 13400.41,
107-
"today_pl_pct": 2.934,
108-
"today_pl": 393.0815,
109-
"total_pl_pct": 2.912,
110-
"total_pl": 390.17
107+
"today_pl_pct": 2.7550000000000003,
108+
"today_pl": 369.15,
109+
"total_pl_pct": 2.733,
110+
"total_pl": 366.24
111111
},
112112
{
113113
"symbol": "DHR",
@@ -126,53 +126,53 @@
126126
"symbol": "GE",
127127
"qty": 31.0,
128128
"side": "long",
129-
"current_price": 298.73,
130-
"market_value": 9260.63,
129+
"current_price": 298.57,
130+
"market_value": 9255.67,
131131
"avg_entry_price": 286.45,
132132
"cost_basis": 8879.95,
133-
"today_pl_pct": 3.4779999999999998,
134-
"today_pl": 311.24,
135-
"total_pl_pct": 4.287,
136-
"total_pl": 380.68
133+
"today_pl_pct": 3.422,
134+
"today_pl": 306.28,
135+
"total_pl_pct": 4.231,
136+
"total_pl": 375.72
137137
},
138138
{
139139
"symbol": "NET",
140140
"qty": 66.0,
141141
"side": "long",
142-
"current_price": 222.4167,
143-
"market_value": 14679.5,
142+
"current_price": 222.25,
143+
"market_value": 14668.5,
144144
"avg_entry_price": 203.78,
145145
"cost_basis": 13449.48,
146-
"today_pl_pct": 5.023,
147-
"today_pl": 702.0222,
148-
"total_pl_pct": 9.146,
149-
"total_pl": 1230.02
146+
"today_pl_pct": 4.944,
147+
"today_pl": 691.02,
148+
"total_pl_pct": 9.064,
149+
"total_pl": 1219.02
150150
},
151151
{
152152
"symbol": "SBUX",
153153
"qty": 105.0,
154154
"side": "long",
155-
"current_price": 96.25,
156-
"market_value": 10106.25,
155+
"current_price": 96.53,
156+
"market_value": 10135.65,
157157
"avg_entry_price": 88.59,
158158
"cost_basis": 9302.4,
159-
"today_pl_pct": 1.551,
160-
"today_pl": 154.35,
161-
"total_pl_pct": 8.641,
162-
"total_pl": 803.85
159+
"today_pl_pct": 1.846,
160+
"today_pl": 183.75,
161+
"total_pl_pct": 8.957,
162+
"total_pl": 833.25
163163
},
164164
{
165165
"symbol": "SNOW",
166166
"qty": 68.0,
167167
"side": "long",
168-
"current_price": 153.6929,
169-
"market_value": 10451.12,
168+
"current_price": 153.5,
169+
"market_value": 10438.0,
170170
"avg_entry_price": 154.65,
171171
"cost_basis": 10516.2,
172-
"today_pl_pct": 2.887,
173-
"today_pl": 293.2772,
174-
"total_pl_pct": -0.619,
175-
"total_pl": -65.08
172+
"today_pl_pct": 2.758,
173+
"today_pl": 280.16,
174+
"total_pl_pct": -0.744,
175+
"total_pl": -78.2
176176
},
177177
{
178178
"symbol": "TJX",
@@ -191,14 +191,14 @@
191191
"symbol": "ZS",
192192
"qty": 77.0,
193193
"side": "long",
194-
"current_price": 144.5,
195-
"market_value": 11126.5,
194+
"current_price": 145.49,
195+
"market_value": 11202.73,
196196
"avg_entry_price": 137.21,
197197
"cost_basis": 10565.17,
198-
"today_pl_pct": 3.569,
199-
"today_pl": 383.46,
200-
"total_pl_pct": 5.313,
201-
"total_pl": 561.33
198+
"today_pl_pct": 4.279,
199+
"today_pl": 459.69,
200+
"total_pl_pct": 6.035,
201+
"total_pl": 637.56
202202
}
203203
],
204204
"portfolio_history": [
@@ -289,17 +289,17 @@
289289
},
290290
{
291291
"date": "2026-04-02",
292-
"equity": 112252.0,
292+
"equity": 112220.32,
293293
"profit_loss": -600.0
294294
},
295295
{
296296
"date": "2026-04-03",
297-
"equity": 113191.04,
297+
"equity": 113159.36,
298298
"profit_loss": 939.04
299299
},
300300
{
301301
"date": "2026-04-07",
302-
"equity": 101680.85,
302+
"equity": 101649.17,
303303
"profit_loss": -11510.19
304304
}
305305
],
@@ -324,7 +324,7 @@
324324
"rebalances": [
325325
{
326326
"date": "2026-04-02",
327-
"portfolio_value": 91596.17,
327+
"portfolio_value": 92090.53,
328328
"trades": [
329329
{
330330
"symbol": "AMT",
@@ -442,7 +442,7 @@
442442
},
443443
{
444444
"date": "2026-04-01",
445-
"portfolio_value": 91596.17,
445+
"portfolio_value": 92090.53,
446446
"trades": [
447447
{
448448
"symbol": "ZS",

0 commit comments

Comments
 (0)