-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
New turnover, refactor fixed slippage adjustments #432
Conversation
Talked to @marketneutral and we agreed we should also preserve the old behavior. I'll add an argument like |
Why?
Why? Looks good otherwise. Also needs engineering review @richafrank. |
@twiecki When we talk about GMV, we have been using that to refer to the allocation to the algorithm (from the context of portfolio construction). If we have $100 and allocate evenly across 5 algos, each algo will have $20 GMV, regardless of if that algo is fully invested in the market or not. That's how we've been referring to GMV most often. The term AGB is also correct and avoids confusion — Jonathan recommended I do this. We use average AGB on both EOD's wrapping around the transactions mainly because it helps us avoid NaNs. For example, if we an algo has $10 fully invested Tuesday EOD, then Weds it closes all positions, what is its turnover Weds? Using straight AGB it would be infinity, but using the average it would be $10 / (($10 + 0) / 2) = 200%. This was also at the recommendation of Jonathan. Does that make sense? |
I find it confusing that in the doc string for We should probably also doc string the explanation about averaging yesterday's and today's AGB for the denominator. |
@eigenfoo We don't use GMV anywhere in pyfolio, people are just familiar with the term gross market value; AGB is less known from my understanding. The explanation is AGB == GMV from the frame of the algo. And yeah, good idea, I clarified that a bit. @twiecki @richafrank Should be all set for you to review now, thanks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gusgordon Mostly looks great to me, I just had a few stylistic notes and a suggestion for one of the tests.
pyfolio/txn.py
Outdated
turnover_rate = turnover_rate.fillna(0) | ||
return turnover_rate | ||
raise Exception('Passed denominator must be either' + | ||
'AGB or portfolio_value') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd write this as something like:
raise ValueError(
"Unexpected value for denominator '{}'. The denominator parameter "
"must be either 'AGB' or 'portfolio_value'.".format(denominator)
)
pyfolio/txn.py
Outdated
# We want our denom to be avg(AGB previous, AGB current) | ||
AGB = positions.drop('cash', axis=1).abs().sum(axis=1) | ||
denom = AGB.rolling(2).mean() | ||
denom.iloc[0] = AGB.iloc[0] / 2 # "day 0" AGB = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you expand a bit on this comment, maybe just explaining more (like you mentioned in this PR) that on day 1, we consider AGB_t-1 to be zero, so this is simply the first AGB value. This confused me at first without referencing back to your formula.
pyfolio/tests/test_txn.py
Outdated
|
||
expected = Series([0.5]*len(dates), index=dates) | ||
expected = Series([4.0] + [2.0] * (len(dates) - 1), index=dates) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a brief comment explaining the 4.0 here? (I know why it is, just for future readers)
pyfolio/tests/test_txn.py
Outdated
|
||
expected = Series([0.5]*len(dates), index=dates) | ||
expected = Series([4.0] + [2.0] * (len(dates) - 1), index=dates) | ||
result = get_turnover(positions, transactions) | ||
assert_series_equal(result, expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple comments on this test:
- it's probably worth adding a test for the case where you pass
denominator='portfolio_value'
- the
positions
DataFrame confuses me a bit here. If there are transactions for 2 different sids, shouldn't thepositions
DataFrame presumably have columns for 2 sids as well? Also worth noting that thepositions
column for sid0
is set to always be 10.0, meaning that doesn't really capture the averaging behavior where you doAGB.rolling(2).mean()
. Incremental or alternating positions values would remedy this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I'll add this, and change it to just 1 sid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
The new
get_turnover
method is different in a few ways:We no longer support the
average
argument. This led to confusion. We also no longer support passing aperiod
, because this was unused and also can lead to confusion. For example, if we passedperiod='1D'
, this is different than passing no period, becauseperiod='1D'
would include weekends.The denominator is now a function of actual gross book, or AGB. AGB is effectively the GMV from the algo's frame of reference, or longs + abs(shorts) EOD each day. Note, confusingly, this is different from an algo's GMV from the reference frame of a portfolio of algos, which is why we refer to it as AGB.
The denominator isn't AGB, but rather
(AGB_t-1 + AGB_t) / 2)
. On day 1, we considerAGB_t-1
to be zero. (@marketneutral is this correct?)The "default" is now
average=False
, whereas before the default wasaverage=True
. Again, this parameter has been removed.Effectively, this means that
avg_new_turnover ~= avg_old_turnover * 2 / avg_gross_lev
if my math is correct. Approximate because of the averaging of the AGB.Also, the fixed slippage adjustment functions have been refactored to not use the turnover function.