Select and hold Vietnamese equities by Price-to-Earnings ratio and dividend yield, rebalanced monthly.
This project uses the Price-to-Earnings (P/E) ratio and the dividend yield (DY) to select and hold stocks in the Vietnamese stock market. The portfolio is rebalanced on the first day of each month; if that day is a holiday, rebalancing is carried out on the next trading day.
Developed and evaluated end-to-end on data from the Algotrade database over an in-sample period of 2019-01-01 → 2022-01-01 and an out-of-sample period of 2022-01-01 → 2024-01-01, following the 9-step Development Process and the Plutus Reproducibility Standard. On the in-sample period it reaches a Sharpe ratio of 1.2971 and a holding-period return of 104.9%; on the out-of-sample period it reaches a Sharpe ratio of -0.3497 and a holding-period return of -3.171%. Every reported number is reproducible in an isolated Docker container via plutus-verify against a committed groundtruth baseline (see Implementation & Reproducibility).
In value investing, one common approach to stock selection involves identifying undervalued companies with strong income potential. This strategy leverages two key financial metrics: the Price-to-Earnings (P/E) ratio and the dividend yield. The P/E ratio helps assess whether a stock is trading at a reasonable price relative to its earnings, while the dividend yield highlights its income-generating potential.
The core idea is to select stocks with a low P/E ratio and a high dividend yield, as these stocks may represent undervalued opportunities that also offer steady cash returns. This method is particularly effective in mature or stable markets, such as the Vietnamese stock market, where dividend-paying companies are often more established and financially sound.
We filter and maintain a portfolio of stocks with a price-to-earnings (P/E) ratio within the range of (0, 15) and a dividend yield (DY) greater than 0.01. The dividend yield is calculated as:
- DY = DPS / Price
where DPS denotes dividends per share. Existing holdings are sold before purchasing new stocks at each rebalancing period. The strategy does not yet account for trading volume; it is assumed that all stocks can be bought immediately without liquidity constraints.
- Source: Algotrade database (daily close prices, financial info for EPS/DPS, and the VNINDEX benchmark), shipped as processed CSVs via Google Drive.
- Period: in-sample 2019-01-01 → 2022-01-01; out-of-sample 2022-01-01 → 2024-01-01.
- Fees: each buy and each sell is charged a 0.035% fee. Starting capital is 25,000,000 VND.
The daily close price and financial data (Net Profit After Tax Attributable to Shareholders, outstanding shares, dividends paid) are extracted, used to compute P/E and DPS, and stored as CSV files to reduce the time required for subsequent steps. The data lands under data/is/ (in-sample) and data/os/ (out-of-sample).
Option 1 — Download from Google Drive (no database credentials needed).
Download from this Google Drive folder. Place the data/ folder at the repo root:
data
├── is
│ ├── pe_dps.csv
│ └── vnindex.csv
└── os
├── pe_dps.csv
└── vnindex.csv
Option 2 — Collect from the database. Requires read access to the Algotrade database. Copy .env.example to .env and fill in the connection variables the loader reads (HOST, PORT, DATABASE, USER_DB, PASSWORD), then run:
psb-load-dataAt each monthly rebalancing date (the first trading day of the month):
- Compute, for every eligible stock, its P/E ratio and dividend yield (DY = DPS / Price) from the latest available financial and price data.
- Select the stocks whose P/E falls inside the configured range and whose DY exceeds the configured lower bound (in-sample defaults: P/E in [0, 15], DY ≥ 0.01).
- Liquidate all current holdings, then allocate capital across the newly selected stocks (lot-rounded), holding until the next rebalancing date.
Trading volume / liquidity is not modelled — all selected stocks are assumed immediately tradable.
The rules are evaluated with the following metrics, which are also the expected values verified by plutus check:
- Sharpe Ratio
- Information Ratio
- Sortino Ratio
- Maximum Drawdown (MDD)
- HPR (%) and Excess HPR (%) vs the VNINDEX benchmark
- Monthly return (%), Excess monthly return (%), and Annual return (%)
A risk-free rate of 6% per annum (≈ 0.023% per day) is used as the benchmark for the Sharpe and Sortino ratios.
The pipeline is packaged as proto-smart-beta (a src/-layout Python package) with console-script entry points — psb-load-data (data preparation), psb-backtest (in-sample), psb-optimize (optimization), and psb-evaluate (out-of-sample); each step below shows its own command.
uv sync # create the env from the committed uv.lock and install the packageThe environment is pinned by pyproject.toml + a committed uv.lock, so uv sync --frozen restores it exactly and installs the proto-smart-beta package (which is what makes the psb-* console scripts available). Database credentials are only needed for Option 2 of Data Preparation; the Google Drive path needs none.
This repo ships a .plutus/manifest.yaml declaring the environment, data sources, steps, and expected metrics. Reproduce every result in an isolated Docker container with plutus-verify 0.5.1, installed from the public release wheel:
# Requires Docker running and uv installed
uv venv .plutus-venv && source .plutus-venv/bin/activate
uv pip install "plutus-verify[runner] @ https://github.com/algotrade-plutus/plutus-verify/releases/download/v0.5.1/plutus_verify-0.5.1-py3-none-any.whl"
plutus check . # build -> run each step in-container -> compare vs baselineThe [runner] extra brings Docker orchestration and gdown (used to fetch the Google Drive data source). plutus check builds the image from the locked environment, installs the proto-smart-beta package (env.install_project), downloads the data source into a cache, runs each step in-container via its console script, and compares the emitted metrics and charts against the committed .plutus/expected/ baseline. Exit code 0 = reproduced (within tolerance), 1 = partial, 2 = failed.
Parameters (period, fees, capital, P/E and DY ranges) are specified in parameter/backtesting_parameter.json. Run:
psb-backtestCharts are written to result/backtest/.
| Metric | Value |
|---|---|
| Sharpe Ratio | 1.2971 |
| Information Ratio | 0.4716 |
| Sortino Ratio | 1.7298 |
| Maximum Drawdown (MDD) | -0.2828 |
| HPR (%) | 104.9 |
| Excess HPR (%) | 35.98 |
| Monthly return (%) | 2.2617 |
| Excess monthly return (%) | 0.5012 |
| Annual return (%) | 27.09 |
Holding-period return vs the VNINDEX benchmark — result/backtest/hpr.svg
Drawdown over time — result/backtest/drawdown.svg
The search space is configured in parameter/optimization_parameter.json (P/E upper bound and DY lower bound), optimized over 100 Optuna trials to maximize the in-sample Sharpe ratio. A fixed random seed reproduces the search. Run:
psb-optimizeThe optimized parameters are written to parameter/optimized_parameter.json. With seed 2024, the current optimum is:
{
"pe": [0, 10],
"dy": [0.056272982721535775, 1e6]
}Using the optimized parameters from Step 5, evaluate on the out-of-sample period:
psb-evaluateCharts are written to result/optimization/.
| Metric | Value |
|---|---|
| Sharpe Ratio | -0.3497 |
| Information Ratio | 0.7442 |
| Sortino Ratio | -0.4363 |
| Maximum Drawdown (MDD) | -0.4604 |
| HPR (%) | -3.171 |
| Excess HPR (%) | 18.27 |
| Monthly return (%) | -0.1905 |
| Excess monthly return (%) | 0.8779 |
| Annual return (%) | -4.244 |
Holding-period return vs the VNINDEX benchmark — result/optimization/hpr.svg
Drawdown over time — result/optimization/drawdown.svg
[1] ALGOTRADE, Algorithmic Trading Theory and Practice - A Practical Guide with Applications on the Vietnamese Stock Market, 1st ed. DIMI BOOK, 2023, pp. 64–67. Accessed: Apr. 30, 2025. [Online]. Available: Link
[2] ALGOTRADE, "Weighting Methods Used in Smart-Beta Strategy," Website, Jul. 10, 2024. Available: Link (accessed Apr. 30, 2025).