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.
Files changed (132) hide show
  1. {dfm_python-0.6.1 → dfm_python-0.6.11}/PKG-INFO +1 -1
  2. {dfm_python-0.6.1 → dfm_python-0.6.11}/config/dfm_finance.yaml +1 -1
  3. {dfm_python-0.6.1 → dfm_python-0.6.11}/config/dfm_macro.yaml +1 -1
  4. dfm_python-0.6.11/failure_reports/finance_failure_20260127_183841.json +22 -0
  5. dfm_python-0.6.11/failure_reports/macro_failure_20260127_183842.json +15 -0
  6. dfm_python-0.6.11/models/dfm_finance.pkl +0 -0
  7. {dfm_python-0.6.1 → dfm_python-0.6.11}/models/dfm_macro.pkl +0 -0
  8. {dfm_python-0.6.1 → dfm_python-0.6.11}/pyproject.toml +1 -1
  9. dfm_python-0.6.11/tutorial/tutorial_exchange_dfm.py +205 -0
  10. dfm_python-0.6.11/tutorial/tutorial_finance_dfm.py +234 -0
  11. dfm_python-0.6.11/tutorial/tutorial_macro_dfm.py +205 -0
  12. dfm_python-0.6.1/tutorial/tutorial_exchange_dfm.py +0 -57
  13. dfm_python-0.6.1/tutorial/tutorial_finance_dfm.py +0 -84
  14. dfm_python-0.6.1/tutorial/tutorial_macro_dfm.py +0 -56
  15. {dfm_python-0.6.1 → dfm_python-0.6.11}/.cursor/agents/test-runner.md +0 -0
  16. {dfm_python-0.6.1 → dfm_python-0.6.11}/.git +0 -0
  17. {dfm_python-0.6.1 → dfm_python-0.6.11}/.gitignore +0 -0
  18. {dfm_python-0.6.1 → dfm_python-0.6.11}/.python-version +0 -0
  19. {dfm_python-0.6.1 → dfm_python-0.6.11}/=0.11.0 +0 -0
  20. {dfm_python-0.6.1 → dfm_python-0.6.11}/MANIFEST.in +0 -0
  21. {dfm_python-0.6.1 → dfm_python-0.6.11}/config/ddfm_exchange.yaml +0 -0
  22. {dfm_python-0.6.1 → dfm_python-0.6.11}/config/ddfm_finance.yaml +0 -0
  23. {dfm_python-0.6.1 → dfm_python-0.6.11}/config/ddfm_macro.yaml +0 -0
  24. {dfm_python-0.6.1 → dfm_python-0.6.11}/config/dfm_exchange.yaml +0 -0
  25. {dfm_python-0.6.1 → dfm_python-0.6.11}/data/exchange_rate.csv +0 -0
  26. {dfm_python-0.6.1 → dfm_python-0.6.11}/data/finance.csv +0 -0
  27. {dfm_python-0.6.1 → dfm_python-0.6.11}/data/macro.csv +0 -0
  28. {dfm_python-0.6.1 → dfm_python-0.6.11}/models/dfm_exchange.pkl +0 -0
  29. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/__init__.py +0 -0
  30. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/__init__.py +0 -0
  31. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/adapter.py +0 -0
  32. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/constants.py +0 -0
  33. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/__init__.py +0 -0
  34. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/block.py +0 -0
  35. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/model.py +0 -0
  36. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/params.py +0 -0
  37. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/schema/results.py +0 -0
  38. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/config/types.py +0 -0
  39. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/__init__.py +0 -0
  40. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/ddfm_dataset.py +0 -0
  41. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/dfm_dataset.py +0 -0
  42. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/dataset/time.py +0 -0
  43. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/__init__.py +0 -0
  44. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/base.py +0 -0
  45. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/linear.py +0 -0
  46. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/decoder/mlp.py +0 -0
  47. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/__init__.py +0 -0
  48. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/base.py +0 -0
  49. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/pca.py +0 -0
  50. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/encoder/simple_autoencoder.py +0 -0
  51. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/functional/__init__.py +0 -0
  52. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/functional/em.py +0 -0
  53. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/__init__.py +0 -0
  54. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/dfm_logger.py +0 -0
  55. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/inference_logger.py +0 -0
  56. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/logger.py +0 -0
  57. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/logger/train_logger.py +0 -0
  58. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/__init__.py +0 -0
  59. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/base.py +0 -0
  60. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/ddfm/ddfm.py +0 -0
  61. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/__init__.py +0 -0
  62. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/dfm.py +0 -0
  63. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/initialization.py +0 -0
  64. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/mixed_freq.py +0 -0
  65. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/models/dfm/tent.py +0 -0
  66. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/builder.py +0 -0
  67. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/estimator.py +0 -0
  68. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/stability.py +0 -0
  69. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/statistic.py +0 -0
  70. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/numeric/validator.py +0 -0
  71. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/ssm/__init__.py +0 -0
  72. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/ssm/kalman.py +0 -0
  73. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/__init__.py +0 -0
  74. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/checkpoint.py +0 -0
  75. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/errors.py +0 -0
  76. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/helper.py +0 -0
  77. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/loss.py +0 -0
  78. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/metric.py +0 -0
  79. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/misc.py +0 -0
  80. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/dfm_python/utils/validation.py +0 -0
  81. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/__init__.py +0 -0
  82. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/__init__.py +0 -0
  83. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/__init__.py +0 -0
  84. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/test_block.py +0 -0
  85. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/test_model.py +0 -0
  86. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/schema/test_results.py +0 -0
  87. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_adapter.py +0 -0
  88. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_constants.py +0 -0
  89. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_tent_weights_config.py +0 -0
  90. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/config/test_types.py +0 -0
  91. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/conftest.py +0 -0
  92. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/dataset/test_ddfm_dataset.py +0 -0
  93. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/dataset/test_dfm_dataset.py +0 -0
  94. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/decoder/__init__.py +0 -0
  95. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/decoder/test_linear.py +0 -0
  96. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/decoder/test_mlp.py +0 -0
  97. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/__init__.py +0 -0
  98. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/test_base.py +0 -0
  99. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/test_pca.py +0 -0
  100. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/encoder/test_simple_autoencoder.py +0 -0
  101. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/__init__.py +0 -0
  102. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_em.py +0 -0
  103. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_em_correctness.py +0 -0
  104. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_em_properties.py +0 -0
  105. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_invariance.py +0 -0
  106. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/functional/test_macro_overflow.py +0 -0
  107. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/__init__.py +0 -0
  108. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/test_inference_logger.py +0 -0
  109. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/test_logger.py +0 -0
  110. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/logger/test_train_logger.py +0 -0
  111. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/models/__init__.py +0 -0
  112. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/models/test_ddfm.py +0 -0
  113. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/models/test_dfm.py +0 -0
  114. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/__init__.py +0 -0
  115. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_builder.py +0 -0
  116. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_cov_rowvar_regression.py +0 -0
  117. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_estimator.py +0 -0
  118. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_initialization.py +0 -0
  119. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_stability.py +0 -0
  120. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/numeric/test_validator.py +0 -0
  121. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/ssm/__init__.py +0 -0
  122. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/ssm/test_kalman.py +0 -0
  123. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/ssm/test_kalman_correctness.py +0 -0
  124. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/__init__.py +0 -0
  125. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_errors.py +0 -0
  126. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_helper.py +0 -0
  127. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_metric.py +0 -0
  128. {dfm_python-0.6.1 → dfm_python-0.6.11}/src/test/utils/test_misc.py +0 -0
  129. {dfm_python-0.6.1 → dfm_python-0.6.11}/tutorial/tutorial_exchange_ddfm.py +0 -0
  130. {dfm_python-0.6.1 → dfm_python-0.6.11}/tutorial/tutorial_finance_ddfm.py +0 -0
  131. {dfm_python-0.6.1 → dfm_python-0.6.11}/tutorial/tutorial_macro_ddfm.py +0 -0
  132. {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.1
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
@@ -7,7 +7,7 @@
7
7
 
8
8
  # EM Algorithm Parameters
9
9
  threshold: 1.0e-05 # EM convergence threshold
10
- max_iter: 50 # Maximum EM iterations
10
+ max_iter: 12 # Maximum EM iterations
11
11
 
12
12
  # Data Handling
13
13
  clock: d # Daily clock frequency (finance.csv uses daily index via date_id)
@@ -6,7 +6,7 @@
6
6
 
7
7
  # EM Algorithm Parameters
8
8
  threshold: 1.0e-05 # EM convergence threshold
9
- max_iter: 5 # Maximum EM iterations
9
+ max_iter: 12 # Maximum EM iterations
10
10
 
11
11
  # Numerical Stability Parameters
12
12
  clip_ar_coefficients: true
@@ -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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dfm-python"
3
- version = "0.6.1"
3
+ version = "0.6.11"
4
4
  description = "Dynamic Factor Model (DFM) estimation and forecasting in Python"
5
5
  requires-python = ">=3.10"
6
6
  license = {text = "MIT"}
@@ -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()