dfm-python 0.6.1__tar.gz → 0.6.11__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.
- {dfm_python-0.6.1 → dfm_python-0.6.11}/PKG-INFO +1 -1
- {dfm_python-0.6.1 → dfm_python-0.6.11}/config/dfm_finance.yaml +1 -1
- {dfm_python-0.6.1 → dfm_python-0.6.11}/config/dfm_macro.yaml +1 -1
- dfm_python-0.6.11/failure_reports/finance_failure_20260127_183841.json +22 -0
- dfm_python-0.6.11/failure_reports/macro_failure_20260127_183842.json +15 -0
- dfm_python-0.6.11/models/dfm_finance.pkl +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/models/dfm_macro.pkl +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/pyproject.toml +1 -1
- dfm_python-0.6.11/tutorial/tutorial_exchange_dfm.py +205 -0
- dfm_python-0.6.11/tutorial/tutorial_finance_dfm.py +234 -0
- dfm_python-0.6.11/tutorial/tutorial_macro_dfm.py +205 -0
- dfm_python-0.6.1/tutorial/tutorial_exchange_dfm.py +0 -57
- dfm_python-0.6.1/tutorial/tutorial_finance_dfm.py +0 -84
- dfm_python-0.6.1/tutorial/tutorial_macro_dfm.py +0 -56
- {dfm_python-0.6.1 → dfm_python-0.6.11}/.cursor/agents/test-runner.md +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/.git +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/.gitignore +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/.python-version +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/=0.11.0 +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/MANIFEST.in +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/config/ddfm_exchange.yaml +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/config/ddfm_finance.yaml +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/config/ddfm_macro.yaml +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/config/dfm_exchange.yaml +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/data/exchange_rate.csv +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/data/finance.csv +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/data/macro.csv +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/models/dfm_exchange.pkl +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/adapter.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/constants.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/block.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/model.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/params.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/results.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/types.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/ddfm_dataset.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/dfm_dataset.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/time.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/base.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/linear.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/mlp.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/base.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/pca.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/simple_autoencoder.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/functional/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/functional/em.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/dfm_logger.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/inference_logger.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/logger.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/train_logger.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/base.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/ddfm/ddfm.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/dfm.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/initialization.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/mixed_freq.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/tent.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/builder.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/estimator.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/stability.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/statistic.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/validator.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/ssm/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/ssm/kalman.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/checkpoint.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/errors.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/helper.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/loss.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/metric.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/misc.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/validation.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/test_block.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/test_model.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/test_results.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_adapter.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_constants.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_tent_weights_config.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_types.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/conftest.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/dataset/test_ddfm_dataset.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/dataset/test_dfm_dataset.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/decoder/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/decoder/test_linear.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/decoder/test_mlp.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/test_base.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/test_pca.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/test_simple_autoencoder.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_em.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_em_correctness.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_em_properties.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_invariance.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_macro_overflow.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/test_inference_logger.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/test_logger.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/test_train_logger.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/models/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/models/test_ddfm.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/models/test_dfm.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_builder.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_cov_rowvar_regression.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_estimator.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_initialization.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_stability.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_validator.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/ssm/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/ssm/test_kalman.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/ssm/test_kalman_correctness.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/__init__.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_errors.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_helper.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_metric.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_misc.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/tutorial/tutorial_exchange_ddfm.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/tutorial/tutorial_finance_ddfm.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/tutorial/tutorial_macro_ddfm.py +0 -0
- {dfm_python-0.6.1 → dfm_python-0.6.11}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dfm-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.11
|
|
4
4
|
Summary: Dynamic Factor Model (DFM) estimation and forecasting in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/yourusername/dfm-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"experiment": "finance",
|
|
3
|
+
"step": "Step 2: Creating Dataset",
|
|
4
|
+
"timestamp": "20260127_183841",
|
|
5
|
+
"error_type": "AttributeError",
|
|
6
|
+
"error_message": "'DFMDataset' object has no attribute 'obs_dim'",
|
|
7
|
+
"traceback": "Traceback (most recent call last):\n File \"/data/nowcasting-kr/dfm-python/tutorial/tutorial_finance_dfm.py\", line 150, in main\n print(f\" Dataset created: obs_dim={dataset.obs_dim}, state_dim={dataset.state_dim if hasattr(dataset, 'state_dim') else 'N/A'}\")\n ^^^^^^^^^^^^^^^\nAttributeError: 'DFMDataset' object has no attribute 'obs_dim'\n",
|
|
8
|
+
"context": {
|
|
9
|
+
"config_max_iter": 12,
|
|
10
|
+
"config_blocks": {
|
|
11
|
+
"Block_ALL": null,
|
|
12
|
+
"Block_EI": null,
|
|
13
|
+
"Block_MD": null,
|
|
14
|
+
"Block_PSV": null
|
|
15
|
+
},
|
|
16
|
+
"data_shape": [
|
|
17
|
+
1000,
|
|
18
|
+
99
|
|
19
|
+
],
|
|
20
|
+
"filtered_series_count": 27
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"experiment": "macro",
|
|
3
|
+
"step": "Step 2: Creating Dataset",
|
|
4
|
+
"timestamp": "20260127_183842",
|
|
5
|
+
"error_type": "AttributeError",
|
|
6
|
+
"error_message": "'DFMDataset' object has no attribute 'obs_dim'",
|
|
7
|
+
"traceback": "Traceback (most recent call last):\n File \"/data/nowcasting-kr/dfm-python/tutorial/tutorial_macro_dfm.py\", line 123, in main\n print(f\" Dataset created: obs_dim={dataset.obs_dim}, state_dim={dataset.state_dim if hasattr(dataset, 'state_dim') else 'N/A'}\")\n ^^^^^^^^^^^^^^^\nAttributeError: 'DFMDataset' object has no attribute 'obs_dim'\n",
|
|
8
|
+
"context": {
|
|
9
|
+
"config_max_iter": 12,
|
|
10
|
+
"data_shape": [
|
|
11
|
+
2538,
|
|
12
|
+
15
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Tutorial: DFM for Exchange Data
|
|
2
|
+
|
|
3
|
+
This tutorial demonstrates the complete workflow for training and prediction
|
|
4
|
+
using exchange rate data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import traceback
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
project_root = Path(__file__).parent.parent
|
|
14
|
+
sys.path.insert(0, str(project_root / "src"))
|
|
15
|
+
|
|
16
|
+
import pandas as pd
|
|
17
|
+
import numpy as np
|
|
18
|
+
from sklearn.preprocessing import StandardScaler
|
|
19
|
+
from dfm_python import DFM, DFMDataset
|
|
20
|
+
from dfm_python.config import DFMConfig
|
|
21
|
+
import hydra
|
|
22
|
+
from omegaconf import DictConfig, OmegaConf
|
|
23
|
+
|
|
24
|
+
def save_failure_report(step: str, error: Exception, context: dict, project_root: Path):
|
|
25
|
+
"""Save detailed failure report to file."""
|
|
26
|
+
reports_dir = project_root / "failure_reports"
|
|
27
|
+
reports_dir.mkdir(exist_ok=True)
|
|
28
|
+
|
|
29
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
30
|
+
report_file = reports_dir / f"exchange_failure_{timestamp}.json"
|
|
31
|
+
|
|
32
|
+
report = {
|
|
33
|
+
"experiment": "exchange",
|
|
34
|
+
"step": step,
|
|
35
|
+
"timestamp": timestamp,
|
|
36
|
+
"error_type": type(error).__name__,
|
|
37
|
+
"error_message": str(error),
|
|
38
|
+
"traceback": traceback.format_exc(),
|
|
39
|
+
"context": context
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
with open(report_file, 'w') as f:
|
|
43
|
+
json.dump(report, f, indent=2, default=str)
|
|
44
|
+
|
|
45
|
+
print(f"\n[FAILURE] Error report saved to: {report_file}")
|
|
46
|
+
return report_file
|
|
47
|
+
|
|
48
|
+
def check_model_state(model, step: str):
|
|
49
|
+
"""Check model state for numerical issues and return diagnostics."""
|
|
50
|
+
diagnostics = {}
|
|
51
|
+
|
|
52
|
+
if model is None:
|
|
53
|
+
return diagnostics
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
if hasattr(model, 'training_state') and model.training_state is not None:
|
|
57
|
+
state = model.training_state
|
|
58
|
+
diagnostics['training_state'] = {}
|
|
59
|
+
|
|
60
|
+
for param_name in ['A', 'C', 'Q', 'R', 'V_0']:
|
|
61
|
+
if hasattr(state, param_name):
|
|
62
|
+
param = getattr(state, param_name)
|
|
63
|
+
if param is not None:
|
|
64
|
+
diagnostics['training_state'][param_name] = {
|
|
65
|
+
'shape': param.shape if hasattr(param, 'shape') else None,
|
|
66
|
+
'has_nan': bool(np.any(np.isnan(param))) if isinstance(param, np.ndarray) else None,
|
|
67
|
+
'has_inf': bool(np.any(np.isinf(param))) if isinstance(param, np.ndarray) else None,
|
|
68
|
+
'min': float(np.nanmin(param)) if isinstance(param, np.ndarray) else None,
|
|
69
|
+
'max': float(np.nanmax(param)) if isinstance(param, np.ndarray) else None,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if hasattr(model, 'result') and model.result is not None:
|
|
73
|
+
result = model.result
|
|
74
|
+
diagnostics['result'] = {
|
|
75
|
+
'converged': result.converged if hasattr(result, 'converged') else None,
|
|
76
|
+
'num_iter': result.num_iter if hasattr(result, 'num_iter') else None,
|
|
77
|
+
'loglik': float(result.loglik) if hasattr(result, 'loglik') and result.loglik is not None else None,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if hasattr(model, 'data_processed') and model.data_processed is not None:
|
|
81
|
+
data = model.data_processed
|
|
82
|
+
diagnostics['data_processed'] = {
|
|
83
|
+
'shape': data.shape if hasattr(data, 'shape') else None,
|
|
84
|
+
'has_nan': bool(np.any(np.isnan(data))) if isinstance(data, np.ndarray) else None,
|
|
85
|
+
'has_inf': bool(np.any(np.isinf(data))) if isinstance(data, np.ndarray) else None,
|
|
86
|
+
}
|
|
87
|
+
except Exception as e:
|
|
88
|
+
diagnostics['diagnostic_error'] = f"Failed to collect diagnostics: {str(e)}"
|
|
89
|
+
|
|
90
|
+
return diagnostics
|
|
91
|
+
|
|
92
|
+
@hydra.main(version_base=None, config_path="../config", config_name="dfm_exchange")
|
|
93
|
+
def main(cfg: DictConfig) -> None:
|
|
94
|
+
print("=" * 80)
|
|
95
|
+
print("DFM Tutorial: Exchange Data")
|
|
96
|
+
print("=" * 80)
|
|
97
|
+
|
|
98
|
+
start_time = datetime.now()
|
|
99
|
+
model = None
|
|
100
|
+
dataset = None
|
|
101
|
+
config = None
|
|
102
|
+
df = None
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
print("\n[Step 1] Loading data...")
|
|
106
|
+
try:
|
|
107
|
+
df = pd.read_csv(project_root / "data" / "exchange_rate.csv")
|
|
108
|
+
if "date" in df.columns:
|
|
109
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
110
|
+
print(f" Data shape: {df.shape}")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
context = {
|
|
113
|
+
"data_shape": df.shape if df is not None else None,
|
|
114
|
+
"data_columns": list(df.columns) if df is not None else None,
|
|
115
|
+
}
|
|
116
|
+
save_failure_report("Step 1: Loading data", e, context, project_root)
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
print("\n[Step 2] Creating Dataset...")
|
|
121
|
+
config = DFMConfig.from_dict(OmegaConf.to_container(cfg, resolve=True))
|
|
122
|
+
dataset = DFMDataset(config=config, data=df, time_index="date")
|
|
123
|
+
obs_dim = len(dataset.variables.columns)
|
|
124
|
+
print(f" Dataset created: obs_dim={obs_dim}")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
context = {
|
|
127
|
+
"config_max_iter": cfg.get('max_iter', None),
|
|
128
|
+
"data_shape": df.shape if df is not None else None,
|
|
129
|
+
}
|
|
130
|
+
save_failure_report("Step 2: Creating Dataset", e, context, project_root)
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
print("\n[Step 3] Training DFM model...")
|
|
135
|
+
model = DFM(dataset=dataset, config=config, scaler=StandardScaler())
|
|
136
|
+
model.fit()
|
|
137
|
+
|
|
138
|
+
result = model.result
|
|
139
|
+
print(f" Converged: {result.converged}, Iterations: {result.num_iter}")
|
|
140
|
+
|
|
141
|
+
# Check for numerical issues after training
|
|
142
|
+
diagnostics = check_model_state(model, "after_training")
|
|
143
|
+
if diagnostics.get('training_state'):
|
|
144
|
+
for param_name, param_info in diagnostics['training_state'].items():
|
|
145
|
+
if param_info.get('has_nan') or param_info.get('has_inf'):
|
|
146
|
+
print(f" WARNING: {param_name} contains NaN or Inf!")
|
|
147
|
+
except Exception as e:
|
|
148
|
+
context = {
|
|
149
|
+
"config_max_iter": cfg.get('max_iter', None),
|
|
150
|
+
"dataset_obs_dim": dataset.obs_dim if dataset is not None else None,
|
|
151
|
+
"elapsed_time": str(datetime.now() - start_time),
|
|
152
|
+
}
|
|
153
|
+
diagnostics = check_model_state(model, "during_training")
|
|
154
|
+
context.update(diagnostics)
|
|
155
|
+
save_failure_report("Step 3: Training DFM model", e, context, project_root)
|
|
156
|
+
raise
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
print("\n[Step 4] Making predictions...")
|
|
160
|
+
X_forecast, Z_forecast = model.predict(horizon=6)
|
|
161
|
+
print(f" Forecast shape: {X_forecast.shape}")
|
|
162
|
+
|
|
163
|
+
# Check forecast for issues
|
|
164
|
+
if np.any(np.isnan(X_forecast)) or np.any(np.isinf(X_forecast)):
|
|
165
|
+
print(f" WARNING: Forecast contains NaN or Inf!")
|
|
166
|
+
except Exception as e:
|
|
167
|
+
context = {
|
|
168
|
+
"result_converged": result.converged if 'result' in locals() else None,
|
|
169
|
+
"result_iterations": result.num_iter if 'result' in locals() else None,
|
|
170
|
+
}
|
|
171
|
+
diagnostics = check_model_state(model, "during_prediction")
|
|
172
|
+
context.update(diagnostics)
|
|
173
|
+
save_failure_report("Step 4: Making predictions", e, context, project_root)
|
|
174
|
+
raise
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
print("\n[Step 5] Saving model...")
|
|
178
|
+
model_path = project_root / "models" / "dfm_exchange.pkl"
|
|
179
|
+
model_path.parent.mkdir(exist_ok=True)
|
|
180
|
+
model.save(model_path)
|
|
181
|
+
print(f" Model saved to: {model_path}")
|
|
182
|
+
except Exception as e:
|
|
183
|
+
context = {
|
|
184
|
+
"model_path": str(model_path) if 'model_path' in locals() else None,
|
|
185
|
+
"result_converged": result.converged if 'result' in locals() else None,
|
|
186
|
+
}
|
|
187
|
+
diagnostics = check_model_state(model, "during_saving")
|
|
188
|
+
context.update(diagnostics)
|
|
189
|
+
save_failure_report("Step 5: Saving model", e, context, project_root)
|
|
190
|
+
raise
|
|
191
|
+
|
|
192
|
+
print("\n" + result.summary())
|
|
193
|
+
|
|
194
|
+
elapsed = datetime.now() - start_time
|
|
195
|
+
print(f"\n[SUCCESS] Tutorial completed in {elapsed}")
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
elapsed = datetime.now() - start_time
|
|
199
|
+
print(f"\n[FAILURE] Tutorial failed after {elapsed}")
|
|
200
|
+
print(f"Error: {type(e).__name__}: {str(e)}")
|
|
201
|
+
raise
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
main()
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Tutorial: DFM for Finance Data
|
|
2
|
+
|
|
3
|
+
This tutorial demonstrates the complete workflow for training and prediction
|
|
4
|
+
using finance data with market_forward_excess_returns as the target variable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import traceback
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
project_root = Path(__file__).parent.parent
|
|
14
|
+
sys.path.insert(0, str(project_root / "src"))
|
|
15
|
+
|
|
16
|
+
import pandas as pd
|
|
17
|
+
import numpy as np
|
|
18
|
+
from dfm_python import DFM, DFMDataset
|
|
19
|
+
from dfm_python.config import DFMConfig
|
|
20
|
+
from sklearn.preprocessing import StandardScaler
|
|
21
|
+
import hydra
|
|
22
|
+
from omegaconf import DictConfig, OmegaConf
|
|
23
|
+
|
|
24
|
+
def save_failure_report(step: str, error: Exception, context: dict, project_root: Path):
|
|
25
|
+
"""Save detailed failure report to file."""
|
|
26
|
+
reports_dir = project_root / "failure_reports"
|
|
27
|
+
reports_dir.mkdir(exist_ok=True)
|
|
28
|
+
|
|
29
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
30
|
+
report_file = reports_dir / f"finance_failure_{timestamp}.json"
|
|
31
|
+
|
|
32
|
+
report = {
|
|
33
|
+
"experiment": "finance",
|
|
34
|
+
"step": step,
|
|
35
|
+
"timestamp": timestamp,
|
|
36
|
+
"error_type": type(error).__name__,
|
|
37
|
+
"error_message": str(error),
|
|
38
|
+
"traceback": traceback.format_exc(),
|
|
39
|
+
"context": context
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
with open(report_file, 'w') as f:
|
|
43
|
+
json.dump(report, f, indent=2, default=str)
|
|
44
|
+
|
|
45
|
+
print(f"\n[FAILURE] Error report saved to: {report_file}")
|
|
46
|
+
return report_file
|
|
47
|
+
|
|
48
|
+
def check_model_state(model, step: str):
|
|
49
|
+
"""Check model state for numerical issues and return diagnostics."""
|
|
50
|
+
diagnostics = {}
|
|
51
|
+
|
|
52
|
+
if model is None:
|
|
53
|
+
return diagnostics
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
if hasattr(model, 'training_state') and model.training_state is not None:
|
|
57
|
+
state = model.training_state
|
|
58
|
+
diagnostics['training_state'] = {}
|
|
59
|
+
|
|
60
|
+
for param_name in ['A', 'C', 'Q', 'R', 'V_0']:
|
|
61
|
+
if hasattr(state, param_name):
|
|
62
|
+
param = getattr(state, param_name)
|
|
63
|
+
if param is not None:
|
|
64
|
+
diagnostics['training_state'][param_name] = {
|
|
65
|
+
'shape': param.shape if hasattr(param, 'shape') else None,
|
|
66
|
+
'has_nan': bool(np.any(np.isnan(param))) if isinstance(param, np.ndarray) else None,
|
|
67
|
+
'has_inf': bool(np.any(np.isinf(param))) if isinstance(param, np.ndarray) else None,
|
|
68
|
+
'min': float(np.nanmin(param)) if isinstance(param, np.ndarray) else None,
|
|
69
|
+
'max': float(np.nanmax(param)) if isinstance(param, np.ndarray) else None,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if hasattr(model, 'result') and model.result is not None:
|
|
73
|
+
result = model.result
|
|
74
|
+
diagnostics['result'] = {
|
|
75
|
+
'converged': result.converged if hasattr(result, 'converged') else None,
|
|
76
|
+
'num_iter': result.num_iter if hasattr(result, 'num_iter') else None,
|
|
77
|
+
'loglik': float(result.loglik) if hasattr(result, 'loglik') and result.loglik is not None else None,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if hasattr(model, 'data_processed') and model.data_processed is not None:
|
|
81
|
+
data = model.data_processed
|
|
82
|
+
diagnostics['data_processed'] = {
|
|
83
|
+
'shape': data.shape if hasattr(data, 'shape') else None,
|
|
84
|
+
'has_nan': bool(np.any(np.isnan(data))) if isinstance(data, np.ndarray) else None,
|
|
85
|
+
'has_inf': bool(np.any(np.isinf(data))) if isinstance(data, np.ndarray) else None,
|
|
86
|
+
}
|
|
87
|
+
except Exception as e:
|
|
88
|
+
diagnostics['diagnostic_error'] = f"Failed to collect diagnostics: {str(e)}"
|
|
89
|
+
|
|
90
|
+
return diagnostics
|
|
91
|
+
|
|
92
|
+
@hydra.main(version_base=None, config_path="../config", config_name="dfm_finance")
|
|
93
|
+
def main(cfg: DictConfig) -> None:
|
|
94
|
+
print("=" * 80)
|
|
95
|
+
print("DFM Tutorial: Finance Data")
|
|
96
|
+
print("=" * 80)
|
|
97
|
+
|
|
98
|
+
start_time = datetime.now()
|
|
99
|
+
model = None
|
|
100
|
+
dataset = None
|
|
101
|
+
config = None
|
|
102
|
+
df = None
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
print("\n[Step 1] Loading data...")
|
|
106
|
+
try:
|
|
107
|
+
df = pd.read_csv(project_root / "data" / "finance.csv")
|
|
108
|
+
print(f" Data shape: {df.shape}")
|
|
109
|
+
|
|
110
|
+
# Truncate to ~1000 timesteps for faster testing (use most recent data)
|
|
111
|
+
# This reduces complexity from O(T × m³) while still providing enough data
|
|
112
|
+
if len(df) > 1000:
|
|
113
|
+
df = df.iloc[-1000:].copy()
|
|
114
|
+
df = df.reset_index(drop=True)
|
|
115
|
+
print(f" Truncated to {len(df)} timesteps (most recent) for faster testing")
|
|
116
|
+
|
|
117
|
+
# Build a daily date index (finance.csv only has date_id=0..T-1)
|
|
118
|
+
if "date" not in df.columns:
|
|
119
|
+
start_date = datetime(1980, 1, 1)
|
|
120
|
+
df["date"] = pd.date_range(start=start_date, periods=len(df), freq="D")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
context = {
|
|
123
|
+
"data_shape": df.shape if df is not None else None,
|
|
124
|
+
"data_columns": list(df.columns) if df is not None else None,
|
|
125
|
+
}
|
|
126
|
+
save_failure_report("Step 1: Loading data", e, context, project_root)
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
print("\n[Step 2] Creating Dataset...")
|
|
131
|
+
config = DFMConfig.from_dict(OmegaConf.to_container(cfg, resolve=True))
|
|
132
|
+
|
|
133
|
+
# Filter data to only include series specified in blocks (reduces observation dimension)
|
|
134
|
+
# Collect all unique series from all blocks
|
|
135
|
+
all_block_series = set()
|
|
136
|
+
if hasattr(config, 'blocks') and config.blocks:
|
|
137
|
+
for block_name, block_config in config.blocks.items():
|
|
138
|
+
if isinstance(block_config, dict) and 'series' in block_config:
|
|
139
|
+
all_block_series.update(block_config['series'])
|
|
140
|
+
|
|
141
|
+
# Keep only series in blocks + time index + any required metadata columns
|
|
142
|
+
columns_to_keep = ['date'] + list(all_block_series)
|
|
143
|
+
# Filter to only columns that exist in the DataFrame
|
|
144
|
+
columns_to_keep = [col for col in columns_to_keep if col in df.columns]
|
|
145
|
+
df_filtered = df[columns_to_keep].copy()
|
|
146
|
+
print(f" Filtered to {len(columns_to_keep) - 1} series (from {len(df.columns) - 1}) based on block configuration")
|
|
147
|
+
|
|
148
|
+
dataset = DFMDataset(config=config, data=df_filtered, time_index="date")
|
|
149
|
+
obs_dim = len(dataset.variables.columns)
|
|
150
|
+
print(f" Dataset created: obs_dim={obs_dim}")
|
|
151
|
+
except Exception as e:
|
|
152
|
+
context = {
|
|
153
|
+
"config_max_iter": cfg.get('max_iter', None),
|
|
154
|
+
"config_blocks": {k: len(v.get('series', [])) if isinstance(v, dict) else None
|
|
155
|
+
for k, v in cfg.get('blocks', {}).items()} if hasattr(cfg, 'blocks') else None,
|
|
156
|
+
"data_shape": df.shape if df is not None else None,
|
|
157
|
+
"filtered_series_count": len(columns_to_keep) - 1 if 'columns_to_keep' in locals() else None,
|
|
158
|
+
}
|
|
159
|
+
save_failure_report("Step 2: Creating Dataset", e, context, project_root)
|
|
160
|
+
raise
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
print("\n[Step 3] Training DFM model...")
|
|
164
|
+
model = DFM(dataset=dataset, config=config, scaler=StandardScaler())
|
|
165
|
+
model.fit()
|
|
166
|
+
|
|
167
|
+
result = model.result
|
|
168
|
+
print(f" Converged: {result.converged}, Iterations: {result.num_iter}")
|
|
169
|
+
|
|
170
|
+
# Check for numerical issues after training
|
|
171
|
+
diagnostics = check_model_state(model, "after_training")
|
|
172
|
+
if diagnostics.get('training_state'):
|
|
173
|
+
for param_name, param_info in diagnostics['training_state'].items():
|
|
174
|
+
if param_info.get('has_nan') or param_info.get('has_inf'):
|
|
175
|
+
print(f" WARNING: {param_name} contains NaN or Inf!")
|
|
176
|
+
except Exception as e:
|
|
177
|
+
context = {
|
|
178
|
+
"config_max_iter": cfg.get('max_iter', None),
|
|
179
|
+
"dataset_obs_dim": dataset.obs_dim if dataset is not None else None,
|
|
180
|
+
"elapsed_time": str(datetime.now() - start_time),
|
|
181
|
+
}
|
|
182
|
+
diagnostics = check_model_state(model, "during_training")
|
|
183
|
+
context.update(diagnostics)
|
|
184
|
+
save_failure_report("Step 3: Training DFM model", e, context, project_root)
|
|
185
|
+
raise
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
print("\n[Step 4] Making predictions...")
|
|
189
|
+
X_forecast, Z_forecast = model.predict(horizon=6)
|
|
190
|
+
print(f" Forecast shape: {X_forecast.shape}")
|
|
191
|
+
|
|
192
|
+
# Check forecast for issues
|
|
193
|
+
if np.any(np.isnan(X_forecast)) or np.any(np.isinf(X_forecast)):
|
|
194
|
+
print(f" WARNING: Forecast contains NaN or Inf!")
|
|
195
|
+
except Exception as e:
|
|
196
|
+
context = {
|
|
197
|
+
"result_converged": result.converged if 'result' in locals() else None,
|
|
198
|
+
"result_iterations": result.num_iter if 'result' in locals() else None,
|
|
199
|
+
}
|
|
200
|
+
diagnostics = check_model_state(model, "during_prediction")
|
|
201
|
+
context.update(diagnostics)
|
|
202
|
+
save_failure_report("Step 4: Making predictions", e, context, project_root)
|
|
203
|
+
raise
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
print("\n[Step 5] Saving model...")
|
|
207
|
+
model_path = project_root / "models" / "dfm_finance.pkl"
|
|
208
|
+
model_path.parent.mkdir(exist_ok=True)
|
|
209
|
+
model.save(model_path)
|
|
210
|
+
print(f" Model saved to: {model_path}")
|
|
211
|
+
except Exception as e:
|
|
212
|
+
context = {
|
|
213
|
+
"model_path": str(model_path) if 'model_path' in locals() else None,
|
|
214
|
+
"result_converged": result.converged if 'result' in locals() else None,
|
|
215
|
+
}
|
|
216
|
+
diagnostics = check_model_state(model, "during_saving")
|
|
217
|
+
context.update(diagnostics)
|
|
218
|
+
save_failure_report("Step 5: Saving model", e, context, project_root)
|
|
219
|
+
raise
|
|
220
|
+
|
|
221
|
+
print("\n" + result.summary())
|
|
222
|
+
|
|
223
|
+
elapsed = datetime.now() - start_time
|
|
224
|
+
print(f"\n[SUCCESS] Tutorial completed in {elapsed}")
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
elapsed = datetime.now() - start_time
|
|
228
|
+
print(f"\n[FAILURE] Tutorial failed after {elapsed}")
|
|
229
|
+
print(f"Error: {type(e).__name__}: {str(e)}")
|
|
230
|
+
raise
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
main()
|