Data này tổng hợp 200 chỉ số tài chính từ 4391 công ty niêm yết tại Mỹ. Nguồn data có thể tải tại đây: https://www.kaggle.com/cnic92/200-financial-indicators-of-us-stocks-20142018
Bài viết này mình thử dùng thuật toán Machine Learning: Random Forest, để phân loại công ty nào có khả năng sinh lời trong năm, dựa vào 200 chỉ số tài chính. Kết quả chính xác của mô hình là 75.26% (acc). Trong đầu tư thì phương pháp này thường được gọi là phân tích định lượng
Dữ liệu này không lớn, nhưng số lượng Feature lớn, do đó kĩ thuật xử lí thuộc tính đầu vào khá quan trọng.
Noted: Kiến thức người viết hạn hẹp, cũng như trong tài chính thì các chỉ số tài chính cũng ko phải là yếu tố quyết định 100% kết quả đầu tư, nó còn phụ thuộc vào nhiều yếu tố kể cả quản lí vốn cách đi tiền,... Do đó sai số trong mô hình là điều không thể tránh khỏi. Hi vọng được anh em trong giới đầu tư và Data Science góp ý thêm.
Quan trọng hơn, không có mô hình nào gọi là chén thánh trong đầu tư. Mô hình nó chỉ giúp anh em dễ tiếp cận "Vùng may mắn" hơn, cung cấp góc nhìn đa chiều hơn. Và từ việc có nhiều thông tin khách quan để ra quyết định, chắc chắn việc quyết định sẽ khôn ngoan và ít cảm tính đi nhiều...
Đọc thêm về "vùng may mắn": https://tranbau.com/category/bai-viet-dau-tu/
---------------------------------------------------------------------
Lê Đặng Trung Hiếu
Supervisor
Phòng GD08, CTCP Chứng Khoán Vietcap
Tòa nhà Doji, 81 Hàm Nghi, Quận 1, TpHCM
Kết nối bạn bè: Facebook | Github | Linkedin
---------------------------------------------------------------------
---------------------------------------------------------------------
Bắt đầu vào bài viết với việc xử lí dữ liệu đầu vào ....
Quan sát qua dữ liệu đầu vào:
2019 PRICE VAR [%] là dữ liệu % tăng giảm giá từ đầu năm đến cuối năm của mã cổ phiếu (Unnamed: 0) trong dataset ( tính từ giá open của năm và giá close của năm).Từ 2019 PRICE VAR [%], filter % tăng giá > 0 là 1 (xem như class đầu tư có lợi nhuận) và % tăng giá < 0 là 0 (xem như class đầu tư không lợi nhuận)
Do đó xác định mục tiêu phân loại của bài toán là Class column trong dataset
Quan sát dữ liệu thiếu (Null, NaN) trong dataset và tìm cách loại bỏ
Quan sát thấy nhiều cột trống gần hết dữ liệu, nếu dropna() là tiêu, ko còn data để chạy. Số lượng feature cũng quá nhiều, heatmap ko cho thấy rõ là missing value nằm tại vị trí column nào. Nên idea xử lí là viết hàm drop các Feature có lượng Missing > 1 ngưỡng nào đó (ở đây mình cho là 20% missing)Nếu chưa biết dạng chart bên dưới có thể đọc thêm về (Missing Value Heatmaps) https://dev.to/tomoyukiaota/visualizing-the-patterns-of-missing-value-occurrence-with-python-46dj
Lọc và loại bỏ các Feature có số lượng dữ liệu bị mất >20%, sau đó biểu diễn lại heatmap để quan sát kết quả:
OK, sau khi quan sát lại dữ liệu sau xử lí, bây giờ đã có thể dropna(). Mình sẽ không vẽ lại Missing Value Heatmap nữa cho đỡ mất thời gian. Hiện tại bài này cũng không loại bỏ Collinear Features trong mô hình. Nếu chưa biết về Collinear Features bạn có thể đọc tại https://medium.com/future-vision/collinearity-what-it-means-why-its-bad-and-how-does-it-affect-other-models-94e1db984168
Hiện tại mình loại bỏ các Zero Feature dựa trên Machine Learning để xác định các Feature cần loại bỏ. Phương pháp này tìm các tính năng quan trọng bằng cách sử dụng gradient boosting machine, có thể đọc thêm về thuật toán này tại đây LightGBM library.
Bài sau sẽ giới thiệu rõ hơn về phương pháp loại bỏ Zero Feature này
Biểu diễn 12 Feature quan trọng, ảnh hưởng nhiều đến bài toán phân loại như Gross Profit Growth, Revenue Growth,... Và mình cũng loại bỏ bớt các Feature ảnh hưởng ít đến mô hình, nhằm giảm tải sức nặng khi chạy thuật toán
Xử lí Collinear Features
Quan sát dendrogram corr() từng nhóm feature, mình lựa chọn loại bỏ các feature có orrelation magnitude > 0.97 như bên dưới:
['Net Income', 'Net Income Com', 'Weighted Average Shs Out (Dil)', 'Profit Margin', 'EBIT', 'Consolidated Income', 'Earnings Before Tax Margin', 'Net Profit Margin', 'Total liabilities', 'Investments', 'Other Liabilities', 'priceToBookRatio', 'priceSalesRatio', 'priceFairValue', 'ebitperRevenue', 'pretaxProfitMargin', 'netProfitMargin', 'returnOnEquity', 'daysOfSalesOutstanding', 'companyEquityMultiplier', 'operatingCashFlowSalesRatio', 'dividendpaidAndCapexCoverageRatios', 'Tangible Book Value per Share', 'EV to Operating cash flow', 'R&D to Revenue', 'Capex to Operating Cash Flow', 'Graham Number', 'Tangible Asset Value', 'Invested Capital', 'Average Inventory', 'Days Sales Outstanding', 'Capex per Share', 'Gross Profit Growth', 'Receivables growth']
Bây giờ Exploratory data analysis (EDA) nhanh trước khi chạy các thuật toán Machine Learning
Quan sát thấy số lượng mã cổ phiếu có Return dương (class = 1) lớn hơn 2 lần so với Return âm (class = 0). 2019 quả là 1 năm ngon lành với TTCK Mỹ :))EDA xong thì thấy chỉ có 1 biến là Category còn lại là Numeric (mình chỉ biểu diễn Category feature, hiện tại trong data đó là Feature Sector):
Quan sát biểu đồ Sector ngành thì năm qua lợi nhuận thường nằm tại nhóm cổ phiếu Real Estate,Financial, Industry,... và ngành kém nhất là Energy
OK, giờ thì vào giai đoạn chạy Machine Learning
Ý tưởng bài viết là tìm xem các yếu tố cơ bản nào ảnh hưởng đến lợi nhuận dương và âm cuối năm.Giờ tách data mình có ra làm 2 tệp train(dùng để học dữ liệu) và test(dùng để kiểm tra tính chính xác của mô hình). Giải thích thêm cho các bác ngành đầu tư là 2 dữ liệu này hoàn toàn độc lập, có thể đọc hiểu thêm tại đây: https://www.quora.com/What-is-the-difference-between-training-data-and-testing-data
Lặp qua các thuật toán cơ bản, thì mình pick thuật toán Random Forest có acc = 75.26% (trên tập kiểm tra) làm mô hình cơ sở cho dự đoán. Sau đó sẽ tập trung làm tối ưu cho mô hình này ở những bài viết sau.
Tuning Model
Do nhiều bạn bè góp ý mình nên bổ xung Tuning Model vào bài viết, nên sẵn mình bổ xung phần tuning model luôn, phương pháp Tuning là GridSearchCV (đọc thêm về GridSearchCV).Các parameter mình dùng để optimized Random Forest:
parameters = {'criterion': ['entropy', 'gini'],Sau khi chạy Tuning thì kết quả select cuối cùng là:
'max_depth': list(np.linspace(10, 30, 10, dtype = int)) + [None],
'max_features': ['auto', 'sqrt','log2', None],
'min_samples_leaf': [4, 12],
'min_samples_split': [5, 10],
'n_estimators': list(np.linspace(10, 50, 10, dtype = int))}
RandomForestClassifier(criterion='gini', max_depth='14', max_features='sqrt', min_samples_leaf=12, min_samples_split=5, n_estimators=23)
Quan sát qua kết quả giữa Predict và Actual rồi mình close topic.