Optimising weights with costs (pysystemtrade)
In a previous put up I confirmed you a way to use my open source python backtesting package, pysystemtrade, to estimate forecast weights and device weights. At the time I hadn't included code to calculate costs. Now that has been remedied I concept I need to also write some code to illustrate the one-of-a-kind methods you may optimise inside the presence of charges.
You can see what else has been covered in pysystemtrade since you last checked, here. If you are no longer partial to pysystemtrade, don't worry, you'll in all likelihood locate the dialogue interesting even though you may then should workout the way to put in force it yourself.
Naturally your knowledge and amusement of this publish could be more suitable in case you've study chapter 12 of my e book, even though non readers who have accompanied my weblog posts on the subject of optimisation ought to also be excellent.
This post has been modified to mirror the changes made in version zero.10.0
Costs, commonly
In this first part of the publish I'll discuss in widespread phrases the extraordinary approaches of handling fees. Then I'll show you how to do that in python, with some quite pictures.
How to treat prices
Ignore prices - use gross returns
This is by way of far the best option. It's even ok to do it if the property for your portfolio optimisation, whether or not they be buying and selling rules or gadgets, have the identical fees. Of path if this is not the case you will end up overweighting something that appears outstanding pre-costs, however has very excessive costs to go along with it.
Subtract prices from gross returns: use net returns
This is what most people would do without thinking; it's perhaps the "obvious" solution. However this skips over a fairly fundamental problem, which is this: We know costs with more certainty than we know expected gross returns.I'm assuming here you have calculated your charges properly and not been overly optimistic.
Take for example my own trading system. It backtests at something like a 20% average return before costs. As it happens I came in just over thatlast year. But I expect just over two thirds of my annual returns to be somewhere between -5% and +45% (this is a simple confidence interval given a standard deviation of 25%).
Costs however are a exceptional depend. My charges for closing yr came in at 1.39% as opposed to a backtested 1.Five%. I am pretty assured they may be between 1% and a pair of% maximum years I alternate. Costs of 1.5% don't have an effect on optimisation a good deal, but in case your anticipated charges were 15% versus a 20% gross go back, then it's important you endure in thoughts your expenses have perhaps a 70% danger of being among 14% and sixteen%, and your gross returns between -five% and 45%. That's a device I personally wouldn't trade.
The next few options involve diverse ways of dealing with this difficulty.
Ignore gross returns, just use fees
This is probably the most severe answer - throw away any statistics about gross returns and optimise purely on fee statistics. Expensive assets gets a lower weight than reasonably-priced ones.
(Note this have to be executed in a way that preserves the covariance shape of gross returns; when you consider that we nonetheless want extra diversifying belongings to be upweighted. It's additionally correct for optimisation to avoid negative returns. I follow a drag to the returns such that the asset's Sharpe Ratios are same to the price distinction plus an inexpensive common Sharpe)
I pretty like this idea. I think it's specifically appealing inside the case of allocating instrument weights in a portfolio of trading structures, each buying and selling one tool. There is little reason to suppose that one tool will do higher than some other.
Nevertheless I can apprehend why a few people would possibly locate this a touch excessive, and I possibly would not use it for allocating forecast weights to trading rule variations. This is mainly the case in case you've been following by using popular recommendation now not to healthy trading fashions inside the "layout" segment, this means that an optimiser wishes with the intention to underweight a negative version.
Subtract costs from gross returns, and multiply fees with the aid of a aspect
This is a compromise where we use gross returns, but multiply our costs by a factor (>1) before calculating net costs. A common factor is 2, derived from the common saying "A bird in the hand is worth two in the bush", or in the language of Bayesian financial economics: "A bird which we have in our possession with 100% uncertainty has the same certainty equivalent value as two birds whose ownership and/or existence has a probability of X% (solve for X according to your own personal utility function)".
In other phrases 1% of more fees is well worth as tons as 2% of more gross returns. This has the advantage of being simple, even though it's not obvious what the suitable element is. The correct thing will depend upon the sampling distribution of the gross returns, and that's even with out moving into the sticky international of uncertainty adjustment for private utility functions.
Calculate weights the usage of gross returns, and regulate sooner or later for price degrees
This is a barely more sophisticated approach and one that Thomas Bayes FRS would be more snug with.
![]() |
| I'm 97% sure this is Thomas Bayes. Ex-ante I was 100%, but the image is from Wikipedia after all. |
In table 12 (chapter 4, p.86 print edition) of my book I explain how you can adjust portfolio weights if you know with certainty what the Sharpe ratio difference is between the distribution of returns of the assets in our portfolio (notice this is not as strong as saying we can predict the actual returns). Since we have a pretty good idea what costs are likely to be (so I'm happy to say we can predict their distribution with something close to certainty) it's valid to use the following approach:
- Make a first stab at the portfolio weights using just gross returns. The optimiser (assuming you're using bootstrapping or shrinkage) will automatically incorporate the uncertainty of gross returns into it's work.
- Using the Sharpe Ratio differences of the costs of each asset adjust the weights.
This trick way we will pull in varieties of facts approximately which we have differing levels of statistical self belief. It's additionally easier than the strictly accurate alternative of bootstrapping some weights with only price records, and then combining the weights derived from the use of gross returns with those from expenses.
Apply a maximum fee threshold
In bankruptcy 12 of my e-book I advocated that you do now not use any trading device which sucked up extra than zero.Thirteen SR of prices a 12 months. Although all the methods above will help to a degree, it's likely an excellent concept to totally keep away from allocating to any machine which has immoderate prices.
Different techniques of pooling
I'm a large fan of pooling data throughout special contraptions.
This phase applies most effective to calculating forecast weights; you can not pool information across contraptions to work out instrument weights.
It's rare to have enough data for someone instrument to have the ability to mention with any statistical significance that this buying and selling rule is better than the alternative one. We often need decades of statistics to make that selection. Decades of records just aren't to be had except for a few select markets. But when you have 30 devices with 10 years of records every, then it's three centuries worth.
Once we start thinking about pooling with expenses however there are a few one of a kind approaches of doing it.
Full pooling: Pool both gross returns and fees
This is the only solution; but if our devices have substantially exclusive expenses it can prove deadly. In a discipline of on the whole cheap instruments we would plump for a high go back, high turnover, trading rule. When carried out to a expensive device that would be a guaranteed money loser.
Don't pool: Use every instruments own gross returns and fees
This is the "throw our toys out of the pram" solution to the point I raised above. Of course we lose all the benefits of pooling.
Half pooling: Use pooled gross returns, and an instrument's personal fees
This is a pleasing compromise. The concept being all over again that gross returns are distinctly unpredictable (so allow's get as a lot statistics as possible approximately them), whilst fees are smooth to forecast on an tool by way of device foundation (so let's use them).
Notice that the calculation for cost comes in two parts - the cost "per turnover" (buy and sell) and the number of turnovers per year. So we can use some pooled information about costs (the average turnover of the trading rule), whilst using the relevant cost per turnover of an individual instrument.
Note
I optimistically don't want to point out to the clever readers of my blog that the usage of an instrument's very own gross returns, but with pooled charges, is quite silly.
Costs in pysystemtrade
You can comply with along right here. Notice that I'm the usage of the standard futures instance from chapter 15 of my e book, however we are using all the versions of the ewmac rule from 2_8 upwards to make things extra interesting.
Key
This is an extract from a pysystemtrade YAML configuration document:
Forecast_weight_estimate:
date_method: expanding ## different alternatives: in_sample, rolling
rollyears: 20
frequency: "W" ## different options: D, M, Y
Forecast weights
Let's begin with setting forecast weights. I'll begin with the simplest possible behaviour which is:- applying no cost weighting adjustments
- applying no ceiling to costs before weights are zeroed
- A cost multiplier of 0.0 i.e. no costs used at all
- Pooling gross returns across instruments and also costs; same as pooling net returns
- Using gross returns without equalising them
I'll attention on the weights for Eurostoxx (because it's the most inexpensive marketplace inside the chapter 15 set) and V2X (the most pricey one); although of course on this first instance they'll be the same as I'm pooling internet returns. Although I'm doing all my optimisation on a rolling out of pattern basis I'll best be showing the very last set of weights.
Forecast_cost_estimates:
use_pooled_costs: True
Forecast_weight_estimate:
apply_cost_weight: False
ceiling_cost_SR: 999.Zero
cost_multiplier: zero.Zero
pool_gross_returns: True
equalise_gross: False
# optimisation parameters remain the same
method: bootstrap
equalise_vols: True
monte_runs: a hundred
bootstrap_length: 50
equalise_SR: False
frequency: "W"
date_method: "expanding"
rollyears: 20
cleansing: True

These are the Eurostoxx weights; however they're the same for each market. As you may see the diversifying convey rule receives the most weight; for the variations of EWMAC it is close to same weights.
Subtract prices from gross returns: use net returns
Forecast_cost_estimates:
use_pooled_costs: True
Forecast_weight_estimate:
apply_cost_weight: False
ceiling_cost_SR: 999.Zero
cost_multiplier: 1.Zero ## be aware alternate
pool_gross_returns: True
equalise_gross: False

Forecast_cost_estimates:
use_pooled_costs: False
Forecast_weight_estimate:
apply_cost_weight: False
ceiling_cost_SR: 999.Zero
cost_multiplier: 1.Zero
pool_gross_returns: False
equalise_gross: False
![]() |
| V2X (Expensive market) |
Half pooling: Use pooled gross returns, and an instrument's personal fees
Forecast_cost_estimates:
use_pooled_costs: False
use_pooled_turnover: True ## even if pooling I advise doing this
Forecast_weight_estimate:apply_cost_weight: False
ceiling_cost_SR: 999.Zero
cost_multiplier: 1.Zero
pool_gross_returns: True
equalise_gross: False
![]() |
| EUROSTOXX (Cheap) |
![]() |
| V2X (Expensive) |
These graphs are some distance more realistic. There is ready 30% in convey in each; with quite plenty identical weighting for the less expensive Eurostoxx marketplace, and a tilt towards the inexpensive variations for highly-priced V2X.
For the rest of this post I'll be the use of this mixture.
Ignore gross returns, just use fees
Forecast_cost_estimates:
use_pooled_costs: False
use_pooled_turnover: True ## even if no longer pooling fees I advise doing this
Forecast_weight_estimate:apply_cost_weight: False
ceiling_cost_SR: 999.Zero
cost_multiplier: 1.Zero
pool_gross_returns: True
equalise_gross: True
![]() |
| V2X (luxurious) |
For cheap Eurostoxx expenses do not appear to remember a great deal and there may be a range to the greater diversifying 'wings' driven by way of correlations. For V2X there is absolutely a tilting away from the extra expensive options, however it is not dramatic.
Subtract costs from gross returns after multiply costs by a factor>1
Forecast_cost_estimates:
use_pooled_costs: False
use_pooled_turnover: True ## even if pooling I advise doing this
Forecast_weight_estimate:
apply_cost_weight: False
ceiling_cost_SR: 999.Zero
cost_multiplier: three.Zero
pool_gross_returns: True
equalise_gross: False
![]() |
| Eurostoxx (Expensive) |
![]() |
| V2X (Cheap) |
Forecast_weight_estimate:
Forecast_cost_estimates:
use_pooled_costs: False
use_pooled_turnover: True ## even if pooling I advise doing this
apply_cost_weight: True
ceiling_cost_SR: 999.Zero
cost_multiplier: zero.Zero
pool_gross_returns: True
equalise_gross: False
![]() |
| EUROSTOXX (Cheap) |
![]() |
| V2X (Expensive) |
Apply a maximum fee threshold
Forecast_cost_estimates:
use_pooled_costs: False
use_pooled_turnover: True ## even if pooling I advise doing this
apply_cost_weight: False
ceiling_cost_SR: zero.Thirteen
cost_multiplier: 1.Zero
pool_instruments: True
equalise_gross: False
![]() |
| EUROSTOXX (Cheap) |
![]() |
| V2X (Expensive) |
Really giving any allocation to ewmac2_8 has been form of loopy due to the fact it is a alternatively highly-priced beast to exchange. The V2X weighting is even greater extreme doing away with all but the three slowest EWMAC variations. It's for that reason that chapter 15 of my e-book makes use of only these 3 regulations plus deliver.
My favorite setup
Forecast_cost_estimates:
use_pooled_costs: False
use_pooled_turnover: True ## even if pooling I advise doing this
Forecast_weight_estimate:
apply_cost_weight: True
ceiling_cost_SR: zero.Thirteen
cost_multiplier: zero.Zero
pool_gross_returns: True
equalise_gross: False
As you may realize from studying my e-book I just like the concept of culling highly-priced buying and selling guidelines. This leaves the optimiser with much less to do. I virtually just like the idea of making use of price weighting (versus say multiplying costs my some arbitrary figure); this means that optimising on gross returns. Equalising the returns earlier than expenses seems a bit severe to me; I'd nonetheless like the idea of truly poor trading policies being downweighted, despite the fact that they're the reasonably-priced ones. Pooling gross returns however the use of tool precise fees strikes me because the most logical path to take.
![]() |
| Eurostoxx (cheap) |
![]() |
| V2X (luxurious) |
A observe approximately different optimisation methods
The other three methods in the package are shrinkage and one shot optimisation (just a standard mean variance). I don't recommend using the latter for reasons which I've mentioned many, many times before. However if you're using shrinkage be careful; the shrinkage of net return Sharpe Ratios will cancel out the effect of costs. So again I'd suggest using a cost ceiling, setting the cost multiplier to zero, then applying a cost weighting adjustment; the same setup as for bootstrapping.
Forecast_cost_estimates:
use_pooled_costs: False
use_pooled_turnover: True ## even if pooling I advise doing this
approach: shrinkage
equalise_SR: Falseann_target_SR: zero.5
equalise_vols: True
shrinkage_SR: 0.90
shrinkage_corr: 0.50
apply_cost_weight: True
ceiling_cost_SR: zero.Thirteen
cost_multiplier: zero.Zero
pool_instruments: True
pool_costs: False
equalise_gross: False
The Eurostoxx weights are very similar to those with bootstrapping. For V2X shrinkage ends up putting about 60% in carry with the rest split between the 3 slowest ewmac variations. This is more a property of the shrinkage method, rather than the costs. Perhaps the shrinkage isn't compensating enough for the uncertainty in the correlation matrix, which the bootstrapping does.
If you don't want to pool gross returns you may need to recall a better shrinkage component as there is less records (one of the reasons I do not like shrinkage is the fact the foremost shrinkage varies relying on the use case).
There's a final method Equal_weight which will give equal weights (duh). However it can be used in combination with ceiling_cost_SR and apply_cost_weights.Instrument weights
In my book I absolutely recommended that ignoring costs for device buying and selling subsystems is a wonderfully legitimate aspect to do. In truth I'd be pretty glad to equalise Sharpe ratios within the final optimisation and simply let correlations do the speaking.
Note that with device weights we additionally don't have the advantage of pooling. Also via the usage of a ceiling on fees for trading rule versions it is unlikely that any instrument subsystem will come to be breaking that ceiling.
![]() |
| Instrument weights |
Summary
Key points from this submit:
- Pooling gross returns data across instruments - good thing
- Using instrument specific costs - important
- Filtering out trading rules that are too expensive - very very important
- Using a post optimisation cost weighting - nice, but not a dealbreaker
- Using costs when estimating instrument weights - less important
Happy optimising.













