90-Minute Main Lesson: From Task to Out-of-Sample Evidence
Objectives: Define \((X,y)\) and the forecast time; split train/validation/test chronologically; choose metrics from error costs; explain loss, gradient, and learning rate.
learning path:
Foundations: prerequisite and task framing 10 min → representation and learning types 15 min → evaluation and leakage 20 min.
Application: loss, gradient, and learning rate 20 min → Fuyao Glass main case 15 min → exercise and feedback 10 min.
Advanced optimizers are an extension.
Answer first: What is \([2,3]\cdot[4,-1]\)? Why must test data not tune the model? Commit an answer before the reveal.
Feedback: The inner product is \(5\). Test-driven tuning feeds out-of-sample information back into the model and makes error optimistic. Review inner products and data splitting if either step is unclear.
The Core Question: Why Should Economists Learn Machine Learning?
Traditional econometrics and machine learning represent two different ‘cultures’ for solving problems.
Econometrics: The core is Causal Inference.
Goal: To understand ‘why’ the world works the way it does.
Concern: Unbiasedness and consistency of parameter estimates.
Example: By how much did the minimum wage increase cause a change in the unemployment rate?
Machine Learning: The core is Prediction.
Goal: To predict ‘what’ will happen in the world.
Concern: The model’s generalization ability on unseen data.
Example: Based on current macroeconomic indicators, predict next quarter’s GDP growth rate.
A Visual Contrast of the Two Cultures
The two methodologies have fundamental differences in model selection and objectives.
Why Prediction Matters to Economists
In a data-driven era, predictive power is itself a potent economic tool.
Financial Markets: Predicting asset prices, volatility, and credit risk.
Macroeconomics: Forecasting inflation, GDP growth, and unemployment to inform policy.
Business Decisions: Forecasting product sales, customer churn, and supply chain demand.
Policy Evaluation: Predicting the likely economic impact of a policy (e.g., a tax cut).
Causal inference explains the past; accurate prediction provides insight into the future. Combined, their power is multiplied.
Today’s Goal: Build a Complete Mental Framework for Machine Learning
After this chapter, you will be able to systematically understand any machine learning project from the perspective of ‘The Four Pillars’.
Pillar I: Frame the Problem (Framing)
This is the starting point for all work.
Before any technical details, you must clearly define the business problem and translate it into a specific machine learning task.
What are you trying to predict?
A continuous value (e.g., tomorrow’s stock price) \(\rightarrow\)Regression
A discrete category (e.g., whether a customer will default) \(\rightarrow\)Classification
What data do you have?
Does the data come with the ‘answer’ you want to predict (i.e., a label y)?
Yes \(\rightarrow\)Supervised Learning
No \(\rightarrow\)Unsupervised Learning
Pillar II: Define the Model (Modeling)
A model is essentially a mathematical function \(f(x, \theta)\) that tries to capture the relationship between input features \(x\) and the output \(y\).
\(x\): The input feature vector (e.g., house area, location).
\(\theta\): The model’s parameters. These are the values that need to be determined through ‘learning’ (e.g., coefficients in a linear regression).
\(f\): The form of the function. This is what we, as modelers, choose.
The range of models is vast:
Simple Models: Linear Regression, Logistic Regression (highly interpretable).
How do we objectively measure how good a model is? We need an evaluation metric.
This metric must reflect the business objective.
During model development, calculate it on validation data; after choosing the final model, report it once on test data untouched by selection.
Common Evaluation Metrics:
Regression Tasks:
Mean Squared Error (MSE)
R-squared (R²)
Classification Tasks:
Accuracy
Precision, Recall, F1-Score
Pillar IV: Define ‘Learning’ (Optimization)
The process of ‘learning’ is the process of automatically finding the best parameters \(\theta\).
We first define a Loss Function\(J(\theta)\), which measures how bad the model’s predictions are with the current parameters \(\theta\). The smaller the loss, the better the model.
Then, we use an Optimizer, such as Gradient Descent, to systematically and iteratively adjust the parameters \(\theta\) to find the set of values \(\theta^*\) that minimizes the loss function \(J(\theta)\).
What is Machine Learning? Learning Functions from Data
The essence of Machine Learning (ML) is to have a computer automatically learn a function from data, rather than through explicit programming, where this function can make predictions on unknown data.
Data Representation in ML: Everything is a Vector
In machine learning, we need to convert real-world objects into a language computers understand—numbers.
Dataset: A collection of N samples \(X = \{x_1, x_2, \dots, x_N\}\).
Sample: A single data point (a house, a customer).
Feature: A dimension describing a sample (area, number of bedrooms).
Feature Vector: A vector of all features for one sample \(x = (x_{\text{area}}, x_{\text{bedrooms}}, \dots)^T\).
Label: The target value we want to predict, y (house price).
From the Real World to Mathematical Objects
This transformation process is at the heart of data preprocessing.
A Sample = A Point in d-Dimensional Space
Once we represent samples as feature vectors, each sample can be viewed as a point in a d-dimensional feature space.
This provides a geometric foundation for understanding machine learning algorithms.
Case Study: Feature Vectors from Local A-Share Data
Use the local pre-adjusted daily prices of Jiangsu Hengrui Pharmaceuticals (600276.XSHG). One sample contains today’s close and volume; the next trading day’s close is the label.
Trading date
Pre-adjusted close
Volume (shares)
Next-trading-day close \(y\)
2023-01-03
37.9796
25,756,493
38.3056
2023-01-04
38.3056
25,766,800
39.0763
Sample: one row represents one trading day.
Feature vector:\(x_t=(\text{close}_t,\text{volume}_t)^T\), a point in two-dimensional space.
Label:\(y_t=\text{close}_{t+1}\); preserve chronological alignment and never leak it into the features.
Data used in this example:data/stock/stock_price_pre_adjusted.h5, key data, fields close, volume; prices follow the pre-adjusted-price convention and volume is in shares.
The displayed values are selectively read from that local data.
The Three Main Categories of Machine Learning
Based on the data we have (especially whether we have the label y), machine learning tasks can be divided into three main categories.
Supervised Learning
Unsupervised Learning
Reinforcement Learning
We will introduce them one by one.
Category 1: Supervised Learning
Used when the data comes with clear ‘answers’ or ‘labels’.
Unsupervised Learning
Used when data has no ‘answers’, and we want to discover its internal structure.
Category 3: Reinforcement Learning
Used when we need to learn an optimal strategy through ‘trial and error’ with an environment.
Focusing on Supervised Learning: Regression vs. Classification
The vast majority of tasks in economics and business fall under supervised learning. It can be further divided into two major tasks based on the type of the label y.
Regression:
Goal: Predict a continuous numerical value.
Output: \(y \in \mathbb{R}\)
Examples: Predicting GDP growth rate, company sales figures.
Classification:
Goal: Predict a discrete category.
Output: \(y \in \{C_1, C_2, \dots, C_K\}\)
Examples: Determining if a customer will churn, if a transaction is fraudulent.
Geometric Intuition of Regression and Classification
Pillar III: How to Evaluate if a Model is Good?
How do we know if the model we trained, \(f(x; \theta)\), is a ‘good’ model?
Core principle
Fit parameters on training data, choose features, hyperparameters, and stopping points on validation data, then perform one final generalization check on an untouched test period after the workflow is fixed.
This leads to a simple practice: use training and validation data to make modeling choices, and save the test data for one final check.
A train/test split is enough only when every modeling choice is made before the test results are viewed.
Keep Training, Validation, and Test Data Separate
Use earlier observations for training and validation. Keep the later test period out of every model-selection decision.
Why Must We Split? The Ghost of Overfitting
Overfitting occurs when a model learns the training data “too well,” to the point that it memorizes the noise and random fluctuations in the data as if they were general patterns.
Symptom: Performs extremely well on the training set, but very poorly on the test set.
Cause: The model is too complex, with too much freedom relative to the amount of data.
Validation is the mock exam used to improve; the test set is a held-out final opened once. Changing the model after seeing it turns it into another validation set.
Cornerstone of Classification Evaluation: The Confusion Matrix
For binary classification problems (e.g., predicting customer default), all evaluation metrics derive from a simple table: the Confusion Matrix.
Predicted Positive
Predicted Negative
Actual Positive
True Positive (TP)
False Negative (FN)
Actual Negative
False Positive (FP)
True Negative (TN)
Positive: The event we care about, like ‘default’ or ‘fraud’.
Negative: The other case, like ‘no default’.
True/False: Refers to whether the prediction was correct.
Understanding the Four Quadrants of the Confusion Matrix
TP (True Positive): Correct prediction, the customer did default. (Hit)
TN (True Negative): Correct prediction, the customer did not default. (Correct Rejection)
FP (False Positive): Incorrect prediction, predicted default, but they didn’t. (False Alarm, Type I Error)
FN (False Negative): Incorrect prediction, predicted no default, but they did. (Miss, Type II Error)
In fields like financial risk management, the cost of an FN (missing a bad customer) is often far greater than the cost of an FP (misjudging a good customer).
Classification Metric (1): Accuracy
Accuracy measures the proportion of total samples that the model predicted correctly.
\[
\large
\begin{aligned}
\text{Accuracy}
&= \frac{\text{Number of Correct Predictions}}{\text{Total Number of Samples}} \\
&= \frac{TP + TN}{TP + TN + FP + FN}
\end{aligned}
\]
Advantage: Very intuitive and easy to understand.
Disadvantage: Highly misleading on imbalanced datasets.
The Accuracy Trap: An Example
Imagine a credit card fraud detection scenario:
Total transactions: 10,000
Normal transactions: 9,990 (99.9%)
Fraudulent transactions: 10 (0.1%)
How does a ‘lazy’ model that predicts all transactions as ‘normal’ perform?
TP = 0, TN = 9990
FP = 0, FN = 10
Accuracy = (0 + 9990) / 10000 = 99.9%
This model has extremely high accuracy but is completely useless, as it fails to identify a single case of fraud.
Classification Metric (2): Precision
Precision measures the proportion of all samples predicted as positive that are actually positive.
Business Meaning: ‘Of all the fraud alerts I raised, how many were real?’
Focus: The purity of the predictions. High precision means fewer false alarms (FP).
Precision Diagram: The Intersection Contains True Positives
Classification Metric (3): Recall
Recall measures the proportion of all actual positive samples that we successfully identified.
\[ \large \text{Recall} = \frac{TP}{TP + FN} \]
Business Meaning: ‘Of all the actual fraud cases that occurred, how many did my model catch?’
Focus: How complete the search is. High recall means fewer misses (FN).
Recall Diagram: How Many Actual Positives Were Found
Precision vs. Recall: An Eternal Trade-off
For a fixed scoring model, threshold changes often produce an empirical precision–recall trade-off, but precision is not monotone in the threshold.
Lower the threshold:
the predicted-positive set can only expand, so TP and FP counts cannot decrease and recall cannot fall.
If the added cases are mostly true positives, however, precision may fall, stay flat, or rise.
Raise the threshold: the predicted-positive set can only shrink, so TP and FP counts cannot increase and recall cannot rise. Precision is still not guaranteed to move monotonically.
Empirical PR curves are therefore stepwise and may locally improve in both coordinates. Select the threshold from validation-period business costs, not from a mechanical inverse rule.
Business Decision
We need to decide which balance point to choose in this trade-off based on the different business costs of FPs and FNs.
Visualizing the Precision-Recall Trade-off
Classification Metric (4): F1-Score
To balance precision and recall, we use the F1-Score, which is their harmonic mean.
\(\hat{y}_i\) is the model’s prediction for sample \(i\).
\((y_i - \hat{y}_i)\) is the residual.
MSE calculates the average of the squared residuals. Because it uses squares, it penalizes large errors more heavily.
Visualizing Mean Squared Error (MSE)
The Essence of Learning: Optimization
We’ve defined the model and evaluation criteria, but how does a machine actually ‘learn’? The essence of learning is an Optimization process.
We define a Loss Function\(J(\theta)\), which measures how bad the model’s predictions are on the training set with the current parameters \(\theta\).
The lower the loss function’s value, the better the model performs.
The goal of ‘learning’ is to find a set of parameters \(\theta^*\) that minimizes the loss function \(J(\theta)\).
For regression problems, MSE is the most commonly used loss function.
Loss Function: The ‘Navigation Map’ for Optimization
The loss function \(J(\theta)\) describes a ‘topographical map’, where the altitude is the loss value. Our goal is to start from a random point and walk to the lowest valley in this terrain.
Figure 2: The landscape of a loss function: Our goal is to find the global minimum.
Loss Function for Classification: Cross-Entropy
For classification problems, we commonly use Cross-Entropy Loss.
Intuitive Understanding: It measures the ‘distance’ between the probability distribution predicted by the model and the true probability distribution.
For binary classification: \[ \large L(\theta) = - \frac{1}{N} \sum_{i=1}^N \left[ y_i \log(\hat{y}_i) + (1-y_i) \log(1-\hat{y}_i) \right] \] where \(y_i \in \{0, 1\}\) is the true label, and \(\hat{y}_i \in (0, 1)\) is the model’s predicted probability of class 1.
This function has a nice property: when the model makes a very confident and wrong prediction, the loss becomes very large, giving the model a strong ‘penalty’ signal.
Pillar IV: Define ‘Learning’ (Optimization)
We have the map (the loss function), but how do we find the way down the mountain?
The most classic and important method is Gradient Descent.
Core Idea
Imagine you are on a foggy mountainside, and you can only see the small patch of ground at your feet.
To get down the fastest, you should take a step in the direction of the steepest descent from your current position.
Mathematically, the negative of the gradient of a function at a point is the direction in which the function’s value decreases most rapidly.
The Mathematical Principle of Gradient Descent
Gradient descent is an iterative algorithm. At each step t, it updates the parameters \(\theta\) according to the following rule:
\(\theta_t\): The value of the parameters at step t.
\(\nabla J(\theta_t)\): The gradient of the loss function \(J\) at \(\theta_t\). It is a vector pointing in the direction of the fastest increase in the function’s value.
\(\eta\): The Learning Rate, a hyperparameter that controls how far we step each time.
\(-\eta \nabla J(\theta_t)\): We take a small step in the direction opposite to the gradient.
We repeat this process until the parameters converge.
Visualizing Gradient Descent
The Learning Rate (η): Determining Optimization Speed and Success
Code
def gradient_path(learning_rate, step_count): # Calculate Gradient Descent Path for a Given Learning Rate current_parameter =-1.8# Fixed Common Origin of Three Paths parameter_path = [current_parameter] # Save Parameter Locations Per Step loss_path = [current_parameter**2] # Save Secondary Losses Per Stepfor gradient_step_index inrange(step_count): # Perform Gradient Update at Specified Steps current_parameter -= learning_rate * (2* current_parameter) # Moving the parameter along the negative gradient parameter_path.append(current_parameter) # Record Updated Parameters loss_path.append(current_parameter**2) # Record Updated Lossesreturn parameter_path, loss_path # Returns a path that can be drawn directlylearning_axis = np.linspace(-2, 2, 400) # Prepare common parameter axeslearning_loss = learning_axis**2# Calculate the common quadratic loss curvesmall_path = gradient_path(.15, 10) # Pre-Calculated Path with Excessive Learning Rategood_path = gradient_path(.8, 5) # Path to pre-calculate moderate learning ratelarge_path = gradient_path(1.02, 5) # Pre-Calculated Path with Excessive Learning Rate
Figure 3: The impact of the learning rate (η) on the gradient descent process
Takeaway: Too small stalls; too large overshoots; validate a stable middle range.
Variants of Gradient Descent: Handling Large-Scale Data
When our training set is very large, computing the gradient over the entire dataset becomes very time-consuming. For this reason, variants of gradient descent have been developed.
Comparison of Gradient Descent Variants
Type
Gradient Calculation Method
Advantages
Disadvantages
Batch GD (BGD)
Uses all training samples
Accurate gradient, smooth convergence
High computational cost, slow
Stochastic GD (SGD)
Uses one randomly picked sample
Fast, can escape local minima
High variance in updates, noisy path
Mini-batch GD (MBGD)
Uses a small batch of samples (e.g., 32)
Combines benefits of BGD and SGD, the default choice
Requires tuning batch size
Main lesson: advanced optimizers are Extension; continue directly to the Fuyao Glass main case.
Advanced Optimizers: Making the Descent Smarter
Optional topic: after the learning-rate check, the 90-minute Core goes directly to the Fuyao Glass main case. Return here only after the principal exercise and summary, then continue to the final summary.
Basic gradient descent can struggle in complex loss landscapes. In modern deep learning, we use more advanced optimizers.
Momentum
Idea: Simulates momentum from physics. The update considers not only the current gradient but also the previous update direction, like a ball rolling down a hill.
Effect: Helps the algorithm “power through” flat regions and local minima, accelerating convergence.
Adam (Adaptive Moment Estimation)
Idea: Combines momentum with adaptive learning rates (adjusting the learning rate independently for each parameter).
Effect: Performs well across a wide range of tasks and is often the go-to default optimizer.
For beginners: Using the Adam optimizer directly will often yield excellent results.
Retrieval Check Before the Main Case
Retrieve the opening objectives: express a business question as \((X,y)\) and a prediction time; make chronological train/validation/test splits; choose metrics from error costs; and relate loss, gradient, and learning rate.
Check: What is the dot product of \([2,3]\) and \([4,-1]\)? Why must the test set not tune the model?
Answer:\(2\times4+3\times(-1)=5\). Test-set tuning feeds out-of-sample information back into development and makes reported generalization error optimistic.
Formative Check 1: Define the Task First
At the close of day \(t\), use information available by then to predict Fuyao Glass’s next-day return. Which label is correct?
A. \(r_t\) B. \(r_{t+1}\) C. day-\(t\) volume D. full-sample mean
Answer: B. Features are known at \(t\) and the label is future \(r_{t+1}\); using \(r_{t+1}\) as a feature would be look-ahead leakage.
from pathlib import Path # Locate the downloaded data fileimport pandas as pd # Read the local HDF5 data into a tablefrom sklearn.linear_model import LinearRegression # Use linear regression to establish interpretable benchmarksfrom sklearn.metrics import mean_squared_error # Evaluate a continuous prediction with a out-of-sample mean squared error# Public download: https://assets.qiufei.site/data/stock/stock_price_pre_adjusted.h5# After downloading, change the next line to the file's actual location on your device.# Course-relative option: Path("data/stock/stock_price_pre_adjusted.h5")# Windows: Path(r"C:\qiufei\data\stock\stock_price_pre_adjusted.h5")# macOS: Path("/Users/your_name/data/stock/stock_price_pre_adjusted.h5")# Linux: Path("/home/your_name/data/stock/stock_price_pre_adjusted.h5")price_path = Path("/home/ubuntu/r2_data_mount/data/stock/stock_price_pre_adjusted.h5")price_rows = pd.read_hdf(price_path, key='data', where=['order_book_id=="600660.XSHG"', 'date>=Timestamp("2018-01-01")', 'date<=Timestamp("2024-12-31")'], columns=['close', 'volume']) # Only Load Target Companies, Periods, and Fieldsmodel_frame = price_rows.reset_index().sort_values('date') # Restore Date Columns and Guarantee Chronologymodel_frame['return_t'] = model_frame['close'].pct_change() # Calculate daily returns from pre-adjusted closing pricesmodel_frame['volume_growth_t'] = model_frame['volume'].pct_change() # Construct Volume Growth Characteristicsmodel_frame['target_return_t1'] = model_frame['return_t'].shift(-1) # Define the next-trading-day return targetmodel_frame['target_date_t1'] = model_frame['date'].shift(-1) # Preserve the label-realization date for boundary purgingmodel_frame = model_frame.replace([float('inf'), float('-inf')], pd.NA).dropna() # Remove non-finite observations created by transformations
Code
train_end =int(len(model_frame) *0.6) # Fixed First Sixty Percent as Training Periodvalidation_end =int(len(model_frame) *0.8) # Fixed 20% Validation Periodvalidation_start_date = model_frame.iloc[train_end]['date']test_start_date = model_frame.iloc[validation_end]['date']train_rows = model_frame[(model_frame['date'] < validation_start_date) & (model_frame['target_date_t1'] < validation_start_date)] # Purge training labels realized in validationvalidation_rows = model_frame[(model_frame['date'] >= validation_start_date) & (model_frame['date'] < test_start_date) & (model_frame['target_date_t1'] < test_start_date)] # Purge validation labels realized in testtest_rows = model_frame[model_frame['date'] >= test_start_date] # Keep the final window held-outassert train_rows['target_date_t1'].max() < validation_rows['date'].min() and validation_rows['target_date_t1'].max() < test_rows['date'].min() # Verify label-realization boundariesfeature_names = ['return_t', 'volume_growth_t'] # Clarify the set of information available at time treturn_model = LinearRegression().fit(train_rows[feature_names], train_rows['target_return_t1']) # estimate model only on training periodvalidation_prediction = return_model.predict(validation_rows[feature_names]) # Evaluate the current benchmark during the validation period without touching the test periodpd.Series({'validation_mse': mean_squared_error(validation_rows['target_return_t1'], validation_prediction), 'train_end': train_rows['date'].max(), 'validation_start': validation_rows['date'].min(), 'test_start': test_rows['date'].min()}) # Report development evidence and the held-out test boundary
If defaults are 2% and a model predicts “no default” for everyone, what is accuracy, and is the model useful?
Answer
Accuracy is 98%, but default recall is zero. When false negatives are costly, report recall, precision, PR-AUC, and the confusion matrix rather than accuracy alone.
Step-by-Step Exercise: Add a Second Lag
Task: Add return_t2 = return_t.shift(1), keep the same split, compare validation MSE, choose one feature set, refit on train+validation, and evaluate the test period once.
Complete solution:
Add the lag before dropna() and use only validation MSE to choose the baseline or extended features.
Keep the three time-window boundaries fixed, do not choose from test results, and calculate test MSE only once after choosing the features.
Code
from sklearn.linear_model import LinearRegression # Reuse the same linear benchmark to isolate the impact of newly added lagging itemsfrom sklearn.metrics import mean_squared_error # Compare two sets of features with a fixed test period mean square errorlag_answer = model_frame.copy() # Create a Copy of Answers from Post-Cleaning Data of Primary Practicelag_answer['return_t2'] = lag_answer['return_t'].shift(1) # Adding a second lagged return known at time tlag_answer = lag_answer.dropna() # Drop the first row made unavailable by the added lagfixed_train_end = train_rows['date'].max()fixed_validation_end = validation_rows['date'].max()lag_train = lag_answer[lag_answer['date'] <= fixed_train_end] # keep original training period unchangedlag_validation = lag_answer[(lag_answer['date'] > fixed_train_end) & (lag_answer['date'] <= fixed_validation_end)] # select only in middle windowlag_test = lag_answer[lag_answer['date'] > fixed_validation_end] # Last Window Remains Archived During Selectionbaseline_features = ['return_t', 'volume_growth_t'] # Define the original benchmark information setextended_features = ['return_t', 'return_t2', 'volume_growth_t'] # Define the lagging information setbaseline_fit = LinearRegression().fit(lag_train[baseline_features], lag_train['target_return_t1']) # Re-Fit Benchmark on Fixed Training Periodextended_fit = LinearRegression().fit(lag_train[extended_features], lag_train['target_return_t1']) # Fit Extended Model on Same Training Periodbaseline_val_mse = mean_squared_error(lag_validation['target_return_t1'], baseline_fit.predict(lag_validation[baseline_features])) # Evaluate Benchmark with Validation Periodextended_val_mse = mean_squared_error(lag_validation['target_return_t1'], extended_fit.predict(lag_validation[extended_features])) # Evaluate the extended model with the same validation periodselected_features = extended_features if extended_val_mse < baseline_val_mse else baseline_featureslag_development = lag_answer[lag_answer['date'] <= fixed_validation_end] # Merging Training and Validation Periods for Final Fitfinal_fit = LinearRegression().fit(lag_development[selected_features], lag_development['target_return_t1']) # Re-fit after lockoutfinal_test_mse = mean_squared_error(lag_test['target_return_t1'], final_fit.predict(lag_test[selected_features])) # Only check Archive Test Periodpd.Series({'baseline_validation_mse': baseline_val_mse, 'extended_validation_mse': extended_val_mse, 'selected_features': ', '.join(selected_features), 'final_test_mse': final_test_mse, 'test_start': lag_test['date'].min()}) # Clearly distinguishing the selection evidence from the final check
Replace the firm with Jiangsu Hengrui Pharmaceuticals 600276.XSHG; use 2018–2021 for training, 2022 for validation, and 2023–2024 as held-out test; then add five-day volatility.
Submit candidate validation MSEs, the fixed model’s one test MSE, a residual plot, and a 150-word limitation.
Complete-answer reminder
use a chronological three-period split, report fields, units and sample size, make choices only with training and validation data, evaluate the test period once, interpret residuals, and state that predictive association is not causation.
New Data: Choose on Validation, Evaluate Once on Test
Table 3
Code
from pathlib import Path # Locate the downloaded data fileimport pandas as pd # Read and Organize Local Real A-Shares Quotesfrom sklearn.linear_model import LinearRegression # Establish explainable yield forecast baselinefrom sklearn.metrics import mean_squared_error # Compare two sets of models during the fixed test period# Public download: https://assets.qiufei.site/data/stock/stock_price_pre_adjusted.h5# After downloading, change the next line to the file's actual location on your device.# Course-relative option: Path("data/stock/stock_price_pre_adjusted.h5")# Windows: Path(r"C:\qiufei\data\stock\stock_price_pre_adjusted.h5")# macOS: Path("/Users/your_name/data/stock/stock_price_pre_adjusted.h5")# Linux: Path("/home/your_name/data/stock/stock_price_pre_adjusted.h5")transfer_path = Path("/home/ubuntu/r2_data_mount/data/stock/stock_price_pre_adjusted.h5")transfer_rows = pd.read_hdf(transfer_path, key='data', where=['order_book_id=="600276.XSHG"', 'date>=Timestamp("2018-01-01")', 'date<=Timestamp("2024-12-31")'], columns=['close', 'volume']) # Select Hengrui Medicine and Required Fieldstransfer_frame = transfer_rows.reset_index().sort_values('date') # Sort by trading days in order of forecast occurrencetransfer_frame['return_t'] = transfer_frame['close'].pct_change() # Construct daily return featurestransfer_frame['volume_growth_t'] = transfer_frame['volume'].pct_change() # Compute one-day volume growthtransfer_frame['volatility_5d_t'] = transfer_frame['return_t'].rolling(5).std() # Use only the historical five-day yield to estimate the volatilitytransfer_frame['target_return_t1'] = transfer_frame['return_t'].shift(-1) # keep next-trading-day return targettransfer_frame['target_date_t1'] = transfer_frame['date'].shift(-1) # Preserve the label-realization datetransfer_frame = transfer_frame.replace([float('inf'), float('-inf')], pd.NA).dropna() # Delete records that are not available for prediction or evaluationtransfer_train = transfer_frame[(transfer_frame['date'] <='2021-12-31') & (transfer_frame['target_date_t1'] <'2022-01-01')] # Purge training labels realized in validationtransfer_validation = transfer_frame[(transfer_frame['date'] >='2022-01-01') & (transfer_frame['date'] <='2022-12-31') & (transfer_frame['target_date_t1'] <'2023-01-01')] # Purge validation labels realized in testtransfer_test = transfer_frame[transfer_frame['date'] >='2023-01-01']assert transfer_train['target_date_t1'].max() < transfer_validation['date'].min() and transfer_validation['target_date_t1'].max() < transfer_test['date'].min() # Verify label-realization boundariestransfer_baseline_features = ['return_t', 'volume_growth_t'] # Define the benchmark information set without volatilitytransfer_extended_features = transfer_baseline_features + ['volatility_5d_t'] # Only Extend One Historical Volatility Feature
Code
transfer_baseline = LinearRegression().fit(transfer_train[transfer_baseline_features], transfer_train['target_return_t1']) # Fit Benchmark on Full Training Periodtransfer_extended = LinearRegression().fit(transfer_train[transfer_extended_features], transfer_train['target_return_t1']) # Fit Extended Model on Same Training Periodtransfer_baseline_val_mse = mean_squared_error(transfer_validation['target_return_t1'], transfer_baseline.predict(transfer_validation[transfer_baseline_features])) # Record the baseline validation errortransfer_extended_val_mse = mean_squared_error(transfer_validation['target_return_t1'], transfer_extended.predict(transfer_validation[transfer_extended_features])) # Record Extended Model Validation Errortransfer_selected_features = transfer_extended_features if transfer_extended_val_mse < transfer_baseline_val_mse else transfer_baseline_featurestransfer_development = pd.concat([transfer_train, transfer_validation])transfer_final = LinearRegression().fit(transfer_development[transfer_selected_features], transfer_development['target_return_t1'])transfer_prediction = transfer_final.predict(transfer_test[transfer_selected_features]) # Only Predict Archive Test Period Oncetransfer_residual = transfer_test['target_return_t1'].to_numpy() - transfer_prediction # Calculate Residual Difference for Diagnostic Graph Usepd.Series({'train_n': len(transfer_train), 'validation_n': len(transfer_validation), 'test_n': len(transfer_test), 'baseline_validation_mse': transfer_baseline_val_mse, 'extended_validation_mse': transfer_extended_val_mse, 'selected_features': ', '.join(transfer_selected_features), 'final_test_mse': mean_squared_error(transfer_test['target_return_t1'], transfer_prediction)}) # Strictly Separate Selected Evidence from Test check
Validation MSE 0.000546119023 (baseline) versus 0.000544403032 locks the extended features.
After the 2018–2022 refit, held-out test MSE is 0.000398683922 through 2024-12-30; predictive association is not causation.
Complete Solution for the New Case: Residual Diagnostic
Code
import matplotlib.pyplot as plt # Drawing Out-of-Sample Residuals Sorted by Timefig, ax = plt.subplots(figsize=(9, 3.2)) # Suitable for One-Page Display horizontal canvasax.plot(transfer_test['date'], transfer_residual, color='#006F99', linewidth=1) # Demonstrate Time Aggregation and Extremes for Residualsax.axhline(0, color='#536164', linestyle='--', linewidth=1) # Mark the reference lines without system deviationsax.set(xlabel='Date', ylabel='actual return − prediction', title='test-period residuals') # Clarify the business meaning of coordinatesax.tick_params(axis='both', labelsize=24)ax.xaxis.label.set_size(26); ax.yaxis.label.set_size(26); ax.title.set_size(28)import matplotlib.dates as mdatesax.xaxis.set_major_locator(mdates.MonthLocator(interval=6))ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y\n%m'))fig.tight_layout() # Avoid fitting axis labels outside the graphical boundariesplt.show() # Output residual charts for diagnosis
Figure 4: Hengrui residuals in the fixed 2023–2024 test period
Residual mean is about 0.00051 and standard deviation about 0.01998. A near-zero mean does not remove risk: tail errors still dominate squared loss.
Formative Check 3: Has Optimization Succeeded?
Training loss keeps falling while test loss first falls and then rises. Should training continue?
Answer
Not on training loss alone. The pattern indicates overfitting; choose the stopping point on a validation period and evaluate once on an untouched test period.
Hastie, Tibshirani & Friedman, The Elements of Statistical Learning, Chapters 2 and 7.
scikit-learn User Guide: model selection, metrics, and linear models.
Data: local pre-adjusted A-share data. This slide records only repository-verifiable file, key, fields, and filters; it does not infer an undocumented vendor.
Chapter Summary: The Four Pillars of Machine Learning