paint-brush
Xử lý dữ liệu bị thiếu trong chuỗi thời gian tài chính - Bí quyết và cạm bẫytừ tác giả@hackerclrk2ky7l00003j6qwtumiz7c
12,024 lượt đọc
12,024 lượt đọc

Xử lý dữ liệu bị thiếu trong chuỗi thời gian tài chính - Bí quyết và cạm bẫy

từ tác giả Vladimir Kirilin10m2024/04/03
Read on Terminal Reader

dài quá đọc không nổi

Tôi tập trung vào các phương pháp xử lý dữ liệu còn thiếu trong chuỗi thời gian tài chính. Sử dụng một số dữ liệu mẫu, tôi cho thấy rằng LOCF thường là một phương pháp phù hợp so với việc loại bỏ và cắt bỏ nhưng có những lỗi - tức là có thể tạo ra các bước nhảy nhân tạo không mong muốn trong dữ liệu. Tuy nhiên, các lựa chọn thay thế như nội suy có vấn đề riêng, đặc biệt là trong bối cảnh dự đoán/dự báo trực tiếp.
featured image - Xử lý dữ liệu bị thiếu trong chuỗi thời gian tài chính - Bí quyết và cạm bẫy
Vladimir Kirilin HackerNoon profile picture
0-item

Nếu bạn giống tôi - bạn đã xử lý dữ liệu bị thiếu trong tập dữ liệu của mình ít nhất một lần. Hoặc hai lần. Hoặc quá nhiều lần…


Đôi khi, tất cả những gì cần làm để xử lý những NA rắc rối đó là loại bỏ chúng, tức là xóa các hàng chứa dữ liệu bị thiếu. Tuy nhiên, điều này có thể không phải lúc nào cũng tối ưu, đặc biệt đối với dữ liệu chuỗi thời gian và thậm chí còn hơn thế đối với dữ liệu tài chính. Tất nhiên, vấn đề này đã được nghiên cứu kỹ lưỡng nên có rất nhiều giải pháp thay thế cho việc bỏ rơi.


Tôi sẽ xem xét một vài trong số đó (được liệt kê bên dưới) và thảo luận về những ưu và nhược điểm:


  1. Rơi

  2. LOCF (quan sát cuối cùng được chuyển tiếp)

  3. Sự quy định trung bình (hoặc tương tự)

  4. Nội suy


Cảnh báo về spoiler: không có cách tiếp cận nào phù hợp cho tất cả! Tôi sẽ lập luận rằng LOCF thường là một lựa chọn tốt cho hoạt động tài chính nhưng cũng không phải không có nhược điểm. Với ý nghĩ đó, hãy để tôi mô tả các phương pháp và dữ liệu mà tôi sẽ sử dụng để giới thiệu chúng.


Lưu ý: nếu một người muốn mang tính mô phạm, tất cả các phương pháp 2-4 đều là ví dụ về một số cách quy gán.


Dữ liệu mô hình

Hãy bắt đầu với một số ví dụ về lý do tại sao người ta lại quan tâm đến việc tụt hạng ngay từ đầu. Để minh họa, tôi đã tạo ra một số dữ liệu giá cổ phiếu hàng ngày được đơn giản hóa quá mức với giả định rằng nó tuân theo một bước đi ngẫu nhiên không có sự chênh lệch (tức là giá trung bình dài hạn sẽ không đổi) - tuy nhiên đây không phải là giả định chính xác nhất nhưng là một giả định lành tính.


 np.random.seed(10) # needed for reproducibility price_moves = 3*pd.Series(np.random.randn(100)) # generating random "steps" with 0 mean price_vec = 100 + price_moves.cumsum() # generating brownian motion price_vec.plot() 


dãy giá không đổi


Chà, cốt truyện có vẻ khá có ý nghĩa.


Giả sử bây giờ chúng ta muốn tìm ra ý nghĩa thực nghiệm của chênh lệch giá hàng ngày -

 price_vec.diff().mean() #sample mean >0.20030544816842052

Rõ ràng là khác 0, không giống như chuỗi tạo - nhưng đây chỉ là nhiễu mẫu. Càng xa càng tốt.


Bỏ dữ liệu

Bây giờ hãy làm biến dạng dữ liệu này một chút bằng cách xóa một vài điểm dữ liệu:

 price_vec_na_simple = price_vec.copy() price_vec_na_simple.iloc[90:95] = np.array([np.NaN, np.NaN, np.NaN, np.NaN, np.NaN]) # price_vec_na_simple.diff().mean() >0.1433356258183252


Chúng tôi nhận thấy một vài điều ngay lập tức -

  1. Giá trị trung bình bằng cách nào đó không phải là NA mặc dù vectơ diff rõ ràng sẽ chứa NA

  2. Giá trị trung bình khác với giá trị chúng tôi thu được trước đây


Bây giờ, #1 khá dễ dàng - pd.mean tự động xóa NA theo mặc định.

Nhưng còn số 2 thì sao? Hãy suy nghĩ lại những gì chúng ta đang tính toán.


Thật dễ dàng để chỉ ra rằng ít nhất không có NA, chênh lệch giá trung bình chỉ nên là (price_vec[99]-price_vec[0])/99 - thực sự, khi chúng ta cộng chênh lệch giá, tất cả các phần "trung gian" sẽ bị loại bỏ, như vậy (price_vec[1] - price_vec[0]) + (price_vec[2] - price_vec[1]) + .. !


Bây giờ, với dữ liệu bị thiếu được chèn vào, nếu trước tiên chúng ta lấy chênh lệch rồi bỏ NA , việc hủy này sẽ bị hỏng - một số phép toán đơn giản cho thấy rằng bạn hiện đang tính toán (price_vec[99] - price_vec[0] - price_vec[95] + price_vec[89])/93 .


Để thể hiện điều này, hãy lưu ý rằng hai thuật ngữ sau hiện bị bỏ qua - price_vec[95] - price_vec[94]price_vec[90] - price_vec[89] , vì (NA - any number) đánh giá thành NA và sau đó bị loại bỏ.


Hãy xác minh điều này:

 (price_vec[99] - price_vec[0])/99 >0.20030544816842052 (price_vec[99] - price_vec[0] - price_vec[95] + price_vec[89])/93 >0.1433356258183252


Bây giờ, mọi việc trở nên rõ ràng hơn về cách chúng ta có thể khắc phục mọi thứ - trước tiên chúng ta cần loại bỏ NA và sau đó là diff -

 price_vec_na_simple.dropna().diff().mean() >0.21095999328376203


Giá trị trung bình gần như trở lại vị trí cũ - có một sự khác biệt nhỏ xảy ra vì hiện tại chúng ta có ít số hạng hơn trong giá trị trung bình - 94 thay vì 99.


Được rồi, có vẻ như nếu chúng ta chỉ quan tâm đến giá trị trung bình thì chúng ta chỉ cần sử dụng dropna (miễn là chúng ta làm đúng)? Xét cho cùng, sự khác biệt giữa 0.20.21 rõ ràng nằm trong khả năng chịu tiếng ồn của chúng tôi ngay từ đầu. Chà, không hẳn - hãy xem tại sao.


LOCF

LOCF là gì?

LOCF là viết tắt của Quan sát cuối cùng được chuyển tiếp. Ý tưởng đằng sau nó cực kỳ đơn giản - nếu tôi ghi lại dữ liệu vào một số khoảng thời gian, dữ liệu đó có thể đều đặn hoặc không, nếu thiếu quan sát của một khoảng thời gian cụ thể nào đó, chúng ta chỉ cần giả sử không có gì thay đổi với biến của mình và thay thế nó bằng biến cuối cùng không- giá trị bị thiếu (ví dụ - [3, 5, NA, 8] → [3, 5, 5, 8]). Người ta có thể hỏi - tại sao ngay từ đầu lại quan tâm đến khoảng thời gian thiếu một quan sát, tức là không xóa nó như trong phương pháp “bỏ”? Chà, câu trả lời nằm ở khuyết điểm cố hữu của việc “đánh rơi” mà tôi chưa đề cập ở trên.


Giả sử bạn ghi lại nhiều đại lượng cùng một lúc, đặc biệt là những đại lượng thường không thay đổi quá nhiều quá nhanh - như các bản ghi nhiệt độ và độ ẩm hàng giờ. Giả sử bạn có cả hai giá trị cho 10:00, 11:00 và 12:00 nhưng chỉ có độ ẩm lúc 13:00. Bạn xóa “hàng” đó đi - tức là giả vờ như bạn không có bài đọc lúc 13:00? Chà, không sao nếu bạn chỉ có hai biến - mặc dù bạn vừa xóa một số thông tin có giá trị (độ ẩm 13:00). Nhưng nếu bạn có nhiều lần xuất hiện như vậy hoặc nhiều biến số cùng một lúc, việc giảm có thể khiến bạn hầu như không có dữ liệu nào cả!


Một cách thay thế rất hấp dẫn là giả định rằng không có gì thay đổi về nhiệt độ trong khoảng thời gian từ 12:00 đến 13:00. Rốt cuộc, nếu ai đó đến gặp chúng tôi lúc 12:30 và hỏi chúng tôi - “nhiệt độ hiện tại là bao nhiêu”, chúng tôi sẽ trả lời chính xác bằng chỉ số 12:00 (tất nhiên là nếu chúng tôi không thể nhận được chỉ số mới ngay lập tức). ). Tại sao không sử dụng logic tương tự cho giá trị 13:00?


Tại sao nên sử dụng LOCF (trong tài chính)?

Trước tiên, hãy thử nghiệm phương pháp mới tìm thấy của chúng tôi trên dữ liệu từ trước:

 price_vec_na_simple.ffill().diff().mean() # ffill performs LOCF by default >0.20030544816842052


Có vẻ như chúng tôi đã lấy lại được giá trị cũ một cách chính xác! Ngoài ra, nếu bạn muốn nghiên cứu sâu hơn về dữ liệu chênh lệch giá - hiện tại dữ liệu này có vẻ "có trật tự" hơn vì nó có một mục nhập cho mỗi ngày, mặc dù năm mục trong số đó hiện bằng 0 (tại sao? hãy thử chạy price_vec_na_simple.ffill().diff().iloc[90:95] để bạn tự mình xem).


Thêm vào đó, trong tài chính, dữ liệu bị thiếu và dữ liệu ngoại lệ thường đi đôi với nhau. Hãy để tôi minh họa điều đó:

 #inflate two observations, delete three next ones price_moves_na[90] += 20 price_moves_na[91] += 30 price_moves_na[92] -= 50 # to "deflate" the price shock back price_vec_na = (100 + price_moves_na.cumsum()) price_vec_na[92:95] = [np.NaN, np.NaN, np.NaN] price_vec_na.tail(20).plot() price_vec_na.diff().dropna().mean() >0.7093365245831178 

tăng đột biến + thiếu dữ liệu


Chúng ta có thể thấy rằng sau khi giá tăng mạnh, dữ liệu không có sẵn trong 3 ngày liên tục. Đây không phải là một ví dụ "nhân tạo" như bạn tưởng! Hãy tưởng tượng giao dịch dừng lại sau khi tăng đột biến, ít nhất là trên sàn giao dịch cụ thể này. Sau đó, mọi thứ ổn định lại một chút nên giá quay trở lại chế độ bình thường. Có lẽ điều gì đó dần dần đã xảy ra. đang diễn ra đằng sau hậu trường thực sự “kết nối” các điểm giữa tăng đột biến và sau tăng đột biến, nhưng bạn không biết điều đó và không có bất kỳ dữ liệu nào cho nó!


Giả định tự nhiên nhất là gì nếu chúng ta không có bất kỳ dữ liệu mới nào? Chà, hãy nhớ lại rằng mô hình tạo dữ liệu của chúng tôi về cơ bản dựa trên sự thay đổi giá cả. Vậy nếu không có dữ liệu mới thì có lẽ giá không thay đổi chút nào? Đây chính xác là những gì LOCF (Quan sát lần cuối được chuyển tiếp) giả định.

Một số phép toán cho ngữ cảnh

Một lưu ý phụ dành cho người đọc tò mò - có lẽ một quan điểm cơ bản hơn về lý do tại sao LOCF đặc biệt phù hợp với dữ liệu giá cổ phiếu là nó thường được mô hình hóa như một martingale . Nói một cách đại khái, martingale là thứ mà dự đoán tốt nhất của chúng ta cho ngày mai là những gì chúng ta thấy hôm nay hoặc E[x_{t+1} | x_t] = x_t


Ok, quay lại dữ liệu thực tế! Hãy xem xét hậu quả của LOCF cả về mặt trực quan và số lượng:

 price_vec_na.ffill().tail(20).plot() price_vec_na.ffill().diff().mean() >0.20030544816842052 

cắt cụt LOCF


Ngay lập tức, chúng ta thấy được ưu và nhược điểm của LOCF (theo đúng nghĩa đen)! Thứ nhất, giá trị trung bình quay trở lại vị trí mà chúng ta “mong đợi” - tức là giá trị thực nghiệm không thay đổi. Tuy nhiên, chúng tôi giới thiệu một khoảng thời gian khá xấu khi giá không phù hợp với mức giá “điển hình” và sự sụt giảm giá giả tạo giữa ngày 94 và 95.

Còn việc buộc tội thì sao?

Hãy đối chiếu kết quả mà chúng tôi nhận được từ LOCF với sự cắt bỏ (trung bình). Đó là một lựa chọn rất phổ biến để xử lý NA, đặc biệt đối với dữ liệu chuỗi không theo thời gian. Tuy nhiên, nếu thực hiện một cách ngây thơ thì nó có nhiều nhược điểm khi sử dụng cho dữ liệu tài chính.


  • Nếu bạn chỉ sử dụng giá trị trung bình của toàn mẫu, bạn đưa ra một xu hướng nhìn về phía trước rõ ràng - tức là bạn sử dụng dữ liệu trong tương lai để quy kết các giá trị trong quá khứ.

  • Sử dụng một số loại phương pháp nhìn lại hoặc trung bình cuộn chắc chắn là tốt hơn - tuy nhiên, đôi khi nó có thể gây căng thẳng với quan điểm “đường cơ sở” Martingale mà chúng tôi đã mô tả trước đây.


Chúng ta hãy xem xét điều này chi tiết hơn một chút. Tôi sẽ sử dụng phép quy kết nhìn lại trên dữ liệu cũ của chúng ta -

 price_vec_na_impute = price_vec_na.copy() price_vec_na_impute[price_vec_na_impute.isna()] = price_vec_na.iloc[:90].mean() price_vec_na_impute.diff().mean() >0.20030544816842052 

nghĩa là sự buộc tội

Chúng tôi khôi phục giá trị trung bình thay đổi giá "chính xác", giống như LOCF. NHƯNG chúng tôi đưa ra một đợt giảm giá giả tạo giữa ngày 91 và ngày 92, theo một cách nào đó thậm chí còn tệ hơn mức chúng tôi đã có trước đây. Rốt cuộc, điều đó xảy ra khi hoặc sau khi mọi thứ có thể đã lắng xuống, trong khi điều này chỉ cho rằng mọi thứ sẽ trở lại bình thường ngay lập tức. Ngoài ra, trên thực tế, có thể gặp khó khăn đôi chút trong việc cân bằng giai đoạn xem lại sao cho chúng ta a) nắm bắt các xu hướng gần đây nhưng cũng b) nắm bắt các xu hướng dài hạn (sự đánh đổi sai lệch-phương sai thông thường).


Thêm biến thứ hai

Giả sử bây giờ chúng ta muốn thực hiện một nhiệm vụ phức tạp hơn - trích xuất mối tương quan giữa biến động giá của hai tài sản từ dữ liệu thực nghiệm, khi một hoặc cả hai chuỗi giá bị thiếu dữ liệu. Chắc chắn, chúng ta vẫn có thể sử dụng tính năng thả, nhưng:


  • ngay cả khi chúng ta có thể sử dụng nó, nó có tối ưu không?

  • điều gì sẽ xảy ra nếu chúng ta có nhiều biến - khi đó việc loại bỏ tất cả các hàng có ít nhất một NA có thể khiến chúng ta không có dữ liệu nào cả!


Có nhiều lý do khiến người ta có thể muốn tính toán mối tương quan - đó là bước đầu tiên của EDA trong hầu hết mọi mô hình đa biến, nó được sử dụng khá rộng rãi trong mọi loại hình xây dựng danh mục đầu tư , v.v. Vì vậy việc đo lường con số này một cách chính xác nhất có thể là điều khá cần thiết!


Để minh họa, hãy tạo biến thứ hai có mối tương quan “tích hợp” là 0,4 với biến đầu tiên. Để làm điều đó, chúng tôi sẽ sử dụng một loại Mô hình hỗn hợp Gaussian . Hình ảnh mà người ta có thể nghĩ đến là hai cổ phiếu có mối tương quan với nhau có chung một yếu tố rủi ro quan trọng, nhưng cổ phiếu thứ hai cũng có nguy cơ gặp phải yếu tố rủi ro chính mà cổ phiếu thứ nhất không có. Ví dụ, hãy nghĩ đến Google và Facebook - yếu tố đầu tiên có thể là quan điểm chung về lĩnh vực công nghệ và yếu tố thứ hai có thể là sự cạnh tranh với các mạng xã hội đối thủ.


 np.random.seed(2) # needed to ensure a fixed second series price_moves_2 = pd.Series(np.random.randn(100)) price_vec_2 = 50+(0.4*price_moves/3 + np.sqrt(1-0.4**2)*price_moves_2).cumsum() # all this math to ensure we get a 0.4 "theoretical" correlation with the first one


Hãy kiểm tra mối tương quan thực nghiệm “cơ bản” - nghĩa là không có NA và các bước nhảy.

 pd.concat([price_vec, price_vec_2], axis = 1).diff().corr().iloc[0,1] >0.4866403018044526


Hiện tại, điều này khá gần với mối tương quan “lý thuyết” - người ta biết rằng phép đo tương quan theo kinh nghiệm dễ bị nhiễu khá lớn.


NA không có ngoại lệ

Bước tiếp theo, chúng tôi sẽ kiểm tra trường hợp với NA, nhưng không có ngoại lệ. Chúng ta cũng sẽ so sánh điều gì sẽ xảy ra nếu chúng ta dropna trước và sau diff

 pd.concat([price_vec_na_simple, price_vec_2], axis = 1).diff().corr().iloc[0,1] # implicit dropna after diff >0.5022675176281746 pd.concat([price_vec_na_simple, price_vec_2], axis = 1).dropna().diff().corr().iloc[0,1] >0.5287405341268966


Cả hai kết quả đều khá gần nhau và không quá xa so với giá trị “thực nghiệm” mà chúng tôi nhận được trước đây. Hãy xác minh LOCF và việc cắt bỏ cũng hoạt động tốt:

 pd.concat([price_vec_na_simple, price_vec_2], axis = 1).ffill().diff().corr().iloc[0,1] >0.5049380499525835 price_vec_na_simple_impute = price_vec_na_simple.copy() price_vec_na_simple_impute[price_vec_na_simple_impute.isna()] = price_vec_na_simple_impute.iloc[:90].mean() pd.concat([price_vec_na_simple_impute, price_vec_2], axis = 1).ffill().diff().corr().iloc[0,1] >0.4866728183859715


Từ việc so sánh 4 kết quả trên, chúng ta thấy rằng tất cả các phương pháp đều hoạt động khá tốt. Có lẽ chúng ta nên mong đợi điều tương tự cho trường hợp ngoại lệ?


NA có ngoại lệ

Hãy nhớ rằng, để duy trì sự nhất quán, chúng ta cần đưa ra chuỗi giá thứ hai với những cú sốc về giá tương tự như chuỗi giá đầu tiên đã trải qua, nhưng không có các NA sau. Quay lại ví dụ trên - hãy tưởng tượng một số sự kiện lớn gây ra sự tăng đột biến của yếu tố rủi ro đầu tiên và cuối cùng khiến giao dịch tài sản đầu tiên phải dừng lại. Chắc chắn, tài sản thứ hai cũng sẽ trải qua những điều đó, nhưng có lẽ ở mức độ thấp hơn, và do đó sẽ không có sự tạm dừng nào diễn ra và do đó không có NA.


 price_vec_na_2 = 50+(0.4*price_moves_na/3 + np.sqrt(1-0.4**2)*price_moves_2).cumsum()


Hãy so sánh lại hiệu suất của tất cả các phương pháp của chúng tôi -

 pd.concat([price_vec_na, price_vec_na_2], axis = 1).diff().corr().iloc[0,1] >0.6527112906179914 pd.concat([price_vec_na, price_vec_na_2], axis = 1).dropna().diff().corr().iloc[0,1] >0.7122391279139506


Đó là một sự khác biệt khá lớn cả về mặt lý thuyết lẫn giá trị thực nghiệm! Thế còn LOCF và quy kết thì sao?

 pd.concat([price_vec_na, price_vec_na_2], axis = 1).ffill().diff().corr().iloc[0,1] >0.33178239830519984 pd.concat([price_vec_na_impute, price_vec_na_2], axis = 1).dropna().diff().corr().iloc[0,1] >0.7280990594963112


Bây giờ cuối cùng chúng ta cũng thấy LOCF có giá trị như thế nào! Nó rõ ràng vượt trội hơn tất cả các phương pháp khác!


Cạm bẫy tiềm ẩn + Nội suy

Tất nhiên, điều này không mạnh mẽ 100%. Đầu tiên, bằng cách thực hiện LOCF, chúng tôi đưa ra mức giảm giá lớn khi dữ liệu bị thiếu kết thúc. Nếu nó trùng với một số ngoại lệ trên vectơ giá thứ hai, kết quả có thể thay đổi khá nhiều. (*Bài tập dành cho người đọc - lật dấu về chuyển động giá của price_vec_na_2[95] và kiểm tra xem nó ảnh hưởng đến kết quả như thế nào). Không rõ liệu việc chỉ giới thiệu đợt giảm giá này có “sạch” hay không thay vì nội suy giữa mức giá đỉnh price_vec_na[91] và giá trị “bình thường” sau đó price_vec_na[95] . Tuy nhiên, đặc biệt đối với việc sử dụng “trực tiếp”, việc nội suy là không thực sự khả thi! Rốt cuộc, nếu hôm nay là ngày thứ 93, làm thế nào chúng ta có thể nội suy bằng cách sử dụng giá trị tương lai được ghi vào cuối ngày thứ 95? Đối với một nghiên cứu lịch sử - chắc chắn, đó vẫn là một lựa chọn, nhưng vẫn chưa rõ cách diễn giải và sử dụng nó để dự báo thực tế! Tóm lại, việc nội suy theo chiều thời gian là có thể, nhưng có phần đáng nghi ngờ hơn.


Kết luận

Tôi đã cố gắng đưa ra một nghiên cứu điển hình nhỏ để giới thiệu và giải thích tại sao LOCF thường là lựa chọn hấp dẫn và đơn giản nhất để sử dụng để xử lý dữ liệu còn thiếu trong chuỗi thời gian tài chính.


Tóm lại, ưu điểm là:

  • Hấp dẫn từ góc độ martingale/”luồng thông tin”
  • Siêu dễ thực hiện
  • Không cần tối ưu hóa (trái ngược với việc cắt bỏ)
  • Xử lý các ngoại lệ ít nhất là tốt


Một số nhược điểm:

  • Có khả năng gây ra những bước nhảy lớn vào cuối khoảng thời gian bị thiếu
  • Có thể bỏ lỡ một số động lực khớp sắc thái khi được sử dụng cho một số biến


Với tư cách là người định lượng trong một cửa hàng giao dịch độc quyền, tôi sử dụng nó cho hầu hết các nghiên cứu của mình như một cơ sở hiệu quả. Tất nhiên, một số tình huống đòi hỏi các biện pháp phức tạp hơn, nhưng những biện pháp đó rất ít và thường không thực sự được “giải quyết” 100% bằng bất kỳ phương pháp nào trong số 3 phương pháp khác được đề cập.