robertmartin8 / PyPortfolioOpt

Financial portfolio optimisation in python, including classical efficient frontier, Black-Litterman, Hierarchical Risk Parity
https://pyportfolioopt.readthedocs.io/
MIT License
4.24k stars 927 forks source link

Question: How to use PyPortfolioOpt for algo trading strategies rather than stocks/assets #580

Open ts-project opened 5 months ago

ts-project commented 5 months ago

Rather than use PyPortfolioOpt to weight or balance a set of stocks or assets, I am trying to use it to weight or balance a set of algo futures trading strategies. Trying to achieve what the README describes as "an algorithmic trader who has a basket of strategies".

I have dataframes of the trades taken by each strategy for a single futures contract with each trade date, the profit/loss in dollars, and a trade-by-trade cumulative balance per strategy, among other data points like drawdown etc. If needed, I also certainly have entry and exit prices for each futures trade (but obviously this would require each contract's point values to calculate the actual PnL).

What I don't quite understand is how to use PyPortfolioOpt when using this sort of trade by trade data for a set of strategies, rather than what the examples describe, which appears to be more of a weighted buy and hold approach and using the adjusted close prices for a set of assets over a period of time.

Thanks for any input and feedback on how to optimize a basket of algo strategies.

While I don't believe what I describe below is probably close to the right approach, let me also share what I have tried.

I have tried building a dataframe with the various strategies as columns and with each of their daily cumulative balances as rows.

If a strategy didn't take a trade on a given day, then the "adjusted close" value or balance of that strategy would remain the previous day's balance.

When I chop the dataframe to only include rows that have no initial balance (or zero values) for a strategy's initial balance and each strategy has already experienced a first trade, I don't get any errors, but all of my returned raw weights are 'nan'. This is also true if I don't chop the dataframes and include 'nan' as an initial balance rather than 0.

Again, don't believe this approach is right, but just wanted to share what I've tried.

When I include zeros as the initial balance before a first trade, I get an error that says:

...python3.8/site-packages/pypfopt/expected_returns.py:36: UserWarning: Some returns are infinite. Please check your price data."

Followed by:

ValueError Traceback (most recent call last)

in 200 201 --> 202 run_strategies(0.5,0.75,9) 203 204 in run_strategies(ratio, ratio_2, min_pnldd) 140 # Optimize for maximal Sharpe ratio 141 ef = EfficientFrontier(mu, S) --> 142 raw_weights = ef.max_sharpe() 143 cleaned_weights = ef.clean_weights() 144 # ef.save_weights_to_file("weights.csv") # saves to file ~/opt/anaconda3/lib/python3.8/site-packages/pypfopt/efficient_frontier/efficient_frontier.py in max_sharpe(self, risk_free_rate) 251 # max_sharpe requires us to make a variable transformation. 252 # Here we treat w as the transformed variable. --> 253 self._objective = cp.quad_form(self._w, self.cov_matrix) 254 k = cp.Variable() 255 ~/opt/anaconda3/lib/python3.8/site-packages/cvxpy/atoms/quad_form.py in quad_form(x, P, assume_PSD) 250 if assume_PSD: 251 P = psd_wrap(P) --> 252 return QuadForm(x, P) 253 else: 254 raise Exception( ~/opt/anaconda3/lib/python3.8/site-packages/cvxpy/atoms/quad_form.py in __init__(self, x, P) 40 def __init__(self, x, P) -> None: 41 """Atom representing :math:`x^T P x`.""" ---> 42 super(QuadForm, self).__init__(x, P) 43 44 def numeric(self, values): ~/opt/anaconda3/lib/python3.8/site-packages/cvxpy/atoms/atom.py in __init__(self, *args) 48 # Convert raw values to Constants. 49 self.args = [Atom.cast_to_const(arg) for arg in args] ---> 50 self.validate_arguments() 51 self._shape = self.shape_from_args() 52 if len(self._shape) > 2: ~/opt/anaconda3/lib/python3.8/site-packages/cvxpy/atoms/quad_form.py in validate_arguments(self) 56 raise ValueError("Invalid dimensions for arguments.") 57 if not self.args[1].is_hermitian(): ---> 58 raise ValueError("Quadratic form matrices must be symmetric/Hermitian.") 59 60 def sign_from_args(self) -> Tuple[bool, bool]: ValueError: Quadratic form matrices must be symmetric/Hermitian.
88d52bdba0366127fffca9dfa93895 commented 5 months ago

interesting topic, here is my quick reply:

Again, don't believe this approach is right, but just wanted to share what I've tried.

=> yes you are doing right

When I include zeros as the initial balance before a first trade, I get an error that says

=> yes pls drop NaN in the returns

ValueError Traceback (most recent call last) in 200 201 --> 202 run_strategies(0.5,0.75,9) 203 204

in run_strategies(ratio, ratio_2, min_pnldd) 140 # Optimize for maximal Sharpe ratio 141 ef = EfficientFrontier(mu, S) --> 142 raw_weights = ef.max_sharpe() 143 cleaned_weights = ef.clean_weights() 144 # ef.save_weights_to_file("weights.csv") # saves to file

=> I guess your covariance matrix S is not valid, could you share how you compute the mu and S