-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwhatif_analysis.py
More file actions
264 lines (232 loc) · 11.1 KB
/
Copy pathwhatif_analysis.py
File metadata and controls
264 lines (232 loc) · 11.1 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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
"""
What-if 分析:投資人各種假設情境的損益比較
===========================================
使用 0050 實際價格資料,計算:
1. 實際交易 vs 策略執行 的差異
2. 如果晚進場 / 早進場的影響
3. 如果嚴格按策略出場的損益
"""
import warnings
warnings.filterwarnings("ignore")
import sys, os
import numpy as np
import pandas as pd
sys.stdout.reconfigure(encoding="utf-8")
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
OUTPUT_DIR = os.path.join(SCRIPT_DIR, "output")
def download_0050():
import yfinance as yf
df = yf.download("0050.TW", start="2023-09-01", end="2026-02-18", auto_adjust=True, progress=False)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
df.index = pd.to_datetime(df.index).tz_localize(None)
return df
def main():
os.makedirs(OUTPUT_DIR, exist_ok=True)
sep = "=" * 72
print(f"\n{sep}")
print(" What-If 情境分析:0050 實際價格驗證")
print(sep)
df = download_0050()
print(f" 0050 日線資料: {len(df)} 筆 ({df.index[0].date()} ~ {df.index[-1].date()})")
# ── 2023 Cycle Analysis ──
print(f"\n{sep}")
print(" 【2023 年週期】投資人實際 vs 多種假設情境")
print(sep)
buy_date = pd.Timestamp("2023-10-24")
buy_price = 123.65
shares = 2000
sell_date = pd.Timestamp("2024-01-02")
sell_price = 134.70
# Actual
actual_pnl = (sell_price - buy_price) * shares
actual_ret = (sell_price - buy_price) / buy_price
print(f"\n 實際交易:")
print(f" 買入: {buy_date.date()} @ {buy_price} x {shares}")
print(f" 賣出: {sell_date.date()} @ {sell_price}")
print(f" 報酬率: {actual_ret:.2%}")
print(f" 獲利: TWD {actual_pnl:,.0f}")
# What-if: Hold to +30%
target_30 = buy_price * 1.30
reached_30 = df[df.index > buy_date][df["Close"] >= target_30]
if len(reached_30) > 0:
date_30 = reached_30.index[0]
price_30 = reached_30.iloc[0]["Close"]
sell_70_shares = int(shares * 0.70 / 1000) * 1000 # 1000
remain = shares - sell_70_shares
pnl_30 = (price_30 - buy_price) * sell_70_shares
print(f"\n 假設A:嚴格執行 +30% 賣 70%")
print(f" +30% 目標價: {target_30:.2f}")
print(f" 達到日期: {date_30.date()} @ {price_30:.2f}")
print(f" 賣出 {sell_70_shares} 股,獲利: TWD {pnl_30:,.0f}")
print(f" 剩餘 {remain} 股繼續持有")
# Then check +50%
target_50 = buy_price * 1.50
reached_50 = df[df.index > date_30][df["Close"] >= target_50]
if len(reached_50) > 0:
date_50 = reached_50.index[0]
price_50 = reached_50.iloc[0]["Close"]
pnl_50 = (price_50 - buy_price) * remain
total_strat = pnl_30 + pnl_50
print(f" +50% 目標價: {target_50:.2f}")
print(f" 達到日期: {date_50.date()} @ {price_50:.2f}")
print(f" 賣出剩餘 {remain} 股,獲利: TWD {pnl_50:,.0f}")
print(f" 策略總獲利: TWD {total_strat:,.0f}")
print(f" vs 實際獲利: TWD {actual_pnl:,.0f}")
print(f" 差異: TWD {total_strat - actual_pnl:+,.0f} ({(total_strat/actual_pnl - 1):.0%} more)")
else:
# Check latest price for remaining
latest = df.iloc[-1]["Close"]
unrealized = (latest - buy_price) * remain
total_strat = pnl_30 + unrealized
print(f" +50% 未達到,剩餘 {remain} 股持至今 @ {latest:.2f}")
print(f" 未實現損益: TWD {unrealized:,.0f}")
print(f" 策略總損益: TWD {total_strat:,.0f}")
else:
print(f"\n 假設A:0050 從未達到 +30% ({target_30:.2f})")
# What-if: 3 tranches
print(f"\n 假設B:嚴格 3 碼分批(100萬資金)")
capital = 1_000_000
tranche1_pct = 0.30
tranche1_budget = capital * tranche1_pct
t1_shares = int(tranche1_budget / buy_price / 1000) * 1000
t1_cost = t1_shares * buy_price
print(f" 第1碼: {buy_date.date()} @ {buy_price} x {t1_shares} = TWD {t1_cost:,.0f}")
# 2nd tranche: next KD < 50 signal or -10% drawdown (approximate)
# From backtest, 2nd signal was ~2 weeks later
t2_date = pd.Timestamp("2023-11-03") # approximate
if t2_date in df.index:
t2_price = df.loc[t2_date, "Close"]
else:
nearest = df.index[df.index >= t2_date][0]
t2_price = df.loc[nearest, "Close"]
t2_date = nearest
t2_budget = capital * 0.30
t2_shares = int(t2_budget / t2_price / 1000) * 1000
t2_cost = t2_shares * t2_price
print(f" 第2碼: {t2_date.date()} @ {t2_price:.2f} x {t2_shares} = TWD {t2_cost:,.0f}")
t3_date = pd.Timestamp("2023-11-17")
if t3_date in df.index:
t3_price = df.loc[t3_date, "Close"]
else:
nearest = df.index[df.index >= t3_date][0]
t3_price = df.loc[nearest, "Close"]
t3_date = nearest
t3_budget = capital * 0.40
t3_shares = int(t3_budget / t3_price / 1000) * 1000
t3_cost = t3_shares * t3_price
print(f" 第3碼: {t3_date.date()} @ {t3_price:.2f} x {t3_shares} = TWD {t3_cost:,.0f}")
total_shares = t1_shares + t2_shares + t3_shares
avg_cost = (t1_cost + t2_cost + t3_cost) / total_shares
print(f" 總股數: {total_shares:,} 平均成本: {avg_cost:.2f}")
if len(reached_30) > 0:
sell_70 = int(total_shares * 0.70 / 1000) * 1000
pnl_b30 = (price_30 - avg_cost) * sell_70
remain_b = total_shares - sell_70
print(f" +30% ({target_30:.2f})? → 均價 +30% = {avg_cost*1.30:.2f}")
# Use avg cost for target
target_30b = avg_cost * 1.30
reached_30b = df[df.index > t3_date][df["Close"] >= target_30b]
if len(reached_30b) > 0:
d30b = reached_30b.index[0]
p30b = reached_30b.iloc[0]["Close"]
pnl_b30 = (p30b - avg_cost) * sell_70
print(f" 均價+30%達到: {d30b.date()} @ {p30b:.2f}")
print(f" 賣 70% ({sell_70}股): TWD {pnl_b30:,.0f}")
target_50b = avg_cost * 1.50
reached_50b = df[df.index > d30b][df["Close"] >= target_50b]
if len(reached_50b) > 0:
d50b = reached_50b.index[0]
p50b = reached_50b.iloc[0]["Close"]
pnl_b50 = (p50b - avg_cost) * remain_b
total_b = pnl_b30 + pnl_b50
print(f" 均價+50%達到: {d50b.date()} @ {p50b:.2f}")
print(f" 賣 30% ({remain_b}股): TWD {pnl_b50:,.0f}")
print(f" 3碼策略總獲利: TWD {total_b:,.0f}")
print(f" vs 實際獲利: TWD {actual_pnl:,.0f}")
print(f" 差異: TWD {total_b - actual_pnl:+,.0f}")
# What-if: Late entry (1 week, 2 weeks, 1 month late)
print(f"\n 假設C:延遲進場的影響")
delays = [("1週後", 7), ("2週後", 14), ("1個月後", 30)]
for label, days in delays:
late_date = buy_date + pd.Timedelta(days=days)
closest = df.index[df.index >= late_date]
if len(closest) > 0:
late_d = closest[0]
late_p = df.loc[late_d, "Close"]
late_ret_at_sell = (sell_price - late_p) / late_p
late_pnl = (sell_price - late_p) * shares
# Also check what +30% would be
late_target30 = late_p * 1.30
print(f" {label} ({late_d.date()}): 買入 @ {late_p:.2f}")
print(f" 以 134.70 賣出: 報酬 {late_ret_at_sell:.2%}, 獲利 TWD {late_pnl:,.0f}")
print(f" +30% 目標: {late_target30:.2f}")
# ── 2024 Cycle ──
print(f"\n{sep}")
print(" 【2024 年週期】0050 進場分析")
print(sep)
buy2_date = pd.Timestamp("2024-10-07")
buy2_price = 187.15
buy2_shares = 2000
# Price trajectory after buy
after_buy2 = df[df.index >= buy2_date].copy()
if len(after_buy2) > 0:
peak_after = after_buy2["Close"].max()
peak_date = after_buy2["Close"].idxmax()
trough_after = after_buy2["Close"].min()
trough_date = after_buy2["Close"].idxmin()
latest = after_buy2.iloc[-1]
current_ret = (latest["Close"] - buy2_price) / buy2_price
print(f" 買入: {buy2_date.date()} @ {buy2_price} x {buy2_shares}")
print(f" 期間最高: {peak_date.date()} @ {peak_after:.2f} (+{(peak_after/buy2_price-1):.2%})")
print(f" 期間最低: {trough_date.date()} @ {trough_after:.2f} ({(trough_after/buy2_price-1):.2%})")
print(f" 最新: {latest.name.date()} @ {latest['Close']:.2f} ({current_ret:+.2%})")
print(f" +30% 目標: {buy2_price*1.30:.2f}")
# Check if +30% was reached
target30_2 = buy2_price * 1.30
reached30_2 = after_buy2[after_buy2["Close"] >= target30_2]
if len(reached30_2) > 0:
print(f" +30% 達到: {reached30_2.index[0].date()} @ {reached30_2.iloc[0]['Close']:.2f}")
else:
print(f" +30% 尚未達到")
# ── 2025 Cycle (from 1.png) ──
print(f"\n{sep}")
print(" 【2025 年週期】1.png KD 信號分析")
print(sep)
# The investor circled area around late Oct - mid Nov 2025
# 0050 was around 60-63 (after stock split, prices are adjusted)
signal_start = pd.Timestamp("2025-10-20")
signal_end = pd.Timestamp("2025-11-15")
signal_range = df[(df.index >= signal_start) & (df.index <= signal_end)]
if len(signal_range) > 0:
low_price = signal_range["Close"].min()
low_date = signal_range["Close"].idxmin()
print(f" 紅框區間: {signal_start.date()} ~ {signal_end.date()}")
print(f" 區間最低: {low_date.date()} @ {low_price:.2f}")
after_signal = df[df.index > signal_end]
if len(after_signal) > 0:
latest = after_signal.iloc[-1]
gain = (latest["Close"] - low_price) / low_price
print(f" 最新價格: {latest.name.date()} @ {latest['Close']:.2f}")
print(f" 從低點漲幅: {gain:.2%}")
print(f" +30% 目標: {low_price * 1.30:.2f}")
if gain >= 0.30:
print(f" ✓ 已達 +30% 停利門檻!")
else:
print(f" 距離 +30% 還差: {0.30 - gain:.2%}")
# ── Summary table ──
print(f"\n{sep}")
print(" 情境損益比較摘要")
print(sep)
results = []
results.append({"情境": "實際交易(1碼+8.94%全出)", "獲利TWD": f"{actual_pnl:,.0f}", "報酬率": f"{actual_ret:.2%}"})
if len(reached_30) > 0:
results.append({"情境": "假設A: 1碼+嚴格停利", "獲利TWD": f"{total_strat:,.0f}", "報酬率": f"{total_strat/(buy_price*shares):.2%}"})
results.append({"情境": "假設B: 3碼+嚴格停利", "獲利TWD": "見上方計算", "報酬率": "—"})
rdf = pd.DataFrame(results)
print(rdf.to_string(index=False))
rdf.to_csv(os.path.join(OUTPUT_DIR, "whatif_summary.csv"), index=False, encoding="utf-8-sig")
print(f"\n 已儲存: {os.path.join(OUTPUT_DIR, 'whatif_summary.csv')}")
print(sep)
if __name__ == "__main__":
main()