vn-backtest 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vn_backtest-0.1.0/LICENSE +21 -0
- vn_backtest-0.1.0/PKG-INFO +302 -0
- vn_backtest-0.1.0/README.md +278 -0
- vn_backtest-0.1.0/pyproject.toml +39 -0
- vn_backtest-0.1.0/setup.cfg +4 -0
- vn_backtest-0.1.0/src/vn_backtest/__init__.py +20 -0
- vn_backtest-0.1.0/src/vn_backtest/analysis.py +405 -0
- vn_backtest-0.1.0/src/vn_backtest/engine.py +2575 -0
- vn_backtest-0.1.0/src/vn_backtest/optimizer.py +362 -0
- vn_backtest-0.1.0/src/vn_backtest/reporter.py +659 -0
- vn_backtest-0.1.0/src/vn_backtest/strategy.py +394 -0
- vn_backtest-0.1.0/src/vn_backtest/templates/backtest_opt_report.html +128 -0
- vn_backtest-0.1.0/src/vn_backtest/templates/backtest_report.html +260 -0
- vn_backtest-0.1.0/src/vn_backtest/templates/style.css +168 -0
- vn_backtest-0.1.0/src/vn_backtest/trading_rules.py +197 -0
- vn_backtest-0.1.0/src/vn_backtest.egg-info/PKG-INFO +302 -0
- vn_backtest-0.1.0/src/vn_backtest.egg-info/SOURCES.txt +18 -0
- vn_backtest-0.1.0/src/vn_backtest.egg-info/dependency_links.txt +1 -0
- vn_backtest-0.1.0/src/vn_backtest.egg-info/requires.txt +7 -0
- vn_backtest-0.1.0/src/vn_backtest.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Phong Vu 2010
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vn-backtest
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A powerful Backtesting Framework tailored strictly for the Vietnam Stock Market (HOSE, HNX, UPCOM).
|
|
5
|
+
Author-email: Hunter Do <phongvu2010@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/phongvu2010/backtest-framework
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/phongvu2010/backtest-framework/issues
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pandas>=1.5.0
|
|
18
|
+
Requires-Dist: numpy>=1.21.0
|
|
19
|
+
Requires-Dist: plotly>=5.10.0
|
|
20
|
+
Requires-Dist: jinja2>=3.1.0
|
|
21
|
+
Provides-Extra: optimize
|
|
22
|
+
Requires-Dist: optuna; extra == "optimize"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# 📈 vn-backtest
|
|
26
|
+
|
|
27
|
+
**vn-backtest** là một framework kiểm thử chiến lược giao dịch (backtesting) mã nguồn mở bằng Python, được thiết kế và tối ưu hóa **đặc thù cho Thị trường Chứng khoán Việt Nam (HOSE, HNX, UPCoM)**.
|
|
28
|
+
|
|
29
|
+
Thư viện hỗ trợ mô phỏng chính xác các luật giao dịch thực tế của Việt Nam qua các thời kỳ lịch sử, kiểm soát rủi ro Look-Ahead Bias, đồng bộ dòng tiền khả dụng, hỗ trợ ký quỹ (margin), xử lý các sự kiện doanh nghiệp và tạo báo cáo HTML tương tác trực quan cao cấp.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## ✨ Tính Năng Nổi Bật
|
|
34
|
+
|
|
35
|
+
### 1. Đặc Thù Giao Dịch Chứng Khoán Việt Nam
|
|
36
|
+
* ⏱️ **Chu kỳ thanh toán & vòng quay tiền T+1.5 / T+2:** Giả lập chính xác thời gian cổ phiếu về tài khoản (settlement lock) và tiền bán chờ về.
|
|
37
|
+
* *Lịch sử thay đổi:* Tự động chuyển đổi chu kỳ thanh toán theo mốc lịch sử thực tế (T+3 trước 2016, T+2 trước 29/08/2022 và T+1.5 từ 29/08/2022 trở đi).
|
|
38
|
+
* ⚡ **Mô phỏng Lệnh định kỳ ATO / ATC & Khớp Liên Tục:**
|
|
39
|
+
* **Lệnh ATO:** Khớp chính xác tại giá mở cửa (`Open`) của ngày thực thi, không áp dụng sai số trượt giá (slippage = 0).
|
|
40
|
+
* **Lệnh ATC:** Khớp chính xác tại giá đóng cửa (`Close`) của ngày thực thi, không áp dụng sai số trượt giá (slippage = 0).
|
|
41
|
+
* **Cơ chế hủy phần dư (Fill-or-Kill/Partial Fill Cancel):** Phần khối lượng chưa khớp của lệnh ATO/ATC sẽ tự động hủy ngay sau phiên khớp lệnh định kỳ, không tồn đọng hoặc đẩy lùi sang ngày hôm sau.
|
|
42
|
+
* **Hủy lệnh khi mất thanh khoản:** Tự động hủy toàn bộ lệnh ATO/ATC nếu mã chứng khoán không phát sinh giao dịch (Volume = 0 hoặc bị tạm ngừng giao dịch) trong ngày thực thi.
|
|
43
|
+
* 💸 **Ứng trước tiền bán & Ký quỹ (Margin):**
|
|
44
|
+
* **Available Cash Sync:** Đồng bộ luồng tiền mặt khả dụng động loại bỏ hoàn toàn hiện tượng lệch dòng tiền (drift) khi đặt lệnh.
|
|
45
|
+
* **Ứng trước tiền bán:** Tự động tính toán phí ứng trước tiền bán khi bạn thực hiện mua mới trước ngày tiền bán về tài khoản.
|
|
46
|
+
* **Margin Trading:** Hỗ trợ cấu hình tỷ lệ ký quỹ (`margin_ratio`), lãi suất vay margin năm, tự động cảnh báo ký quỹ (Margin Call) và tự động giải chấp tài khoản (Force Sell) khi tài sản ròng vi phạm tỷ lệ duy trì tối thiểu.
|
|
47
|
+
* 📏 **Quy tắc bước giá (Tick Size) & Lịch sử biên độ dao động:**
|
|
48
|
+
* **Bước giá động:** Tự động áp dụng bước giá HOSE/HNX/UPCoM (ví dụ: luật bước giá mới của HOSE từ 12/09/2016).
|
|
49
|
+
* **Biên độ Trần/Sàn lịch sử:** Tự động kiểm tra giá đặt lệnh với giới hạn trần/sàn theo quy định từng sàn qua từng thời kỳ (ví dụ: mốc thay đổi biên độ sàn HOSE từ 5% lên 7%, HNX từ 7% lên 10%).
|
|
50
|
+
* **Từ chối lệnh do tắc nghẽn thanh khoản:** Từ chối lệnh mua tại giá trần khi trắng bên bán (hoặc bán sàn khi trắng bên mua).
|
|
51
|
+
* 🏢 **Sự kiện Doanh nghiệp (Corporate Actions):**
|
|
52
|
+
* Tự động xử lý điều chỉnh giá và số lượng cổ phiếu cho cổ tức bằng tiền mặt (Cash Dividend), cổ tức bằng cổ phiếu (Stock Dividend), và quyền mua phát hành thêm (Rights Issue).
|
|
53
|
+
* Khấu trừ thuế thu nhập cá nhân đầu tư chứng khoán **5%** đối với cổ tức theo quy định (Nghị định 126).
|
|
54
|
+
* Mô phỏng độ trễ niêm yết cổ phiếu thưởng/quyền mua về tài khoản (mặc định 90 ngày).
|
|
55
|
+
|
|
56
|
+
### 2. Quản Trị Rủi Ro & Phân Tích Kỹ Thuật
|
|
57
|
+
* 🛡️ **Tự động phát hiện Look-Ahead Bias:** Cảnh báo đỏ ngay lập tức tại hàm đăng ký chỉ báo `self.I()` nếu phát hiện chỉ báo vô tình tham chiếu dữ liệu tương lai (như dùng `.shift(-1)`, nội suy tương lai).
|
|
58
|
+
* ⚖️ **Hỗ trợ tỷ lệ đơn vị giá (`price_scale`):** Giải quyết sai lệch tính toán biên độ trần/sàn và bước giá khi nguồn dữ liệu đầu vào sử dụng đơn vị nghìn đồng (ví dụ: giá FPT hiển thị là `85.5` thay vì `85,500` VND từ CafeF/FireAnt).
|
|
59
|
+
* 📈 **So sánh đa chỉ số tham chiếu (Multi-Benchmark):** Cho phép truyền nhiều DataFrame chỉ số tham chiếu (như VN-Index, VN30, HNX-Index) để so sánh hiệu quả tăng trưởng tài sản đồng thời.
|
|
60
|
+
* 📊 **Biểu đồ Phân bổ Tài sản động (Asset Allocation Chart):** Hiển thị trực quan sự thay đổi tỷ trọng phân bổ vốn giữa các mã cổ phiếu và Tiền mặt (Cash) qua từng ngày dưới dạng Stacked Area.
|
|
61
|
+
* 📋 **Báo cáo hiệu suất từng mã (Ticker Performance Summary):** Nhóm các giao dịch đã đóng theo quy tắc FIFO để tính toán các chỉ số chi tiết cho từng mã (Win Rate, Profit Factor, số giao dịch, lệnh thắng/thua lớn nhất).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ⚙️ Cài Đặt
|
|
66
|
+
|
|
67
|
+
Framework yêu cầu **Python >= 3.12**.
|
|
68
|
+
|
|
69
|
+
Cài đặt ở chế độ phát triển (development mode):
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/phongvu2010/backtest-framework.git
|
|
72
|
+
cd vn-backtest
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🚀 Hướng Dẫn Sử Dụng Nhanh (Quick Start)
|
|
79
|
+
|
|
80
|
+
Dưới đây là ví dụ xây dựng chiến lược giao dịch đa tài sản (HPG, FPT, MWG) sử dụng kết hợp tín hiệu chỉ báo, đặt lệnh ATO/ATC đặc thù, so sánh nhiều chỉ số benchmark và xuất báo cáo HTML cao cấp:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import pandas as pd
|
|
84
|
+
from vn_backtest.strategy import Strategy
|
|
85
|
+
from vn_backtest.engine import BacktestEngine
|
|
86
|
+
from vn_backtest.analysis import PerformanceAnalyzer
|
|
87
|
+
from vn_backtest.reporter import ReportGenerator
|
|
88
|
+
|
|
89
|
+
# 1. Định nghĩa chỉ báo Simple Moving Average (SMA)
|
|
90
|
+
def SMA(data, period, column="Close"):
|
|
91
|
+
return data[column].rolling(window=period).mean()
|
|
92
|
+
|
|
93
|
+
# 2. Xây dựng Chiến lược đa tài sản
|
|
94
|
+
class MultiAssetSmaCross(Strategy):
|
|
95
|
+
sma_fast = 10
|
|
96
|
+
sma_slow = 30
|
|
97
|
+
|
|
98
|
+
def init(self):
|
|
99
|
+
self.fast_ma = {}
|
|
100
|
+
self.slow_ma = {}
|
|
101
|
+
|
|
102
|
+
# Đăng ký chỉ báo cho từng mã qua self.I() để tự động kiểm tra Look-Ahead Bias
|
|
103
|
+
for ticker in self.data.keys():
|
|
104
|
+
self.fast_ma[ticker] = self.I(lambda df: SMA(df, self.sma_fast), name=f"Fast_{ticker}")
|
|
105
|
+
self.slow_ma[ticker] = self.I(lambda df: SMA(df, self.sma_slow), name=f"Slow_{ticker}")
|
|
106
|
+
|
|
107
|
+
def next(self):
|
|
108
|
+
idx = self.current_idx
|
|
109
|
+
# Bỏ qua các ngày đầu chưa đủ dữ liệu tính toán chỉ báo chậm
|
|
110
|
+
if idx < self.sma_slow:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
for ticker in self.data.keys():
|
|
114
|
+
# Lấy giá trị chỉ báo hiện tại và hôm qua
|
|
115
|
+
fast_curr = self.fast_ma[ticker].iloc[idx]
|
|
116
|
+
fast_prev = self.fast_ma[ticker].iloc[idx - 1]
|
|
117
|
+
slow_curr = self.slow_ma[ticker].iloc[idx]
|
|
118
|
+
slow_prev = self.slow_ma[ticker].iloc[idx - 1]
|
|
119
|
+
|
|
120
|
+
# Tín hiệu MUA: Đường nhanh cắt lên đường chậm (Golden Cross)
|
|
121
|
+
if fast_prev <= slow_prev and fast_curr > slow_curr:
|
|
122
|
+
# Đặt lệnh MUA ATO (khớp tại Open ngày hôm sau, phân bổ 30% sức mua)
|
|
123
|
+
self.buy(ticker, size=0.3, order_type="ATO")
|
|
124
|
+
|
|
125
|
+
# Tín hiệu BÁN: Đường nhanh cắt xuống đường chậm (Death Cross)
|
|
126
|
+
elif fast_prev >= slow_prev and fast_curr < slow_curr:
|
|
127
|
+
# Đặt lệnh BÁN ATC (khớp tại Close ngày hôm sau, bán toàn bộ vị thế đang nắm giữ)
|
|
128
|
+
if self.positions.get(ticker, 0) > 0:
|
|
129
|
+
self.sell(ticker, order_type="ATC")
|
|
130
|
+
|
|
131
|
+
# 3. Chuẩn bị dữ liệu OHLCV (Giá chia 1000 từ FireAnt/CafeF)
|
|
132
|
+
dates = pd.date_range(start="2023-01-01", periods=100, freq="D")
|
|
133
|
+
stock_data = {
|
|
134
|
+
"FPT": pd.DataFrame({"Open": 80.0, "High": 82.0, "Low": 79.0, "Close": 81.0, "Volume": 100000}, index=dates),
|
|
135
|
+
"HPG": pd.DataFrame({"Open": 25.0, "High": 26.0, "Low": 24.5, "Close": 25.5, "Volume": 150000}, index=dates),
|
|
136
|
+
"MWG": pd.DataFrame({"Open": 40.0, "High": 41.5, "Low": 39.5, "Close": 40.5, "Volume": 80000}, index=dates)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# 4. Chuẩn bị dữ liệu Benchmark để đối chiếu hiệu quả
|
|
140
|
+
benchmarks = {
|
|
141
|
+
"VNINDEX": pd.DataFrame({"Close": 1000.0 + (dates.day * 2)}, index=dates),
|
|
142
|
+
"VN30": pd.DataFrame({"Close": 1010.0 + (dates.day * 1.5)}, index=dates)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# 5. Cấu hình và chạy Backtest Engine
|
|
146
|
+
engine = BacktestEngine(
|
|
147
|
+
data=stock_data,
|
|
148
|
+
strategy_class=MultiAssetSmaCross,
|
|
149
|
+
initial_cash=100_000_000.0, # Khởi đầu với 100 triệu VND
|
|
150
|
+
price_scale=1000.0, # Quy đổi giá đầu vào sang VND thực tế (nhân 1000)
|
|
151
|
+
lot_size=100, # Lô chẵn 100 cổ phiếu
|
|
152
|
+
exchange="hose" # Áp dụng quy tắc sàn HOSE
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
results = engine.run()
|
|
156
|
+
equity_df = results["equity_curve"]
|
|
157
|
+
trades_df = results["trades"]
|
|
158
|
+
|
|
159
|
+
# 6. Tính toán thống kê hiệu suất chuyên sâu
|
|
160
|
+
metrics = PerformanceAnalyzer.calculate_metrics(
|
|
161
|
+
equity_curve=equity_df,
|
|
162
|
+
trades=trades_df,
|
|
163
|
+
benchmark_data=benchmarks,
|
|
164
|
+
initial_cash=100_000_000.0,
|
|
165
|
+
risk_free_rate=0.04 # Lãi suất phi rủi ro 4%/năm tại Việt Nam
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# 7. Sinh báo cáo HTML cao cấp dạng tương tác trực quan
|
|
169
|
+
reporter = ReportGenerator(output_dir="reports")
|
|
170
|
+
report_path = reporter.generate_report(
|
|
171
|
+
metrics=metrics,
|
|
172
|
+
equity_curve=equity_df,
|
|
173
|
+
trades=trades_df,
|
|
174
|
+
stock_data=stock_data,
|
|
175
|
+
ticker="FPT,HPG,MWG",
|
|
176
|
+
strategy_name="MultiAssetSmaCross",
|
|
177
|
+
benchmark_data=benchmarks,
|
|
178
|
+
filename="multi_asset_report.html",
|
|
179
|
+
benchmark_symbol="VNINDEX"
|
|
180
|
+
)
|
|
181
|
+
print(f"Backtest hoàn tất! Báo cáo đã được xuất ra tại: {report_path}")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 🛠️ Tối Ưu Hóa Tham Số (Parameter Optimization)
|
|
187
|
+
|
|
188
|
+
Bạn có thể tìm kiếm tổ hợp tham số tối ưu bằng cách sử dụng `ParameterOptimizer` hỗ trợ xử lý song song đa tiến trình (multiprocessing):
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from vn_backtest.optimizer import ParameterOptimizer
|
|
192
|
+
|
|
193
|
+
param_grid = {
|
|
194
|
+
"sma_fast": [5, 10, 15],
|
|
195
|
+
"sma_slow": [20, 30, 50]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
optimizer = ParameterOptimizer(
|
|
199
|
+
data=stock_data,
|
|
200
|
+
strategy_class=MultiAssetSmaCross,
|
|
201
|
+
param_grid=param_grid,
|
|
202
|
+
initial_cash=100_000_000.0,
|
|
203
|
+
exchange="hose",
|
|
204
|
+
n_jobs=-1 # Chạy tối đa toàn bộ nhân CPU của máy
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Chạy Grid Search và sắp xếp kết quả theo tỷ số Sharpe giảm dần
|
|
208
|
+
opt_results = optimizer.run_optimization(sort_by="sharpe_ratio", ascending=False)
|
|
209
|
+
print(opt_results.head())
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 📖 Chi Tiết Cấu Hình `BacktestEngine`
|
|
215
|
+
|
|
216
|
+
Dưới đây là mô tả chi tiết toàn bộ các tham số khởi tạo của lớp `BacktestEngine`:
|
|
217
|
+
|
|
218
|
+
| Tham Số | Kiểu Dữ Liệu | Mặc Định | Mô Tả |
|
|
219
|
+
| :--- | :--- | :--- | :--- |
|
|
220
|
+
| `data` | `DataFrame` \| `Dict[str, DataFrame]` | *(Bắt buộc)* | Dữ liệu lịch sử giá. Phải chứa cột `Open`, `High`, `Low`, `Close`, `Volume`. Index định dạng `DatetimeIndex`. |
|
|
221
|
+
| `strategy_class` | `Type[Strategy]` | *(Bắt buộc)* | Lớp chiến lược giao dịch kế thừa từ `Strategy`. |
|
|
222
|
+
| `corporate_actions` | `Dict[str, DataFrame]` | `None` | Dữ liệu sự kiện doanh nghiệp của các mã chứng khoán (Cổ tức, phát hành thêm). |
|
|
223
|
+
| `initial_cash` | `float` | `100,000,000.0` | Số tiền mặt khởi đầu cho tài khoản backtest (đơn vị: VND). |
|
|
224
|
+
| `buy_fee` | `float` | `0.0015` | Tỷ lệ phí giao dịch mua chứng khoán (ví dụ: `0.0015` = 0.15% giá trị giao dịch). |
|
|
225
|
+
| `sell_fee` | `float` | `0.0015` | Tỷ lệ phí giao dịch bán chứng khoán. |
|
|
226
|
+
| `sell_tax` | `float` | `0.001` | Tỷ lệ thuế thu nhập cá nhân khi bán chứng khoán tại Việt Nam (mặc định: 0.1%). |
|
|
227
|
+
| `settlement_days` | `int` | `2` | Chu kỳ thanh toán cổ phiếu mặc định (T+2). Sẽ tự động áp dụng chu kỳ lịch sử nếu bật `dynamic_rules`. |
|
|
228
|
+
| `lot_size` | `int` | `100` | Quy định lô giao dịch mặc định. Tự động chuyển đổi theo lịch sử sàn nếu bật `dynamic_rules`. |
|
|
229
|
+
| `exchange` | `str` \| `Dict[str, str]` | `"hose"` | Sàn giao dịch của cổ phiếu để tính Trần/Sàn và bước giá (`hose`, `hnx`, `upcom`). Nhận vào chuỗi hoặc một dict ánh xạ từng mã. |
|
|
230
|
+
| `execution_at` | `str` | `"open"` | Thời điểm thực thi giao dịch liên tục cho phiên kế tiếp (`"open"`: Khớp tại Open, `"close"`: Khớp tại Close). |
|
|
231
|
+
| `restrict_ceiling_buy` | `bool` | `True` | Nếu bật, lệnh mua sẽ bị từ chối/hủy nếu giá thị trường chạm giá trần (trắng bên bán). |
|
|
232
|
+
| `restrict_floor_sell` | `bool` | `True` | Nếu bật, lệnh bán sẽ bị từ chối/hủy nếu giá thị trường chạm giá sàn (trắng bên mua). |
|
|
233
|
+
| `slippage` | `float` | `0.0` | Tỷ lệ trượt giá áp dụng cho lệnh liên tục (ví dụ: `0.001` = 0.1% trượt giá). ATO/ATC không áp dụng. |
|
|
234
|
+
| `dynamic_rules` | `bool` | `True` | Kích hoạt cơ chế luật giao dịch thay đổi theo lịch sử thực tế của TTCK Việt Nam. |
|
|
235
|
+
| `advance_interest_rate` | `float` | `0.12` | Lãi suất ứng trước tiền bán năm (mặc định: 12%/năm) để mua cổ phiếu trước ngày tiền về. |
|
|
236
|
+
| `auto_close_at_end` | `bool` | `True` | Tự động tất toán (bán) toàn bộ cổ phiếu nắm giữ ở phiên cuối cùng của chuỗi dữ liệu. |
|
|
237
|
+
| `allow_odd_lot` | `bool` | `False` | Cho phép giao dịch lô lẻ dưới 100 cổ phiếu (ví dụ: 1-99). |
|
|
238
|
+
| `max_volume_ratio` | `float` | `None` | Giới hạn khối lượng khớp tối đa theo tỷ lệ thanh khoản ngày hôm đó (ví dụ: `0.01` nghĩa là chỉ được khớp tối đa 1% tổng Volume giao dịch của ngày). |
|
|
239
|
+
| `adjust_corporate_actions` | `bool` | `False` | Bật mô phỏng điều chỉnh giá/số lượng do cổ tức/quyền mua của cổ phiếu chưa được điều chỉnh (raw data). |
|
|
240
|
+
| `force_adjusted` | `bool` | `None` | Ép buộc trạng thái dữ liệu đầu vào. `True`: Đã điều chỉnh giá (Adj_Close), `False`: Giá gốc. |
|
|
241
|
+
| `margin_ratio` | `float` | `1.0` | Tỷ lệ ký quỹ tối thiểu. `1.0`: Không dùng margin. `0.5`: Margin 1:1 (Sức mua gấp đôi tài sản ròng). |
|
|
242
|
+
| `margin_interest_rate` | `float` | `0.13` | Lãi suất vay ký quỹ (margin loan) năm (mặc định: 13%/năm). |
|
|
243
|
+
| `margin_maintenance_ratio` | `float` | `0.35` | Tỷ lệ duy trì ký quỹ duy trì tối thiểu. Khi giá trị tài sản ròng / tổng tài sản dưới ngưỡng này sẽ kích hoạt Margin Call/Force Sell. |
|
|
244
|
+
| `ticker` | `str` | `None` | Tên mã chứng khoán nếu `data` truyền vào là một DataFrame duy nhất. |
|
|
245
|
+
| `strategy_params` | `Dict[str, Any]` | `None` | Bộ tham số truyền trực tiếp vào lớp chiến lược tại thời điểm khởi tạo. |
|
|
246
|
+
| `market_impact_coef` | `float` | `0.0` | Hệ số tác động thị trường dùng để tính toán trượt giá động dựa trên tỷ lệ khối lượng đặt lệnh so với Volume ngày. |
|
|
247
|
+
| `rights_listing_delay` | `int` | `90` | Số ngày chờ niêm yết cổ phiếu thưởng/quyền mua về tài khoản giao dịch (mặc định: 90 ngày). |
|
|
248
|
+
| `dividend_tax_rate` | `float` | `0.05` | Tỷ lệ khấu trừ thuế thu nhập cá nhân đối với cổ tức nhận được (mặc định: 5%). |
|
|
249
|
+
| `price_scale` | `float` | `1.0` | Tỷ lệ điều chỉnh đơn vị giá. Đặt `1000.0` nếu dữ liệu đầu vào đã bị chia cho 1000 (ví dụ: giá hiển thị 85.5 thay vì 85,500 VND). |
|
|
250
|
+
| `listing_dates` | `Dict[str, Timestamp]` | `None` | Ngày niêm yết chính thức của các mã chứng khoán dùng để kích hoạt quy định biên độ giá đặc biệt trong ngày đầu tiên lên sàn (Listing Day). |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 📂 Cấu Trúc Thư Mục Dự Án
|
|
255
|
+
|
|
256
|
+
```plaintext
|
|
257
|
+
vn-backtest/
|
|
258
|
+
├── src/
|
|
259
|
+
│ └── vn_backtest/
|
|
260
|
+
│ ├── __init__.py # Export các lớp và hàm chính
|
|
261
|
+
│ ├── engine.py # Core Engine mô phỏng khớp lệnh, T+, margin, dòng tiền, sự kiện DN
|
|
262
|
+
│ ├── strategy.py # Lớp Strategy cơ sở và đăng ký chỉ báo
|
|
263
|
+
│ ├── trading_rules.py # Quản lý bước giá, lô giao dịch, trần/sàn và chu kỳ T+ lịch sử
|
|
264
|
+
│ ├── analysis.py # Phân tích hiệu suất, đo lường rủi ro và đối chiếu giao dịch FIFO
|
|
265
|
+
│ ├── optimizer.py # Grid Search tối ưu hóa tham số đa luồng (multiprocessing)
|
|
266
|
+
│ └── reporter.py # Sinh báo cáo HTML tương tác (Plotly, Jinja2, Stacked Allocation)
|
|
267
|
+
├── pyproject.toml # Cấu hình cài đặt package và quản lý phụ thuộc
|
|
268
|
+
└── README.md # Tài liệu hướng dẫn sử dụng
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 🛡️ Chi Tiết Cơ Chế Chặn Look-Ahead Bias
|
|
274
|
+
|
|
275
|
+
Look-Ahead Bias là lỗi phổ biến nhất khiến kết quả backtest của bạn "đẹp như mơ" nhưng khi giao dịch thực tế lại thua lỗ vì thuật toán vô tình sử dụng dữ liệu giá của tương lai để ra quyết định hôm nay.
|
|
276
|
+
|
|
277
|
+
`vn-backtest` cung cấp **2 lớp bảo vệ**:
|
|
278
|
+
|
|
279
|
+
1. **Kiểm tra tĩnh trong `self.I()`:**
|
|
280
|
+
Khi bạn đăng ký một chỉ báo qua `self.I()`, hệ thống sẽ tự động chạy hàm tính toán chỉ báo đó trên 2 lát cắt dữ liệu: toàn bộ dữ liệu lịch sử và dữ liệu bị cắt ngắn (sliced) tại một ngày ngẫu nhiên trong quá khứ. Nếu giá trị chỉ báo tại ngày đó khác nhau, hệ thống lập tức ném ra **cảnh báo đỏ** kèm theo giá trị sai lệch để bạn biết chỉ báo của mình đang bị rò rỉ thông tin tương lai.
|
|
281
|
+
2. **Truy cập an toàn qua `self.safe_data`:**
|
|
282
|
+
Trong hàm `next()`, thay vì thao tác trực tiếp trên `self.data` (chứa toàn bộ dữ liệu lịch sử), bạn có thể gọi `self.safe_data`. Thuộc tính này sẽ trả về một DataFrame chứa dữ liệu được cắt nghiêm ngặt từ ngày đầu tiên đến ngày giao dịch hiện tại (`current_idx`), đảm bảo logic giao dịch của bạn tuyệt đối không thể nhìn trước tương lai.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## ⚠️ Khuyến Cáo Dữ Liệu (Survival Bias)
|
|
287
|
+
|
|
288
|
+
Kết quả backtest của bất kỳ hệ thống nào cũng có nguy cơ bị ảnh hưởng bởi **Thiên lệch sống sót (Survivorship Bias)** nếu bạn chỉ chạy kiểm thử trên danh sách các mã cổ phiếu đang niêm yết hiện tại.
|
|
289
|
+
|
|
290
|
+
Để kết quả phản ánh khách quan nhất:
|
|
291
|
+
* Hãy bổ sung dữ liệu lịch sử của các mã cổ phiếu đã bị hủy niêm yết trong quá khứ.
|
|
292
|
+
* Backtest Engine hỗ trợ cơ chế tự động đóng toàn bộ vị thế của mã chứng khoán tại ngày giao dịch cuối cùng trước khi bị hủy niêm yết để tính toán đúng lợi nhuận thực tế.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 🤝 Đóng Góp Ý Kiến (Contributing)
|
|
297
|
+
|
|
298
|
+
Mọi đóng góp nhằm cải thiện thuật toán khớp lệnh định kỳ/liên tục, mở rộng tính năng phân tích rủi ro hoặc báo cáo lỗi phát sinh đều được hoan nghênh! Hãy mở một Issue hoặc gửi Pull Request trực tiếp trên kho lưu trữ Github của dự án.
|
|
299
|
+
|
|
300
|
+
## 📄 Giấy Phép (License)
|
|
301
|
+
|
|
302
|
+
Dự án được phát hành và phân phối theo các điều khoản của giấy phép **MIT**. Xem tệp `LICENSE` để biết thêm chi tiết.
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# 📈 vn-backtest
|
|
2
|
+
|
|
3
|
+
**vn-backtest** là một framework kiểm thử chiến lược giao dịch (backtesting) mã nguồn mở bằng Python, được thiết kế và tối ưu hóa **đặc thù cho Thị trường Chứng khoán Việt Nam (HOSE, HNX, UPCoM)**.
|
|
4
|
+
|
|
5
|
+
Thư viện hỗ trợ mô phỏng chính xác các luật giao dịch thực tế của Việt Nam qua các thời kỳ lịch sử, kiểm soát rủi ro Look-Ahead Bias, đồng bộ dòng tiền khả dụng, hỗ trợ ký quỹ (margin), xử lý các sự kiện doanh nghiệp và tạo báo cáo HTML tương tác trực quan cao cấp.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Tính Năng Nổi Bật
|
|
10
|
+
|
|
11
|
+
### 1. Đặc Thù Giao Dịch Chứng Khoán Việt Nam
|
|
12
|
+
* ⏱️ **Chu kỳ thanh toán & vòng quay tiền T+1.5 / T+2:** Giả lập chính xác thời gian cổ phiếu về tài khoản (settlement lock) và tiền bán chờ về.
|
|
13
|
+
* *Lịch sử thay đổi:* Tự động chuyển đổi chu kỳ thanh toán theo mốc lịch sử thực tế (T+3 trước 2016, T+2 trước 29/08/2022 và T+1.5 từ 29/08/2022 trở đi).
|
|
14
|
+
* ⚡ **Mô phỏng Lệnh định kỳ ATO / ATC & Khớp Liên Tục:**
|
|
15
|
+
* **Lệnh ATO:** Khớp chính xác tại giá mở cửa (`Open`) của ngày thực thi, không áp dụng sai số trượt giá (slippage = 0).
|
|
16
|
+
* **Lệnh ATC:** Khớp chính xác tại giá đóng cửa (`Close`) của ngày thực thi, không áp dụng sai số trượt giá (slippage = 0).
|
|
17
|
+
* **Cơ chế hủy phần dư (Fill-or-Kill/Partial Fill Cancel):** Phần khối lượng chưa khớp của lệnh ATO/ATC sẽ tự động hủy ngay sau phiên khớp lệnh định kỳ, không tồn đọng hoặc đẩy lùi sang ngày hôm sau.
|
|
18
|
+
* **Hủy lệnh khi mất thanh khoản:** Tự động hủy toàn bộ lệnh ATO/ATC nếu mã chứng khoán không phát sinh giao dịch (Volume = 0 hoặc bị tạm ngừng giao dịch) trong ngày thực thi.
|
|
19
|
+
* 💸 **Ứng trước tiền bán & Ký quỹ (Margin):**
|
|
20
|
+
* **Available Cash Sync:** Đồng bộ luồng tiền mặt khả dụng động loại bỏ hoàn toàn hiện tượng lệch dòng tiền (drift) khi đặt lệnh.
|
|
21
|
+
* **Ứng trước tiền bán:** Tự động tính toán phí ứng trước tiền bán khi bạn thực hiện mua mới trước ngày tiền bán về tài khoản.
|
|
22
|
+
* **Margin Trading:** Hỗ trợ cấu hình tỷ lệ ký quỹ (`margin_ratio`), lãi suất vay margin năm, tự động cảnh báo ký quỹ (Margin Call) và tự động giải chấp tài khoản (Force Sell) khi tài sản ròng vi phạm tỷ lệ duy trì tối thiểu.
|
|
23
|
+
* 📏 **Quy tắc bước giá (Tick Size) & Lịch sử biên độ dao động:**
|
|
24
|
+
* **Bước giá động:** Tự động áp dụng bước giá HOSE/HNX/UPCoM (ví dụ: luật bước giá mới của HOSE từ 12/09/2016).
|
|
25
|
+
* **Biên độ Trần/Sàn lịch sử:** Tự động kiểm tra giá đặt lệnh với giới hạn trần/sàn theo quy định từng sàn qua từng thời kỳ (ví dụ: mốc thay đổi biên độ sàn HOSE từ 5% lên 7%, HNX từ 7% lên 10%).
|
|
26
|
+
* **Từ chối lệnh do tắc nghẽn thanh khoản:** Từ chối lệnh mua tại giá trần khi trắng bên bán (hoặc bán sàn khi trắng bên mua).
|
|
27
|
+
* 🏢 **Sự kiện Doanh nghiệp (Corporate Actions):**
|
|
28
|
+
* Tự động xử lý điều chỉnh giá và số lượng cổ phiếu cho cổ tức bằng tiền mặt (Cash Dividend), cổ tức bằng cổ phiếu (Stock Dividend), và quyền mua phát hành thêm (Rights Issue).
|
|
29
|
+
* Khấu trừ thuế thu nhập cá nhân đầu tư chứng khoán **5%** đối với cổ tức theo quy định (Nghị định 126).
|
|
30
|
+
* Mô phỏng độ trễ niêm yết cổ phiếu thưởng/quyền mua về tài khoản (mặc định 90 ngày).
|
|
31
|
+
|
|
32
|
+
### 2. Quản Trị Rủi Ro & Phân Tích Kỹ Thuật
|
|
33
|
+
* 🛡️ **Tự động phát hiện Look-Ahead Bias:** Cảnh báo đỏ ngay lập tức tại hàm đăng ký chỉ báo `self.I()` nếu phát hiện chỉ báo vô tình tham chiếu dữ liệu tương lai (như dùng `.shift(-1)`, nội suy tương lai).
|
|
34
|
+
* ⚖️ **Hỗ trợ tỷ lệ đơn vị giá (`price_scale`):** Giải quyết sai lệch tính toán biên độ trần/sàn và bước giá khi nguồn dữ liệu đầu vào sử dụng đơn vị nghìn đồng (ví dụ: giá FPT hiển thị là `85.5` thay vì `85,500` VND từ CafeF/FireAnt).
|
|
35
|
+
* 📈 **So sánh đa chỉ số tham chiếu (Multi-Benchmark):** Cho phép truyền nhiều DataFrame chỉ số tham chiếu (như VN-Index, VN30, HNX-Index) để so sánh hiệu quả tăng trưởng tài sản đồng thời.
|
|
36
|
+
* 📊 **Biểu đồ Phân bổ Tài sản động (Asset Allocation Chart):** Hiển thị trực quan sự thay đổi tỷ trọng phân bổ vốn giữa các mã cổ phiếu và Tiền mặt (Cash) qua từng ngày dưới dạng Stacked Area.
|
|
37
|
+
* 📋 **Báo cáo hiệu suất từng mã (Ticker Performance Summary):** Nhóm các giao dịch đã đóng theo quy tắc FIFO để tính toán các chỉ số chi tiết cho từng mã (Win Rate, Profit Factor, số giao dịch, lệnh thắng/thua lớn nhất).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## ⚙️ Cài Đặt
|
|
42
|
+
|
|
43
|
+
Framework yêu cầu **Python >= 3.12**.
|
|
44
|
+
|
|
45
|
+
Cài đặt ở chế độ phát triển (development mode):
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/phongvu2010/backtest-framework.git
|
|
48
|
+
cd vn-backtest
|
|
49
|
+
pip install -e .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🚀 Hướng Dẫn Sử Dụng Nhanh (Quick Start)
|
|
55
|
+
|
|
56
|
+
Dưới đây là ví dụ xây dựng chiến lược giao dịch đa tài sản (HPG, FPT, MWG) sử dụng kết hợp tín hiệu chỉ báo, đặt lệnh ATO/ATC đặc thù, so sánh nhiều chỉ số benchmark và xuất báo cáo HTML cao cấp:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import pandas as pd
|
|
60
|
+
from vn_backtest.strategy import Strategy
|
|
61
|
+
from vn_backtest.engine import BacktestEngine
|
|
62
|
+
from vn_backtest.analysis import PerformanceAnalyzer
|
|
63
|
+
from vn_backtest.reporter import ReportGenerator
|
|
64
|
+
|
|
65
|
+
# 1. Định nghĩa chỉ báo Simple Moving Average (SMA)
|
|
66
|
+
def SMA(data, period, column="Close"):
|
|
67
|
+
return data[column].rolling(window=period).mean()
|
|
68
|
+
|
|
69
|
+
# 2. Xây dựng Chiến lược đa tài sản
|
|
70
|
+
class MultiAssetSmaCross(Strategy):
|
|
71
|
+
sma_fast = 10
|
|
72
|
+
sma_slow = 30
|
|
73
|
+
|
|
74
|
+
def init(self):
|
|
75
|
+
self.fast_ma = {}
|
|
76
|
+
self.slow_ma = {}
|
|
77
|
+
|
|
78
|
+
# Đăng ký chỉ báo cho từng mã qua self.I() để tự động kiểm tra Look-Ahead Bias
|
|
79
|
+
for ticker in self.data.keys():
|
|
80
|
+
self.fast_ma[ticker] = self.I(lambda df: SMA(df, self.sma_fast), name=f"Fast_{ticker}")
|
|
81
|
+
self.slow_ma[ticker] = self.I(lambda df: SMA(df, self.sma_slow), name=f"Slow_{ticker}")
|
|
82
|
+
|
|
83
|
+
def next(self):
|
|
84
|
+
idx = self.current_idx
|
|
85
|
+
# Bỏ qua các ngày đầu chưa đủ dữ liệu tính toán chỉ báo chậm
|
|
86
|
+
if idx < self.sma_slow:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
for ticker in self.data.keys():
|
|
90
|
+
# Lấy giá trị chỉ báo hiện tại và hôm qua
|
|
91
|
+
fast_curr = self.fast_ma[ticker].iloc[idx]
|
|
92
|
+
fast_prev = self.fast_ma[ticker].iloc[idx - 1]
|
|
93
|
+
slow_curr = self.slow_ma[ticker].iloc[idx]
|
|
94
|
+
slow_prev = self.slow_ma[ticker].iloc[idx - 1]
|
|
95
|
+
|
|
96
|
+
# Tín hiệu MUA: Đường nhanh cắt lên đường chậm (Golden Cross)
|
|
97
|
+
if fast_prev <= slow_prev and fast_curr > slow_curr:
|
|
98
|
+
# Đặt lệnh MUA ATO (khớp tại Open ngày hôm sau, phân bổ 30% sức mua)
|
|
99
|
+
self.buy(ticker, size=0.3, order_type="ATO")
|
|
100
|
+
|
|
101
|
+
# Tín hiệu BÁN: Đường nhanh cắt xuống đường chậm (Death Cross)
|
|
102
|
+
elif fast_prev >= slow_prev and fast_curr < slow_curr:
|
|
103
|
+
# Đặt lệnh BÁN ATC (khớp tại Close ngày hôm sau, bán toàn bộ vị thế đang nắm giữ)
|
|
104
|
+
if self.positions.get(ticker, 0) > 0:
|
|
105
|
+
self.sell(ticker, order_type="ATC")
|
|
106
|
+
|
|
107
|
+
# 3. Chuẩn bị dữ liệu OHLCV (Giá chia 1000 từ FireAnt/CafeF)
|
|
108
|
+
dates = pd.date_range(start="2023-01-01", periods=100, freq="D")
|
|
109
|
+
stock_data = {
|
|
110
|
+
"FPT": pd.DataFrame({"Open": 80.0, "High": 82.0, "Low": 79.0, "Close": 81.0, "Volume": 100000}, index=dates),
|
|
111
|
+
"HPG": pd.DataFrame({"Open": 25.0, "High": 26.0, "Low": 24.5, "Close": 25.5, "Volume": 150000}, index=dates),
|
|
112
|
+
"MWG": pd.DataFrame({"Open": 40.0, "High": 41.5, "Low": 39.5, "Close": 40.5, "Volume": 80000}, index=dates)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# 4. Chuẩn bị dữ liệu Benchmark để đối chiếu hiệu quả
|
|
116
|
+
benchmarks = {
|
|
117
|
+
"VNINDEX": pd.DataFrame({"Close": 1000.0 + (dates.day * 2)}, index=dates),
|
|
118
|
+
"VN30": pd.DataFrame({"Close": 1010.0 + (dates.day * 1.5)}, index=dates)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# 5. Cấu hình và chạy Backtest Engine
|
|
122
|
+
engine = BacktestEngine(
|
|
123
|
+
data=stock_data,
|
|
124
|
+
strategy_class=MultiAssetSmaCross,
|
|
125
|
+
initial_cash=100_000_000.0, # Khởi đầu với 100 triệu VND
|
|
126
|
+
price_scale=1000.0, # Quy đổi giá đầu vào sang VND thực tế (nhân 1000)
|
|
127
|
+
lot_size=100, # Lô chẵn 100 cổ phiếu
|
|
128
|
+
exchange="hose" # Áp dụng quy tắc sàn HOSE
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
results = engine.run()
|
|
132
|
+
equity_df = results["equity_curve"]
|
|
133
|
+
trades_df = results["trades"]
|
|
134
|
+
|
|
135
|
+
# 6. Tính toán thống kê hiệu suất chuyên sâu
|
|
136
|
+
metrics = PerformanceAnalyzer.calculate_metrics(
|
|
137
|
+
equity_curve=equity_df,
|
|
138
|
+
trades=trades_df,
|
|
139
|
+
benchmark_data=benchmarks,
|
|
140
|
+
initial_cash=100_000_000.0,
|
|
141
|
+
risk_free_rate=0.04 # Lãi suất phi rủi ro 4%/năm tại Việt Nam
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# 7. Sinh báo cáo HTML cao cấp dạng tương tác trực quan
|
|
145
|
+
reporter = ReportGenerator(output_dir="reports")
|
|
146
|
+
report_path = reporter.generate_report(
|
|
147
|
+
metrics=metrics,
|
|
148
|
+
equity_curve=equity_df,
|
|
149
|
+
trades=trades_df,
|
|
150
|
+
stock_data=stock_data,
|
|
151
|
+
ticker="FPT,HPG,MWG",
|
|
152
|
+
strategy_name="MultiAssetSmaCross",
|
|
153
|
+
benchmark_data=benchmarks,
|
|
154
|
+
filename="multi_asset_report.html",
|
|
155
|
+
benchmark_symbol="VNINDEX"
|
|
156
|
+
)
|
|
157
|
+
print(f"Backtest hoàn tất! Báo cáo đã được xuất ra tại: {report_path}")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🛠️ Tối Ưu Hóa Tham Số (Parameter Optimization)
|
|
163
|
+
|
|
164
|
+
Bạn có thể tìm kiếm tổ hợp tham số tối ưu bằng cách sử dụng `ParameterOptimizer` hỗ trợ xử lý song song đa tiến trình (multiprocessing):
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from vn_backtest.optimizer import ParameterOptimizer
|
|
168
|
+
|
|
169
|
+
param_grid = {
|
|
170
|
+
"sma_fast": [5, 10, 15],
|
|
171
|
+
"sma_slow": [20, 30, 50]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
optimizer = ParameterOptimizer(
|
|
175
|
+
data=stock_data,
|
|
176
|
+
strategy_class=MultiAssetSmaCross,
|
|
177
|
+
param_grid=param_grid,
|
|
178
|
+
initial_cash=100_000_000.0,
|
|
179
|
+
exchange="hose",
|
|
180
|
+
n_jobs=-1 # Chạy tối đa toàn bộ nhân CPU của máy
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Chạy Grid Search và sắp xếp kết quả theo tỷ số Sharpe giảm dần
|
|
184
|
+
opt_results = optimizer.run_optimization(sort_by="sharpe_ratio", ascending=False)
|
|
185
|
+
print(opt_results.head())
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 📖 Chi Tiết Cấu Hình `BacktestEngine`
|
|
191
|
+
|
|
192
|
+
Dưới đây là mô tả chi tiết toàn bộ các tham số khởi tạo của lớp `BacktestEngine`:
|
|
193
|
+
|
|
194
|
+
| Tham Số | Kiểu Dữ Liệu | Mặc Định | Mô Tả |
|
|
195
|
+
| :--- | :--- | :--- | :--- |
|
|
196
|
+
| `data` | `DataFrame` \| `Dict[str, DataFrame]` | *(Bắt buộc)* | Dữ liệu lịch sử giá. Phải chứa cột `Open`, `High`, `Low`, `Close`, `Volume`. Index định dạng `DatetimeIndex`. |
|
|
197
|
+
| `strategy_class` | `Type[Strategy]` | *(Bắt buộc)* | Lớp chiến lược giao dịch kế thừa từ `Strategy`. |
|
|
198
|
+
| `corporate_actions` | `Dict[str, DataFrame]` | `None` | Dữ liệu sự kiện doanh nghiệp của các mã chứng khoán (Cổ tức, phát hành thêm). |
|
|
199
|
+
| `initial_cash` | `float` | `100,000,000.0` | Số tiền mặt khởi đầu cho tài khoản backtest (đơn vị: VND). |
|
|
200
|
+
| `buy_fee` | `float` | `0.0015` | Tỷ lệ phí giao dịch mua chứng khoán (ví dụ: `0.0015` = 0.15% giá trị giao dịch). |
|
|
201
|
+
| `sell_fee` | `float` | `0.0015` | Tỷ lệ phí giao dịch bán chứng khoán. |
|
|
202
|
+
| `sell_tax` | `float` | `0.001` | Tỷ lệ thuế thu nhập cá nhân khi bán chứng khoán tại Việt Nam (mặc định: 0.1%). |
|
|
203
|
+
| `settlement_days` | `int` | `2` | Chu kỳ thanh toán cổ phiếu mặc định (T+2). Sẽ tự động áp dụng chu kỳ lịch sử nếu bật `dynamic_rules`. |
|
|
204
|
+
| `lot_size` | `int` | `100` | Quy định lô giao dịch mặc định. Tự động chuyển đổi theo lịch sử sàn nếu bật `dynamic_rules`. |
|
|
205
|
+
| `exchange` | `str` \| `Dict[str, str]` | `"hose"` | Sàn giao dịch của cổ phiếu để tính Trần/Sàn và bước giá (`hose`, `hnx`, `upcom`). Nhận vào chuỗi hoặc một dict ánh xạ từng mã. |
|
|
206
|
+
| `execution_at` | `str` | `"open"` | Thời điểm thực thi giao dịch liên tục cho phiên kế tiếp (`"open"`: Khớp tại Open, `"close"`: Khớp tại Close). |
|
|
207
|
+
| `restrict_ceiling_buy` | `bool` | `True` | Nếu bật, lệnh mua sẽ bị từ chối/hủy nếu giá thị trường chạm giá trần (trắng bên bán). |
|
|
208
|
+
| `restrict_floor_sell` | `bool` | `True` | Nếu bật, lệnh bán sẽ bị từ chối/hủy nếu giá thị trường chạm giá sàn (trắng bên mua). |
|
|
209
|
+
| `slippage` | `float` | `0.0` | Tỷ lệ trượt giá áp dụng cho lệnh liên tục (ví dụ: `0.001` = 0.1% trượt giá). ATO/ATC không áp dụng. |
|
|
210
|
+
| `dynamic_rules` | `bool` | `True` | Kích hoạt cơ chế luật giao dịch thay đổi theo lịch sử thực tế của TTCK Việt Nam. |
|
|
211
|
+
| `advance_interest_rate` | `float` | `0.12` | Lãi suất ứng trước tiền bán năm (mặc định: 12%/năm) để mua cổ phiếu trước ngày tiền về. |
|
|
212
|
+
| `auto_close_at_end` | `bool` | `True` | Tự động tất toán (bán) toàn bộ cổ phiếu nắm giữ ở phiên cuối cùng của chuỗi dữ liệu. |
|
|
213
|
+
| `allow_odd_lot` | `bool` | `False` | Cho phép giao dịch lô lẻ dưới 100 cổ phiếu (ví dụ: 1-99). |
|
|
214
|
+
| `max_volume_ratio` | `float` | `None` | Giới hạn khối lượng khớp tối đa theo tỷ lệ thanh khoản ngày hôm đó (ví dụ: `0.01` nghĩa là chỉ được khớp tối đa 1% tổng Volume giao dịch của ngày). |
|
|
215
|
+
| `adjust_corporate_actions` | `bool` | `False` | Bật mô phỏng điều chỉnh giá/số lượng do cổ tức/quyền mua của cổ phiếu chưa được điều chỉnh (raw data). |
|
|
216
|
+
| `force_adjusted` | `bool` | `None` | Ép buộc trạng thái dữ liệu đầu vào. `True`: Đã điều chỉnh giá (Adj_Close), `False`: Giá gốc. |
|
|
217
|
+
| `margin_ratio` | `float` | `1.0` | Tỷ lệ ký quỹ tối thiểu. `1.0`: Không dùng margin. `0.5`: Margin 1:1 (Sức mua gấp đôi tài sản ròng). |
|
|
218
|
+
| `margin_interest_rate` | `float` | `0.13` | Lãi suất vay ký quỹ (margin loan) năm (mặc định: 13%/năm). |
|
|
219
|
+
| `margin_maintenance_ratio` | `float` | `0.35` | Tỷ lệ duy trì ký quỹ duy trì tối thiểu. Khi giá trị tài sản ròng / tổng tài sản dưới ngưỡng này sẽ kích hoạt Margin Call/Force Sell. |
|
|
220
|
+
| `ticker` | `str` | `None` | Tên mã chứng khoán nếu `data` truyền vào là một DataFrame duy nhất. |
|
|
221
|
+
| `strategy_params` | `Dict[str, Any]` | `None` | Bộ tham số truyền trực tiếp vào lớp chiến lược tại thời điểm khởi tạo. |
|
|
222
|
+
| `market_impact_coef` | `float` | `0.0` | Hệ số tác động thị trường dùng để tính toán trượt giá động dựa trên tỷ lệ khối lượng đặt lệnh so với Volume ngày. |
|
|
223
|
+
| `rights_listing_delay` | `int` | `90` | Số ngày chờ niêm yết cổ phiếu thưởng/quyền mua về tài khoản giao dịch (mặc định: 90 ngày). |
|
|
224
|
+
| `dividend_tax_rate` | `float` | `0.05` | Tỷ lệ khấu trừ thuế thu nhập cá nhân đối với cổ tức nhận được (mặc định: 5%). |
|
|
225
|
+
| `price_scale` | `float` | `1.0` | Tỷ lệ điều chỉnh đơn vị giá. Đặt `1000.0` nếu dữ liệu đầu vào đã bị chia cho 1000 (ví dụ: giá hiển thị 85.5 thay vì 85,500 VND). |
|
|
226
|
+
| `listing_dates` | `Dict[str, Timestamp]` | `None` | Ngày niêm yết chính thức của các mã chứng khoán dùng để kích hoạt quy định biên độ giá đặc biệt trong ngày đầu tiên lên sàn (Listing Day). |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 📂 Cấu Trúc Thư Mục Dự Án
|
|
231
|
+
|
|
232
|
+
```plaintext
|
|
233
|
+
vn-backtest/
|
|
234
|
+
├── src/
|
|
235
|
+
│ └── vn_backtest/
|
|
236
|
+
│ ├── __init__.py # Export các lớp và hàm chính
|
|
237
|
+
│ ├── engine.py # Core Engine mô phỏng khớp lệnh, T+, margin, dòng tiền, sự kiện DN
|
|
238
|
+
│ ├── strategy.py # Lớp Strategy cơ sở và đăng ký chỉ báo
|
|
239
|
+
│ ├── trading_rules.py # Quản lý bước giá, lô giao dịch, trần/sàn và chu kỳ T+ lịch sử
|
|
240
|
+
│ ├── analysis.py # Phân tích hiệu suất, đo lường rủi ro và đối chiếu giao dịch FIFO
|
|
241
|
+
│ ├── optimizer.py # Grid Search tối ưu hóa tham số đa luồng (multiprocessing)
|
|
242
|
+
│ └── reporter.py # Sinh báo cáo HTML tương tác (Plotly, Jinja2, Stacked Allocation)
|
|
243
|
+
├── pyproject.toml # Cấu hình cài đặt package và quản lý phụ thuộc
|
|
244
|
+
└── README.md # Tài liệu hướng dẫn sử dụng
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 🛡️ Chi Tiết Cơ Chế Chặn Look-Ahead Bias
|
|
250
|
+
|
|
251
|
+
Look-Ahead Bias là lỗi phổ biến nhất khiến kết quả backtest của bạn "đẹp như mơ" nhưng khi giao dịch thực tế lại thua lỗ vì thuật toán vô tình sử dụng dữ liệu giá của tương lai để ra quyết định hôm nay.
|
|
252
|
+
|
|
253
|
+
`vn-backtest` cung cấp **2 lớp bảo vệ**:
|
|
254
|
+
|
|
255
|
+
1. **Kiểm tra tĩnh trong `self.I()`:**
|
|
256
|
+
Khi bạn đăng ký một chỉ báo qua `self.I()`, hệ thống sẽ tự động chạy hàm tính toán chỉ báo đó trên 2 lát cắt dữ liệu: toàn bộ dữ liệu lịch sử và dữ liệu bị cắt ngắn (sliced) tại một ngày ngẫu nhiên trong quá khứ. Nếu giá trị chỉ báo tại ngày đó khác nhau, hệ thống lập tức ném ra **cảnh báo đỏ** kèm theo giá trị sai lệch để bạn biết chỉ báo của mình đang bị rò rỉ thông tin tương lai.
|
|
257
|
+
2. **Truy cập an toàn qua `self.safe_data`:**
|
|
258
|
+
Trong hàm `next()`, thay vì thao tác trực tiếp trên `self.data` (chứa toàn bộ dữ liệu lịch sử), bạn có thể gọi `self.safe_data`. Thuộc tính này sẽ trả về một DataFrame chứa dữ liệu được cắt nghiêm ngặt từ ngày đầu tiên đến ngày giao dịch hiện tại (`current_idx`), đảm bảo logic giao dịch của bạn tuyệt đối không thể nhìn trước tương lai.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## ⚠️ Khuyến Cáo Dữ Liệu (Survival Bias)
|
|
263
|
+
|
|
264
|
+
Kết quả backtest của bất kỳ hệ thống nào cũng có nguy cơ bị ảnh hưởng bởi **Thiên lệch sống sót (Survivorship Bias)** nếu bạn chỉ chạy kiểm thử trên danh sách các mã cổ phiếu đang niêm yết hiện tại.
|
|
265
|
+
|
|
266
|
+
Để kết quả phản ánh khách quan nhất:
|
|
267
|
+
* Hãy bổ sung dữ liệu lịch sử của các mã cổ phiếu đã bị hủy niêm yết trong quá khứ.
|
|
268
|
+
* Backtest Engine hỗ trợ cơ chế tự động đóng toàn bộ vị thế của mã chứng khoán tại ngày giao dịch cuối cùng trước khi bị hủy niêm yết để tính toán đúng lợi nhuận thực tế.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 🤝 Đóng Góp Ý Kiến (Contributing)
|
|
273
|
+
|
|
274
|
+
Mọi đóng góp nhằm cải thiện thuật toán khớp lệnh định kỳ/liên tục, mở rộng tính năng phân tích rủi ro hoặc báo cáo lỗi phát sinh đều được hoan nghênh! Hãy mở một Issue hoặc gửi Pull Request trực tiếp trên kho lưu trữ Github của dự án.
|
|
275
|
+
|
|
276
|
+
## 📄 Giấy Phép (License)
|
|
277
|
+
|
|
278
|
+
Dự án được phát hành và phân phối theo các điều khoản của giấy phép **MIT**. Xem tệp `LICENSE` để biết thêm chi tiết.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vn-backtest"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Hunter Do", email="phongvu2010@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
description = "A powerful Backtesting Framework tailored strictly for the Vietnam Stock Market (HOSE, HNX, UPCOM)."
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.12"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pandas>=1.5.0",
|
|
26
|
+
"numpy>=1.21.0",
|
|
27
|
+
"plotly>=5.10.0",
|
|
28
|
+
"jinja2>=3.1.0"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
"Homepage" = "https://github.com/phongvu2010/backtest-framework"
|
|
33
|
+
"Bug Tracker" = "https://github.com/phongvu2010/backtest-framework/issues"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
optimize = ["optuna"]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.package-data]
|
|
39
|
+
"vn_backtest" = ["templates/*.html", "templates/*.css"]
|