Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing Kalman Filter for Pair Trading Strategy Enhancement #7

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

chraberturas
Copy link
Contributor

Overview

This pull request introduces the implementation of the Kalman Filter to dynamically adjust hedge ratios between trading pairs, aiming to enhance the accuracy and profitability of our pair trading strategy. The Kalman Filter, a recursive algorithm, is used to estimate the state of a linear dynamic system from a series of noisy measurements. In the context of pair trading, it allows for more adaptive and responsive hedge ratio adjustments based on the evolving price movements of the stock pairs.

Solves: /issues/3

Changes Made

  • Kalman Filter Implementation: Added a new script kalman_filter.q which encapsulates the Kalman Filter logic applied to pair trading.
  • Strategy Integration: Integrated the Kalman Filter with the existing pair trading strategy, allowing dynamic hedge ratio adjustments.

@chraberturas chraberturas added the enhancement New feature or request label Feb 12, 2024
@chraberturas chraberturas linked an issue Feb 12, 2024 that may be closed by this pull request
3 tasks
@Kokechacho
Copy link
Contributor

Kokechacho commented Feb 13, 2024

I've uploaded a new temporary file named kf.q, which contains the initial implementation of the Kalman Filter function.
One issue I've encountered is related to appending to the covariance matrix, which is currently causing errors:

// Append new updates estimates,: mt; covariances,: (,/)Ct;

Additionally, I'm still figuring out the best approach for returning the values from the function. One idea I'm considering is to return an array containing all the matrices concatenated together.

// Return (estimates;covariances)}

I'll continue to work on refining the Kalman Filter function and addressing these issues. Once I have made further progress and resolved the existing challenges, I'll provide another update.

@chraberturas
Copy link
Contributor Author

chraberturas commented Feb 13, 2024

Why are you flattening your matrix (,/) Ct? We should preserve the full matrix in its original format, so something like covariances, :Ct should work.

Yes, we need to consider how to approach this. I've thought that perhaps we could use set with global variables. However, I believe that returning everything is the best method here. For example, returning a dictionary instead of a list with multiple items would be more efficient. In this case, we need the opinion of our dear great expert @neutropolis.

@Kokechacho
Copy link
Contributor

Okey I will return everything as a dictionary, but then, It does not makes sense to give to the function previous estimations and covariances because it ill never use them inside.
Input: (X,Y, Delta)
Output: (EstAlphaBeta, EstCO)

The function works fine I think so far, I need to test if it gives same results as in python.

Copy link
Contributor Author

@chraberturas chraberturas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review the formulas; this is quite important

kalman_filter.q Outdated
et: y - ft; // e_t = y_t - f_t (EQ 3)
Qt: ((Ft mmu Rt) mmu transpose[Ft]) + V; // Q_t = T(F_t) * R_t * F_t + V_t (EQ 6)
At: ((Rt mmu transpose[Ft]) mmu 1%Qt); // A_t = R_t * F_t * inv(Q_t) (EQ 7)
mt: (At *\: et) + alphat; // m_t = a_t + A_t * e_t (EQ 4)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using each right\:, i think you don't need it

Suggested change
mt: (At *\: et) + alphat; // m_t = a_t + A_t * e_t (EQ 4)
mt: (At * et) + alphat; // m_t = a_t + A_t * e_t (EQ 4)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not remeber right know why I did it exactly. Maybe it is useless i will try to do it without *: and see if it is still the same result

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, as you said it works the same

kalman_filter.q Outdated
Qt: ((Ft mmu Rt) mmu transpose[Ft]) + V; // Q_t = T(F_t) * R_t * F_t + V_t (EQ 6)
At: ((Rt mmu transpose[Ft]) mmu 1%Qt); // A_t = R_t * F_t * inv(Q_t) (EQ 7)
mt: (At *\: et) + alphat; // m_t = a_t + A_t * e_t (EQ 4)
Ct: (eye[2] - (At mmu Ft)) mmu Rt; // C_t = R_t - A_t * Q_t * T(A_t) (EQ 8)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you review this? I think correct formula is this

Suggested change
Ct: (eye[2] - (At mmu Ft)) mmu Rt; // C_t = R_t - A_t * Q_t * T(A_t) (EQ 8)
Ct: (eye[2] - (flip[enlist At] mmu enlist Ft)) mmu Rt; // C_t = R_t - A_t * Q_t * T(A_t) (EQ 8)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey I will try it as well

kalman_filter.q Outdated
At: ((Rt mmu transpose[Ft]) mmu 1%Qt); // A_t = R_t * F_t * inv(Q_t) (EQ 7)
mt: (At *\: et) + alphat; // m_t = a_t + A_t * e_t (EQ 4)
Ct: (eye[2] - (At mmu Ft)) mmu Rt; // C_t = R_t - A_t * Q_t * T(A_t) (EQ 8)
(mt;-1*Ct)} // Return new updates
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you negating C? I think that doesnt make sense

Copy link
Contributor

@Kokechacho Kokechacho Mar 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result is correct if I negate C. I think it has to do with the order of the operation. from above.

Ct: ( (At mmu Ft) . eye[2]) mmu Rt; (mt;Ct)} // Return new updates

This should do the trick. I will change it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about this and I think that the best option is to replicate the formula. If my math is correct, what you have without negating it is correct, but hard to understand. So this is quite easier: Ct:Rt-flip[enlist At] mmu enlist first[Qt] * At

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey this is correct, the spreads works fine now


// Auxiliary functions
// Creates a diagonal matrix
eye:{(2#x)#1f,x#0f}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider this shorter version :)

Suggested change
eye:{(2#x)#1f,x#0f}
eye:{{x=/:x}til x}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try that but I dont know if it is going to cause some problems as we need floats and not booleans or integers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't cause problems, try e.g.: 4f*eye 4

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation causes problems, I think it has to do with types

kalman_filter.q Outdated
At: ((Rt mmu transpose[Ft]) mmu 1%Qt); // A_t = R_t * F_t * inv(Q_t) (EQ 7)
mt: (At *\: et) + alphat; // m_t = a_t + A_t * e_t (EQ 4)
Ct: (eye[2] - (At mmu Ft)) mmu Rt; // C_t = R_t - A_t * Q_t * T(A_t) (EQ 8)
(mt;-1*Ct)} // Return new updates
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider returning e_t as well, in order to have your spreads calculated so you save one calculation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I will try that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But returning e_t doesnt save me from any calculations no? It will need to calculate e_t again on the next iteration

@Kokechacho
Copy link
Contributor

I found this repository, very similar to ours https://github.com/nicholasferguson/kdb.q.kalman.filter.beta.ETFs/blob/master/beta.kalman.q

Updated some mstakes on EQ 8 and EQ 4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Study Using Kalman Filter
2 participants