PvR - Profit vs. Risk

As software developers, while writing trading algorithms, it is beneficial to know whether our changes increase or decrease the output in a run.

Consider an example:
1. Seeing 200% returns over two years, great.
2. Make a change.
3. Now seeing only 180% returns.
4 Throw away that change and think in another direction.
5. You actually made 5% more per trade.

So what happened? How can that be?

The problem is that your change seemed bad yet actually resulted in an increase in profit per dollar transacted for stocks (amount risked, or put into play).

To quote someone here recently, I don't want to be a party pooper. And yet, the fact of the matter is, you can't trust that returns chart.

The returns calculation employs a philosophy that all of your starting capital is at risk as soon as you open the account, and that's all that is ever at risk.
It calculates returns based on starting capital regardless of how much you actually activated (even if you spent very little of it or went negative, into margin, borrowing from the broker). To be fair, that is apparently common in the industry. Meanwhile in developing code, this will help you see straight.

In the example, the new code made different trades based on the change and those trades were overall more profitable, it's just that the amount put to work on each stock was lower. You spent less. You made more for each dollar. That's the goal, right?

PvR stands for Profit vs. Risk. It calculates returns based only on the amount put to work and as a result, is a reliable measure of code merit.

With PvR, while you are writing code, you no longer need to be concerned with trying to make sure you are spending all of the starting capital. You can worry about that later, closer to the time you're ready to go live, or whenever you decide to focus on it. The PvR metric lets you stay focused.

It also means you no longer need to be locked into a cage-match battle with margin, leverage. Doesn't matter, for now. PvR only cares about profit per dollar put to work. You can start out with $100K and spend just$10K or another time $10M, PvR will always give you a deterministic value you can rely on that measures your code merit. By the same token, with no changes in the code, whether your starting capital field in the backtester is set at$10 or $10M, if you make the same trades, the chart will vary widely while PvR will be the same. You can always trust it. Try it. Clone below, run the backtest, note the ending PvR, then change the starting capital value and run again. The main chart returns will be very different, however PvR will be the same. There are three sets of PvR code in this post: 1. Minimal amount of code for backtests 2. Full code for backtests 3. Research/Notebook code, experimental Below is a minimal amount of code for recording PvR. For best speed, replace c. with context. and remove c = context, then move the initialization to def initialize. def handle_data(context, data): pvr(context, data) def pvr(context, data): ''' Minimal custom chart of profit_vs_risk returns ''' c = context # Brevity, readability if 'pvr' not in c: # For real money, you can modify this to total cash input minus any withdrawals manual_cash = c.portfolio.starting_cash c.pvr = { 'chart_pvr' : 1, 'chart_cash_low' : 1, 'chart_max_shrt' : 1, 'chart_max_risk' : 1, 'start' : manual_cash, 'cash_low' : manual_cash, 'max_shrt' : 0, 'max_risk' : 0, } c.pvr['cash_low'] = min(c.pvr['cash_low'], c.portfolio.cash) c.pvr['max_shrt'] = max(c.pvr['max_shrt'], abs(sum([z.amount * z.last_sale_price for s, z in c.portfolio.positions.items() if z.amount < 0]))) c.pvr['max_risk'] = max(0, c.pvr['max_risk'], c.pvr['start'] - c.pvr['cash_low'], c.pvr['max_shrt']) # Profit_vs_Risk returns based on max amount actually invested, risked, long or short if c.pvr['max_risk'] != 0: # Avoid zero-divide if c.pvr['chart_pvr']: record(PvR = 100 * (c.portfolio.portfolio_value - c.pvr['start']) / c.pvr['max_risk']) if c.pvr['chart_cash_low']: record(CashLow = c.pvr['cash_low']) if c.pvr['chart_max_shrt']: record(MxShrt = c.pvr['max_shrt']) if c.pvr['chart_max_risk']: record(MxRisk = c.pvr['max_risk'])  Related Content 25 responses This is the full PvR code. See the 'Source Code' tab. Just pay attention to the Options section at the beginning. Once you are used to it, it's easy to add to any algo. Edit: Better to use the full PvR code below (displayed in the large text block). There's a section for initialize(), the main function, and the call to it, pvr(context, data). I've been making that call from handle_data() rather than using schedule_function() because it is more universal and adaptable that way. See the one pasted below, later, for a simplified and improved version. 35 Loading... Total Returns -- Alpha -- Beta -- Sharpe -- Sortino -- Max Drawdown -- Benchmark Returns -- Volatility --  Returns 1 Month 3 Month 6 Month 12 Month  Alpha 1 Month 3 Month 6 Month 12 Month  Beta 1 Month 3 Month 6 Month 12 Month  Sharpe 1 Month 3 Month 6 Month 12 Month  Sortino 1 Month 3 Month 6 Month 12 Month  Volatility 1 Month 3 Month 6 Month 12 Month  Max Drawdown 1 Month 3 Month 6 Month 12 Month ''' https://www.quantopian.com/posts/pvr --garyha Full PvR Profit vs. Risk code ... added to the Quantopian sample algo (with comments removed) You can add pvr() and the call to it and initializations to your own code. ''' def initialize(context): # for pvr() c = context c.max_lvrg = 0 c.risk_hi = 0 c.date_prv = '' c.cash_low = c.portfolio.starting_cash c.date_end = str(get_environment('end').date()) log.info('{} to {} {} {}'.format(str(get_datetime().date()), c.date_end, int(c.cash_low), get_environment('data_frequency'))) def handle_data(context, data): pvr(context, data) order(sid(24), 50) def pvr(context, data): ''' Custom chart and/or log of profit_vs_risk returns and related information ''' # # # # # # # # # # Options # # # # # # # # # # record_max_lvrg = 1 # Maximum leverage encountered record_leverage = 0 # Leverage (context.account.leverage) record_q_return = 0 # Quantopian returns (percentage) record_pvr = 1 # Profit vs Risk returns (percentage) record_pnl = 0 # Profit-n-Loss record_shorting = 1 # Total value of any shorts record_overshrt = 0 # Shorts beyond longs+cash record_risk = 0 # Risked, max cash spent or shorts beyond longs+cash record_risk_hi = 1 # Highest risk overall record_cash = 0 # Cash available record_cash_low = 1 # Any new lowest cash level logging = 1 # Also to logging window conditionally (1) or not (0) log_method = 'risk_hi' # 'daily' or 'risk_hi' c = context # For brevity pvr_rtrn = 0 # Profit vs Risk returns based on maximum spent profit_loss = 0 # Profit-n-loss shorts = 0 # Shorts value longs = 0 # Longs value overshorts = 0 # Shorts value beyond longs plus cash new_risk_hi = 0 new_cash_low = 0 # To trigger logging in cash_low case lvrg = c.account.leverage # Standard leverage, in-house date = str(get_datetime().date()) # To trigger logging in daily case cash = c.portfolio.cash start = c.portfolio.starting_cash cash_dip = int(max(0, start - cash)) q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start if int(cash) < c.cash_low: # New cash low new_cash_low = 1 c.cash_low = int(cash) if record_cash_low: record(CashLow = int(c.cash_low)) # Lowest cash level hit if record_max_lvrg: if c.account.leverage > c.max_lvrg: c.max_lvrg = c.account.leverage record(MaxLv = c.max_lvrg) # Maximum leverage if record_pnl: profit_loss = c.portfolio.pnl record(PnL = profit_loss) # "Profit and Loss" in dollars for p in c.portfolio.positions: shrs = c.portfolio.positions[p].amount if shrs < 0: shorts += int(abs(shrs * data[p].price)) if shrs > 0: longs += int(shrs * data[p].price) if shorts > longs + cash: overshorts = shorts # Shorts when too high if record_shorting: record(Shorts = shorts) # Shorts value as a positve if record_overshrt: record(OvrShrt = overshorts) # Shorts value as a positve if record_cash: record(Cash = int(c.portfolio.cash)) # Cash if record_leverage: record(Lvrg = c.account.leverage) # Leverage risk = int(max(cash_dip, overshorts)) if record_risk: record(Risk = risk) # Amount in play, maximum of shorts or cash used if risk > c.risk_hi: c.risk_hi = risk new_risk_hi = 1 if record_risk_hi: record(RiskHi = c.risk_hi) # Highest risk overall if record_pvr: # Profit_vs_Risk returns based on max amount actually spent (risk high) if c.risk_hi != 0: # Avoid zero-divide pvr_rtrn = 100 * (c.portfolio.portfolio_value - start) / c.risk_hi record(PvR = pvr_rtrn) # Profit_vs_Risk returns if record_q_return: record(QRet = q_rtrn) # Quantopian returns to compare to pvr returns curve if logging: from pytz import timezone # Python will only do this once, makes pvr() more portable if log_method == 'risk_hi' and new_risk_hi \ or log_method == 'daily' and c.date_prv != date \ or c.date_end == date \ or new_cash_low: qret = 'QRet ' + '%.1f' % q_rtrn lv = 'Lv ' + '%.1f' % lvrg if record_leverage else '' mxlv = 'MaxLv ' + '%.1f' % c.max_lvrg if record_max_lvrg else '' pvr = 'PvR ' + '%.1f' % pvr_rtrn if record_pvr else '' pnl = 'PnL ' + '%.0f' % profit_loss if record_pnl else '' csh = 'Cash ' + '%.0f' % cash if record_cash else '' csh_lw = 'CshLw ' + '%.0f' % c.cash_low if record_cash_low else '' shrt = 'Shrt ' + '%.0f' % shorts if record_shorting else '' ovrshrt = 'Shrt ' + '%.0f' % overshorts if record_overshrt else '' risk = 'Risk ' + '%.0f' % risk if record_risk else '' rsk_hi = 'RskHi ' + '%.0f' % c.risk_hi if record_risk_hi else '' minute = get_datetime().astimezone(timezone('US/Eastern')).time().minute log.info('{} {} {} {} {} {} {} {} {} {} {} {}'.format( minute, lv, mxlv, qret, pvr, pnl, csh, csh_lw, ovrshrt, shrt, risk, rsk_hi)) if c.date_end == date: # Log on last day, like cash 125199 portfolio 126890 log.info('cash {} portfolio {}'.format( int(cash), int(c.portfolio.portfolio_value))) c.date_prv = date  There was a runtime error. Development in progress to chart PvR in a Research/Notebook. Need help with this. Sometimes makes vast numbers. #backtest = get_backtest('5637da7c2fe6e438be119651') # Place these two in a different cell ... #import pyfolio as pf # above below, no need to always rerun them qrets = backtest.daily_performance.returns # Quantopian returns start = backtest.daily_performance.starting_cash[1] # Starting capital like 100000, probably a better way cash_spent = start - min(backtest.daily_performance.starting_cash) # Most spent so far, should not see the future max_cash = max(0, start - cash_spent) # Cash spent, of start or over, a logic puzzle for the mind cash_dip = abs( backtest.positions.min().amount ) # Lowest single short as a positive value, should be all shorts riskhi = max(max_cash, cash_dip) # Should be highest of cash spent or shorting pvr = (qrets * start) / riskhi # (portf - start) / riskhi pf.plot_rolling_returns(qrets, pvr) # Plot Q returns and PvR  If I'm understanding this correctly, the PvR number is like the total return per dollars used? Thanks for sharing. Are you intentionally converting shorts to int and leaving the longs measure as a float? @Minh: Yes. @Shawn: Remnant from the trim from full to minimal, fixed above now, thanks for catching it. Updated Aug 16, 2017 New full PvR code. Now all self-contained (no need for the section in initialize), produces a summary every six months or so (126 days, set to whatever you wish) and always at the end of the run, has a fix for shorting, added pvr/day and CAGR. Info to logging window when some highs are hit is on by default. def initialize(context): for i in range(1, 391): schedule_function(pvr, date_rules.every_day(), time_rules.market_open(minutes=i)) def pvr(context, data): ''' Custom chart and/or logging of profit_vs_risk returns and related information ''' import time from datetime import datetime from pytz import timezone # Python will only do once, makes this portable. # Move to top of algo for better efficiency. c = context # Brevity is the soul of wit -- Shakespeare [for readability] if 'pvr' not in c: # For real money, you can modify this to total cash input minus any withdrawals manual_cash = c.portfolio.starting_cash time_zone = 'US/Pacific' # Optionally change to your own time zone for wall clock time c.pvr = { 'options': { # # # # # # # # # # Options # # # # # # # # # # 'logging' : 0, # Info to logging window with some new maximums 'log_summary' : 126, # Summary every x days. 252/yr 'record_pvr' : 1, # Profit vs Risk returns (percentage) 'record_pvrp' : 0, # PvR (p)roportional neg cash vs portfolio value 'record_cash' : 0, # Cash available 'record_max_lvrg' : 1, # Maximum leverage encountered 'record_max_risk' : 1, # Highest risk overall 'record_shorting' : 0, # Total value of any shorts 'record_max_shrt' : 1, # Max value of shorting total 'record_cash_low' : 1, # Any new lowest cash level 'record_q_return' : 0, # Quantopian returns (percentage) 'record_pnl' : 0, # Profit-n-Loss 'record_risk' : 0, # Risked, max cash spent or shorts beyond longs+cash 'record_leverage' : 0, # End of day leverage (context.account.leverage) # All records are end-of-day or the last data sent to chart during any day. # The way the chart operates, only the last value of the day will be seen. # # # # # # # # # End options # # # # # # # # # }, 'pvr' : 0, # Profit vs Risk returns based on maximum spent 'cagr' : 0, 'max_lvrg' : 0, 'max_shrt' : 0, 'max_risk' : 0, 'days' : 0.0, 'date_prv' : '', 'date_end' : get_environment('end').date(), 'cash_low' : manual_cash, 'cash' : manual_cash, 'start' : manual_cash, 'tz' : time_zone, 'begin' : time.time(), # For run time 'run_str' : '{} to {}${}  {} {}'.format(get_environment('start').date(), get_environment('end').date(), int(manual_cash), datetime.now(timezone(time_zone)).strftime("%Y-%m-%d %H:%M"), time_zone)
}
if c.pvr['options']['record_pvrp']: c.pvr['options']['record_pvr'] = 0 # if pvrp is active, straight pvr is off
if get_environment('arena') not in ['backtest', 'live']: c.pvr['options']['log_summary'] = 1 # Every day when real money
log.info(c.pvr['run_str'])
p = c.pvr ; o = c.pvr['options'] ; pf = c.portfolio ; pnl = pf.portfolio_value - p['start']
def _pvr(c):
p['cagr'] = ((pf.portfolio_value / p['start']) ** (1 / (p['days'] / 252.))) - 1
ptype = 'PvR' if o['record_pvr'] else 'PvRp'
log.info('{} {} %/day   cagr {}   Portfolio value {}   PnL {}'.format(ptype, '%.4f' % (p['pvr'] / p['days']), '%.3f' % p['cagr'], '%.0f' % pf.portfolio_value, '%.0f' % pnl))
log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % pnl, '%.0f' % p['max_risk'], '%.1f' % p['pvr']))
log.info('  QRet {} PvR {} CshLw {} MxLv {} MxRisk {} MxShrt {}'.format('%.2f' % (100 * pf.returns), '%.2f' % p['pvr'], '%.0f' % p['cash_low'], '%.2f' % p['max_lvrg'], '%.0f' % p['max_risk'], '%.0f' % p['max_shrt']))
def _minut():
dt = get_datetime().astimezone(timezone(p['tz']))
return str((dt.hour * 60) + dt.minute - 570).rjust(3)  # (-570 = 9:31a)
date = get_datetime().date()
if p['date_prv'] != date:
p['date_prv'] = date
p['days'] += 1.0
do_summary = 0
if o['log_summary'] and p['days'] % o['log_summary'] == 0 and _minut() == '100':
do_summary = 1              # Log summary every x days
if do_summary or date == p['date_end']:
p['cash'] = pf.cash
elif p['cash'] == pf.cash and not o['logging']: return  # for speed

shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0])
new_key_hi = 0                  # To trigger logging if on.
cash       = pf.cash
cash_dip   = int(max(0, p['start'] - cash))
risk       = int(max(cash_dip, -shorts))

if o['record_pvrp'] and cash < 0:   # Let negative cash ding less when portfolio is up.
cash_dip = int(max(0, cash_dip * p['start'] / pf.portfolio_value))
# Imagine: Start with 10, grows to 1000, goes negative to -10, should not be 200% risk.

if int(cash) < p['cash_low']:             # New cash low
new_key_hi = 1
p['cash_low'] = int(cash)             # Lowest cash level hit
if o['record_cash_low']: record(CashLow = p['cash_low'])

if c.account.leverage > p['max_lvrg']:
new_key_hi = 1
p['max_lvrg'] = c.account.leverage    # Maximum intraday leverage
if o['record_max_lvrg']: record(MxLv    = p['max_lvrg'])

if shorts < p['max_shrt']:
new_key_hi = 1
p['max_shrt'] = shorts                # Maximum shorts value
if o['record_max_shrt']: record(MxShrt  = p['max_shrt'])

if risk > p['max_risk']:
new_key_hi = 1
p['max_risk'] = risk                  # Highest risk overall
if o['record_max_risk']:  record(MxRisk = p['max_risk'])

# Profit_vs_Risk returns based on max amount actually invested, long or short
if p['max_risk'] != 0: # Avoid zero-divide
p['pvr'] = 100 * pnl / p['max_risk']
ptype = 'PvRp' if o['record_pvrp'] else 'PvR'
if o['record_pvr'] or o['record_pvrp']: record(**{ptype: p['pvr']})

if o['record_shorting']: record(Shorts = shorts)             # Shorts value as a positve
if o['record_leverage']: record(Lv     = c.account.leverage) # Leverage
if o['record_cash']    : record(Cash   = cash)               # Cash
if o['record_risk']    : record(Risk   = risk)  # Amount in play, maximum of shorts or cash used
if o['record_q_return']: record(QRet   = 100 * pf.returns)
if o['record_pnl']     : record(PnL    = pnl)                # Profit|Loss

if o['logging'] and new_key_hi:
log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(),
' Lv '     + '%.1f' % c.account.leverage,
' MxLv '   + '%.2f' % p['max_lvrg'],
' QRet '   + '%.1f' % (100 * pf.returns),
' PvR '    + '%.1f' % p['pvr'],
' PnL '    + '%.0f' % pnl,
' Cash '   + '%.0f' % cash,
' CshLw '  + '%.0f' % p['cash_low'],
' Shrt '   + '%.0f' % shorts,
' MxShrt ' + '%.0f' % p['max_shrt'],
' Risk '   + '%.0f' % risk,
' MxRisk ' + '%.0f' % p['max_risk']
))
if do_summary: _pvr(c)
if get_datetime() == get_environment('end'):   # Summary at end of run
_pvr(c) ; elapsed = (time.time() - p['begin']) / 60  # minutes
log.info( '{}\nRuntime {} hr {} min'.format(p['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))


Hi Gary,

I learned to like to your risk assessment and made my own PvR. Mine is a little different as it considers leverage spikes as extra money put at risked. You can even change the leverage limit to greater than 1.0 to simulate a margin account. I am posting this for anyone else who may find this useful.

70
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
context.bought = False

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
if not context.bought:
order_target_percent(symbol('SPY'),3.0)
context.bought = True

return_per_risk(context,data)
record(leverage = context.account.leverage)

def return_per_risk(context,data):
if not '_init_rpr' in context:
context._init_rpr = True
context._leverage_limit = 1.0
context._borrowed = 0
context._min_cash = context.portfolio.starting_cash

context._min_cash = min(context._min_cash, context.portfolio.cash)
cl = context.account.leverage
pv = context.portfolio.portfolio_value

if cl > 1.0 and cl > context._leverage_limit:
context._borrowed = max(context._borrowed, cl*pv - context._leverage_limit*pv)

if context._borrowed > 0:
risked = context.portfolio.starting_cash + context._borrowed
else:
risked = context.portfolio.starting_cash - context._min_cash

rpr = 0
if risked != 0:
rpr = (context.portfolio.portfolio_value - context.portfolio.starting_cash)/(risked)*100

record(RetPerRisk = rpr)
record(MoneyRisked = risked)
record(Borrowed = context._borrowed)
There was a runtime error.

@Blue great stuff...definitely going to incorporate it into my algos. Is there a particular range that is a good PvR value? Or just the higher the better?

Also, does calling it every minute from handle data give you much more accurate (truer?) data than calling it once at the end of the day?

I tested CAGR. It works well as far as I can tell. I added some code to make an option of recording it, took me a little while figure out why it wasn't working until I changed the number of days between summaries to 1 for daily logging. I would move the calculation out of the summary code and maybe put a logging option in for it like the other PvR items.

PvR seems better then QReturn..

But instead to calculate the return based on the "max/min value" why do not consider the "avg"?? I try to explane better with my poor english:

Usually in my algo I use these 3 record line:

record(nav=context.portfolio.portfolio_value) # total money in pocket
record(cash=context.portfolio.cash) # cash
record(port=context.portfolio.positions_value) # portfolio value

Then, visually, I compare the Qreturn with the money invested day by day (wich is rappresented by the area under the "context.portfolio.positions_value" line)
Comparing similar Qreturn, i prefer the one with little "port" and equivalent large "cash"; this means more money avaible for other algos/investment.

In others world, if in 1 year my 10%Return algo use 20% only of the money avaible in the pocket, this algo is 5 time better (not exactly, of course), for me, then another algo which return the same 10% but using 100% of my pocket.

fiuuu..very difficult in english :))

Then, to conclude, because I'm not a programmer, is there a way to calculate the area under pocket/cash/position records and then to have this numerically and not just visually?
Thanks for the patience...

Just noticed the comments above. Yes the call does need to be in handle_data. Leverage and risk for example often hit peaks and fall back by end of day. Anyone's welcome to click my name to send me a direct message too (I'm likely to see it right away). For now I'd just recommend to work with it, modifying to implement your ideas.

The large text block above is being updated, last one was yesterday (noted at the top of that post). I think PvR is quite a bit better now than when it started.

I was asked to provide some guidance about this tool, what values are good and things to watch out for etc.

Adding PvR to existing code Often, I clone new algorithms and add the PvR code above, first copying their backtest code into an editor, then commenting out any record() because the custom chart is limited to five items, also sometimes commenting out any log.info lines. Then find handle_data if it exists and replace just that def line with all of the code above. You get the idea. The call to pvr() can be anywhere in handle_data as long as it isn't after any return. so it might as well be the first line, doesn't hurt. Back to the backtester, ctrl-v to paste and ctrl-b to run it. Sometimes that whole trip might take just 15 seconds I think.

What it means Mainly, the first value like PvR 0.1230 %/day is a good gage of code merit, it neutralizes the differing amounts of calendar time in backtests (although I'm starting to warm up to CAGR added recently even though I don't understand it yet). I pay a lot of attention to Cash Low (CshLw) because the closer it is to zero the more accurate the UI metrics are, like Sharpe. And also Returns of course.

Some routes/suggestions/ideas for keeping track of info

• At the end of the run, copy the last set of PvR info in from logging window along with run time (plus the line with start/stop dates, capital, and today's calendar date/time) to the top of the algo using three comment markers like this:
'''
2017-02-21 13:00 _pvr:209 INFO PvR 0.7512 %/day   cagr 0.5   Portfolio value 13315297   PnL 12315297
2017-02-21 13:00 _pvr:210 INFO   Profited 12315297 on 1062492 activated/transacted for PvR of 1159.1%
2017-02-21 13:00 _pvr:211 INFO   QRet 1231.53 PvR 1159.10 CshLw -62492 MxLv 1.01 RskHi 1062492 MxShrt 0
2017-02-21 13:00 pvr:299 INFO 2011-01-04 to 2017-02-21  $1000000 2017-02-24 21:17 US/Eastern Runtime 0 hr 8.9 min '''  • If %/day looks high, copy its value to the backtest title. Later, when looking thru numerous backtests you can find those that did best using that. • Copy the originating URL from the forums to the top of the code. • If %/day is very high, copy the algo to an editor and save it locally to a folder designated solely for keeping those. Then you can sort them high to low. Mine range from .03 to .75 for other's code that I have saved. Then some of my own are above 2.0. I use PvR so I can see what's going on to wind up with better strategies. (Frankly, some that are best were discovered via mistakes I made and I'm still scratching my head over some of them as to why they even work so well, their PvR is great so I have to accept that, I would still be lost without PvR, my Robinhood code is up over 100% in less than three months). • Also in the filename, you can copy metrics from the browser and run a macro on those, even including the paste and copy back to the clipboard (I use Notepad++), to wind up with a string like r1231.5 a0.42 b0.54 s1.35 d-33.7 for returns, alpha etc. So an entire filename might be: 0.7512 r1231.5 a0.42 b0.54 s1.35 d-33.7.py That string can also go to the top of the backtest code and/or backtest title. • Above, that result of PvR 0.7512 %/day is from SPY who loved WVF.... Here are some others that were high among those I have saved and you can look these up (they are up to .34 %/day): 'worthy of Q fund?', 'etf market rotation strategy', 'How to Build a Pairs Trading Strategy on Quantopian', 'For Robinhood trading', 'minimum variance with constraint'. I've unfortunately lost track of some good code by others from not being disciplined enough to save them all locally. Those are some ideas with a little more overview on this tool. It's not just mine, make it your own. While maybe not perfect, I'd be lost without PvR. How about a toast. To your wealth! :) Use PvR to see clearly. Sorry,still confused for PvR values. Could summarize clear simple guidelines how to choose backtest strategy through PvR output, would be appreciated.! Hi Blue, Do we need to adjust something when the algo does short only. It looks like the pvr code is not proving the right metrics. Can you please shed some light? Here is the result: 2017-04-06 22:00 _pvr:120 INFO PvR 0.0000 %/day cagr 0.5 Portfolio value 40797 PnL 30797 2017-04-06 22:00 _pvr:121 INFO Profited 30797 on 0 activated/transacted for PvR of 0.0% 2017-04-06 22:00 _pvr:122 INFO QRet 307.97 PvR 0.00 CshLw 10000 MxLv 1.41 RskHi 0 MxShrt -41912 2017-04-06 22:00 pvr:208 INFO 2014-01-02 to 2017-04-06$10000 2017-04-11 06:20 US/Eastern

Thanks!

Thanks for catching that bug. Since shorts are a negative value, this line should have had the minus sign:

risk         = int(max(cash_dip, -shorts))


The latest version had that fix, updated now in the message above with the full code (Jan 14), quite a few changes for speed.

For the starting post, you wrote "For best speed, replace c. with context. and remove c = context" in regards to the minimal pvr code.
Should I do the same for the full code from your post on 1/14/16?

Yep. Meanwhile to find out for sure how much difference in speed, could use this: https://www.quantopian.com/posts/timing-code

This could use an upgrade regarding short risk.

Overview: The main benefit of this pvr tool may be freedom for making changes during development to neutralize both cash usage and changes in shorting (the other risk), so up or down returns from a change in pipeline factors or ordering or whatever can be compared with certainty even when risk is not the same.

Rationale: Calculates a profit per dollar on the maximum risk, as both cash dip (including into margin) and max short value. An investor wants to know output vs input. The input is termed "risk" here. An assumption is made that quants will write code to use 100% of the initial capital but the reality of software development is, that's a lot more easily said than done, it's impossible to maintain always, and the process of getting to that goal can be messy. For example, add limit orders and suddenly your perfect 1.0 leverage is down to .62 max and avg .41. This pvr metric means leverage changes like that are also not such an immediate worry.

The upgrade needed: Uses short value at 100% of the shares value and could be punishing shorting too much, I understand that shorting can be done at 4x longs plus cash. A short margin call would only happen if the original short value falls below that. Both are changing of course. Shorting at 3x means wiggle-room. Higher long value allows for a greater short value dip and visa-versa. If someone who knows shorting well could be inclined to roll up their sleeves and write some code to handle shorting risk better, please do, I would send a limousine and helicopter at your disposal if I could.

If you want to tackle this, rather than online back and forth in rather tough task, ping me here, antispam, gmail and take the space out of this user, gary hawk, that's it.

I wonder if PvR is (simplifying) the Return/(abs(Max-Min) $range invested) where Max-Min are the highest peaks (positive and negative) on the whole algo time period. Right? Ideally from the investment standpoint, how much of my money is risked. The question: What constitutes risk? It's a discussion worth having. The Jan 14, 2016 version matters most. This line maybe could be improved. risk = int(max(cash_dip, -shorts))  In the beginning of this post you note the limit of the Qreturn: "The returns calculation employs a philosophy that all of your starting capital is at risk as soon as you open the account..It calculates returns based on starting capital regardless of how much you actually activated..." I totally agree with you. But I have a similar doubt about PvR, but I'm not a programmer then please apologize if I'm wrong.. Just imagine to run a 10 years algo (252 x 10 days) wich invest only 10% of the starting capital for 2519 days (with great return) and 200% for 1 day only (cause to a bug, not expected behaviour, or some dirty data). The Pvr value will be orrible and you will discard the algo...doing a mistake.. I'm wrong? Thanks for your time and for your routine wich is very preciuos to me! Right, and I've seen those scenarios too, although instead of discarding the algo I'd say it could just make the margin problem visible in the custom chart to be able to address it. PvRp (proportional) above was for that type of thing, maybe take a look and you might find a way to modify and use it, but I discontinued using that myself. What I was focused on at the time was margin early on that could be like 200% of the portfolio and yet not much compared to the ending value with a lot of gain over time, to avoid a harsh penalty from early margin. In the contest and fund with recent leverage limits near 1, the Quantopian returns and PvR are always closer to each other now versus the once-upon-a-time idea floated of maybe up to 6x leverage applied to fund algos, This profit per dollar risked value was a bit more of thing back then because by neutralizing margin it could for example make it clear that a 1.1x algo was actually better value even if a 6x appeared to be, providing the apples-to-apples comparison. (Then there's leverage from shorting of course, all a rather complex picture). Toward the investor two most basic concerns: How much in, how much out. On what constitutes risk, with margin and shorting the brokerage has certain requirements. For improvements I'd have to invite those with experience in them including maintenance margin requirements. But just thinking out loud, is risk simply abs(short_value) + long_value, the leverage equation numerator? Maybe try this ...  shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0]) longs = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount > 0]) risk = abs(shorts) + longs # from this current risk, max risk is then used, for profit per max risk  If I could wave a magic wand I'd apply this on the top ~50 of each past contest, in place of returns as the basis for metrics calculations, hence scoring, and different algos would rise to the top. Then see if it is a better or worse evaluator/predictor using out-of-sample. Would the winners using this have performed better? Surely a test worth considering. Glad you find this useful. I feel lost without it. :/ Here's a version that separates out the charting, scheduled to end of day aiming for efficiency. And uses abs(shorts) + longs. Lv is set to 1.2. 19 Loading... Total Returns -- Alpha -- Beta -- Sharpe -- Sortino -- Max Drawdown -- Benchmark Returns -- Volatility --  Returns 1 Month 3 Month 6 Month 12 Month  Alpha 1 Month 3 Month 6 Month 12 Month  Beta 1 Month 3 Month 6 Month 12 Month  Sharpe 1 Month 3 Month 6 Month 12 Month  Sortino 1 Month 3 Month 6 Month 12 Month  Volatility 1 Month 3 Month 6 Month 12 Month  Max Drawdown 1 Month 3 Month 6 Month 12 Month ''' https://www.quantopian.com/posts/universal-pipeline-for-experimentation-and-learning ''' from quantopian.pipeline import Pipeline from quantopian.pipeline.data import Fundamentals from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline.filters import Q500US, Q1500US, Q3000US from quantopian.pipeline.factors import CustomFactor from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.classifiers.morningstar import Sector import quantopian.optimize as opt import statsmodels.api as sm import numpy as np import pandas as pd def make_pipeline(c): m = VolumeMin(window_length=42).top(400) & Sector().notnull() & Q1500US() # mask # Adding to mask, excluding values around the middle, ~ means not. a = EBITPerEV (mask=m); m &= ~(a.percentile_between(40, 60)) #b = Momentum (mask=m); m &= ~(b.percentile_between(40, 60)) #c = ROE1 (mask=m); m &= ~(c.percentile_between(40, 60)) d = Div_Yield (mask=m); m &= ~(d.percentile_between(40, 60)) #e = Slope (mask=m); m &= ~(e.percentile_between(40, 60)) #f = Price_Earnings (mask=m); m &= ~(f.percentile_between(40, 60)) return Pipeline( screen = m, columns = { 'a': a, #'b': b, #'c': c, 'd': -d, # sometimes just tossing a minus sign in front makes for higher outuput #'e': e, #'f': f, 'sector': Sector(mask=m), }) def initialize(context): c = context c.long_shrt_num = 100 c.nullzone = .2 c.headroom = 0 c.log_universe = 2 # Number of days to log universe in before_trading_start. c.cannot_hold = [] # Securities you want optimize to not order or hold onto. #set_commission(commission.PerShare(cost=0.001, min_trade_cost=0)) # default now? attach_pipeline(make_pipeline(c), 'p') use_optimize = 1 if use_optimize: schedule_function(do_opt, date_rules.week_start(2), time_rules.market_open(minutes=1)) else: schedule_function(do_shrts, date_rules.month_start(), time_rules.market_open(minutes=1 )) schedule_function(cancel_oos, date_rules.month_start(), time_rules.market_open(minutes=9)) schedule_function(do_longs, date_rules.month_start(), time_rules.market_open(minutes=10)) schedule_function(cancel_oos, date_rules.every_day(), time_rules.market_close()) # This is included for the indication of profit per dollar invested since # different factors might not always invest the same amount. Apples-to-apples comparison. do_pvr = 1 if do_pvr: for i in range(1, 391): schedule_function(pvr, date_rules.every_day(), time_rules.market_open(minutes=i)) schedule_function(pvr_chart, date_rules.every_day(), time_rules.market_close()) def before_trading_start(context, data): c = context o = pipeline_output('p').dropna().drop(c.cannot_hold, errors='ignore') c.ori = o if not len(o): return num = int(min(c.long_shrt_num, len(o)/2)) ; nullzone = c.nullzone if 'score' in o.columns: o['score_ori'] = o['score'] score = 0 ; valid = 0 for col in o.columns: if not np.issubdtype(o[col][0].dtype, np.number): continue # skip True/False columns if col == 'sector': continue o[col] += abs(o[col].min()) # shift to positive valid = 1 if not valid: log.info('Found no columns with numbers in before_trading_start() pipe') return for col in o.columns: if not np.issubdtype(o[col][0].dtype, np.number): continue if col == 'sector': continue o[col] /= o[col].sum() # normalize if not score: o['score'] = o[col] # combine values else: o['score'] += o[col] score = 1 o['score'] = o['score'].rank() o['score'] /= o['score'].sum() o['score'] -= o['score'].mean() o = o.dropna() mid = o.score.rank().mean(); sliver = (nullzone * mid) longs = o[(o.score.rank() > mid + sliver)].head(num) shrts = o[(o.score.rank() < mid - sliver)].tail(len(longs)) c.longs = longs['score'] / longs['score'].sum() c.shrts = -shrts['score'] / shrts['score'].sum() # Log pipe length & some long, short details a number of times. if c.log_universe >= 0: lng = c.longs.sort_values(ascending=False) shs = c.shrts.sort_values(ascending=False) log.info('pipe len {}'.format(len(c.ori))) log.info('lng {} top {} {} bottom {} {}'.format(len(lng), lng.index[0].symbol, '%5f' % lng[0], lng.index[-1].symbol, '%5f' % lng[-1])) log.info('shs {} top {} {} bottom {} {}'.format(len(shs), shs.index[0].symbol, '%5f' % shs[0], shs.index[-1].symbol, '%5f' % shs[-1])) c.log_universe -= 1 c.actives = c.longs.index.union(c.shrts.index) c.pipe = longs.append(shrts) def do_opt(context, data): order_optimal_portfolio( # For objective, simply use naive ranks as an alpha coefficient # and try to maximize that alpha. # # This is a **very** naive model. Since alphas are so widely spread out, # should expect to always allocate the maximum amount of long/short # capital to assets with high/low ranks. # # A more sophisticated model would apply some re-scaling here to try to generate # more meaningful predictions of future returns. objective = opt.MaximizeAlpha(context.pipe.score), constraints=[ # Constrain gross leverage to 1.0 or less. This means that the absolute # value of long and short positions should not exceed the value of portfolio. opt.MaxGrossExposure(1.2), # Constrain individual position size to no more than a fixed percentage # of portfolio. Because alphas are so widely distributed, # should expect to end up hitting this max for every stock in universe. opt.PositionConcentration.with_equal_bounds(-.015, .015), opt.DollarNeutral(), # Same amount of capital to long and short positions. opt.NetGroupExposure.with_equal_bounds( # Net leverage in each sector. labels = context.pipe.sector, min = -0.0001, max = 0.0001, ), opt.CannotHold(context.cannot_hold) ], ) def close(context, data): for s in context.portfolio.positions: if s in context.actives: continue if not data.can_trade(s): continue order_target(s, 0) def do_shrts(context, data): c = context cancel_oos(context, data) c.headroom = max(0, .5 * c.portfolio.cash) for s in c.shrts.index: if get_open_orders(s): continue if not data.can_trade(s): continue order_target_value(s, c.shrts[s] * c.headroom) def do_longs(context, data): c = context cancel_oos(c, data) for s in c.longs.index: if get_open_orders(s): continue if not data.can_trade(s): continue order_target_value(s, c.longs[s] * c.headroom) def cancel_oos(context, data): # Primarily to prevent the logging of unfilled orders at end of day oo = get_open_orders() # Can also be use at any time to limit partial fills. for s in oo: for o in oo[s]: # Next line can be beneficial if midday cancel_oos() in use. #if cls_opn_crs(c, o) in [0, 2]: continue # closing, leave it alone cancel_order(o.id) def cls_opn_crs(c, o): # c = context o = order object # Whether order is closing, opening or crossover (short to long or reverse) # https://www.quantopian.com/posts/order-state-on-partial-fills-close-open-or-crossover if c.portfolio.positions[o.sid].amount * o.amount < 0: # close or crossover if abs(c.portfolio.positions[o.sid].amount) < abs(o.amount - o.filled): if abs(c.portfolio.positions[o.sid].amount) - abs(o.filled) < 0: return 3 # crossed 0 shares and now opening else: return 2 # cross closing else: return 0 # closing else: return 1 # opening ''' Fundamentals, Factors ... ''' def nanfill(_in): # From https://stackoverflow.com/questions/41190852/most-efficient-way-to-forward-fill-nan-values-in-numpy-array # Includes a way to count nans on webpage at # https://www.quantopian.com/posts/forward-filling-nans-in-pipeline #return _in # uncomment to not run the code below mask = np.isnan(_in) idx = np.where(~mask,np.arange(mask.shape[1]),0) np.maximum.accumulate(idx,axis=1, out=idx) _in[mask] = _in[np.nonzero(mask)[0], idx[mask]] return _in def beta(ts, benchmark, benchmark_var): return np.cov(ts, benchmark)[0, 1] / benchmark_var def slope(in_): # Slope of regression line. Make sure input has no nans or screen its output later # https://www.quantopian.com/posts/slope-calculation return sm.OLS(in_, sm.add_constant(range(-len(in_) + 1, 1))).fit().params[-1] # slope def curve(_in): # ndarray see https://www.quantopian.com/posts/curve-calculation return sm.OLS(_in[-len(_in)/2:], sm.add_constant(range(-len(_in[-len(_in)/2:]) + 1, 1))).fit().params[-1] - sm.OLS(_in[0:len(_in)/2], sm.add_constant(range(-len(_in[0:len(_in)/2]) + 1, 1))).fit().params[-1] class AvgDailyDollarVolumeTraded(CustomFactor): inputs = [USEquityPricing.close, USEquityPricing.volume] ; window_length = 42 def compute(self, today, assets, out, close, volume): volume = nanfill(volume) close = nanfill(close) out[:] = np.mean(close * volume, axis=0) class ATR(CustomFactor): inputs = [USEquityPricing.close,USEquityPricing.high,USEquityPricing.low] window_length = 21 def compute(self, today, assets, out, close, high, low): close = nanfill(close) high = nanfill(high) low = nanfill(low) hml = high - low hmpc = np.abs(high - np.roll(close, 1, axis=0)) lmpc = np.abs(low - np.roll(close, 1, axis=0)) tr = np.maximum(hml, np.maximum(hmpc, lmpc)) atr = np.mean(tr[1:], axis=0) out[:] = atr class Beta(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 60 def compute(self, today, assets, out, close): close = nanfill(close) returns = pd.DataFrame(close, columns=assets).pct_change()[1:] spy_returns = returns[sid(8554)] spy_returns_var = np.var(spy_returns) out[:] = returns.apply(beta, args=(spy_returns,spy_returns_var,)) class CashReturn(CustomFactor): inputs = [Fundamentals.cash_return] ; window_length = 42 def compute(self, today, assets, out, cash_return): cash_return = nanfill(cash_return) out[:] = np.mean(cash_return, axis=0) class CashReturnSlope(CustomFactor): inputs = [Fundamentals.cash_return] ; window_length = 5 def compute(self, today, assets, out, cash_return): cash_return = nanfill(cash_return) out[:] = slope(cash_return) class CrossSectionalMomentum(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 252 def compute(self, today, assets, out, closes): closes = nanfill(closes) closes = pd.DataFrame(closes) R = (closes / closes.shift(100)) out[:] = (R.T - R.T.mean()).T.mean() class Curve(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 6 def compute(self, today, assets, out, closes): closes = nanfill(closes) out[:] = curve(closes) class Div_Yield(CustomFactor): inputs = [Fundamentals.trailing_dividend_yield]; window_length = 12 def compute(self, today, assets, out, d_y): d_y = nanfill(d_y) out[:] = d_y[-1] class Downward(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 5 def compute(self, today, assets, out, close): close = nanfill(close) ratio_avg = (close[-1] / np.mean(close, axis=0)) out[:] = ((close[-1] / close[0]) + ratio_avg) class EBITPerEV(CustomFactor): inputs = [Fundamentals.ebit, Fundamentals.enterprise_value]; window_length = 12 def compute(self, today, assets, out, ebit, ev): ebit = nanfill(ebit) ev = nanfill(ev) out[:] = ebit[-1] / ev[-1] class Liquidity(CustomFactor): inputs = [USEquityPricing.volume, Fundamentals.shares_outstanding] ; window_length = 12 def compute(self, today, assets, out, volume, shares): volume = nanfill(volume) shares = nanfill(shares) out[:] = volume[-1] / shares[-1] class MACD(CustomFactor): inputs = [USEquityPricing.close] window_length = 60 def ema(self, data, window): # Initial value for EMA is taken as trialing SMA import numpy as np c = 2.0 / (window + 1) ema = np.mean(data[-(2*window)+1:-window+1], axis=0) for value in data[-window+1:]: ema = (c * value) + ((1 - c) * ema) return ema def compute(self, today, assets, out, close): close = nanfill(close) fema = self.ema(close, 12) sema = self.ema(close, 26) macd_line = fema - sema macd = [] macd.insert(0, self.ema(close,12) - self.ema(close,26)) for i in range(1,15, 1): macd.insert(0, self.ema(close[:-i],12) - self.ema(close[:-i],26)) signal = self.ema(macd,9) out[:] = macd_line - signal class MaxGap(CustomFactor): # the biggest absolute overnight gap in the previous 90 sessions inputs = [USEquityPricing.close] ; window_length = 90 def compute(self, today, assets, out, close): close = nanfill(close) abs_log_rets = np.abs(np.diff(np.log(close),axis=0)) max_gap = np.max(abs_log_rets, axis=0) out[:] = max_gap class MedianValue(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 42 def compute(self, today, assets, out, close): close = nanfill(close) out[:] = np.nanmedian(close, axis=0) class Momentum(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 20 def compute(self, today, assets, out, close): close = nanfill(close) out[:] = close[-1] / close[0] class Price_Earnings(CustomFactor): inputs = [Fundamentals.pe_ratio] ; window_length = 3 def compute(self, today, assets, out, pe): pe = nanfill(pe) out[:] = pe[-1] class Price_to_TTM_Cashflows(CustomFactor): inputs = [Fundamentals.pcf_ratio] ; window_length = 12 def compute(self, today, assets, out, pcf): pcf = nanfill(pcf) out[:] = -pcf[-1] class Price_to_TTM_Sales(CustomFactor): inputs = [Fundamentals.ps_ratio] ; window_length = 12 def compute(self, today, assets, out, ps): ps = nanfill(ps) out[:] = -ps[-1] class PriceChange(CustomFactor): # Possible missed split in data inputs = [USEquityPricing.close] ; window_length = 2 def compute(self, today, assets, out, close): close = nanfill(close) out[:] = close[-1] / close[0] class PriceToBook(CustomFactor): inputs = [Fundamentals.pb_ratio] ; window_length = 12 def compute(self, today, assets, out, ptb): ptb = nanfill(ptb) out[:] = -ptb[-1] class ProfitPerAssets(CustomFactor): inputs = [Fundamentals.gross_profit, Fundamentals.total_assets]; window_length = 12 def compute(self, today, assets, out, gross_profit, total_assets): gross_profit = nanfill(gross_profit) total_assets = nanfill(total_assets) out[:] = gross_profit[-1] / total_assets[-1] class Quality1(CustomFactor): inputs = [Fundamentals.gross_profit, Fundamentals.total_assets]; window_length = 12 def compute(self, today, assets, out, gross_profit, total_assets): gross_profit = nanfill(gross_profit) total_assets = nanfill(total_assets) out[:] = gross_profit[-1] / total_assets[-1] class Quality2(CustomFactor): inputs = [Fundamentals.gross_profit, Fundamentals.total_assets] window_length = 24 def compute(self, today, assets, out, gross_profit, total_assets): norm = gross_profit / total_assets norm = nanfill(norm) out[:] = (norm[-1] - np.mean(norm, axis=0)) / np.std(norm, axis=0) class Revenue(CustomFactor): inputs = [Fundamentals.total_revenue] ; window_length = 12 def compute(self, today, assets, out, revenue): revenue = nanfill(revenue) out[:] = revenue[-1] class ROE1(CustomFactor): inputs = [Fundamentals.roe] ; window_length = 77 def compute(self, today, assets, out, roe): roe = nanfill(roe) out[:] = np.mean(roe[-5:], axis=0) - np.mean(roe, axis=0) class ROE2(CustomFactor): inputs = [Fundamentals.roe] ; window_length = 11 def compute(self, today, assets, out, roe): roe = nanfill(roe) out[:] = roe[-1] class ROIC(CustomFactor): inputs = [Fundamentals.roic] ; window_length = 12 def compute(self, today, assets, out, roic): roic = nanfill(roic) out[:] = roic[-1] class Slope(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 10 def compute(self, today, assets, out, closes): closes = nanfill(closes) out[:] = slope(closes) class Value1(CustomFactor): inputs = [Fundamentals.ebit, Fundamentals.enterprise_value]; window_length = 12 def compute(self, today, assets, out, ebit, ev): ebit = nanfill(ebit) ev = nanfill(ev) out[:] = ebit[-1] / ev[-1] class Value2(CustomFactor): inputs = [Fundamentals.book_value_yield, Fundamentals.sales_yield, Fundamentals.fcf_yield] window_length = 12 def compute(self, today, assets, out, book_value, sales, fcf): book_value = nanfill(book_value) sales = nanfill(sales) fcf = nanfill(fcf) value_table = pd.DataFrame(index=assets) value_table['book_value'] = book_value[-1] value_table['sales'] = sales[-1] value_table['fcf'] = fcf[-1] out[:] = value_table.rank().mean(axis=1) class Volatility1(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 252 def compute(self, today, assets, out, close): close = nanfill(close) close = pd.DataFrame(data=close, columns=assets) # Rank largest is best, need to invert the sdev. out[:] = 1 / np.log(close).diff().std() class Volatility2(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 252 def compute(self, today, assets, out, close): close = nanfill(close) close = pd.DataFrame(data=close, columns=assets) # Rank largest is best, need to invert the sdev. out[:] = np.log(close).diff().std() class Volatility3(CustomFactor): inputs = [USEquityPricing.close] ; window_length = 122 def compute(self, today, assets, out, close): close = nanfill(close) # 6-month volatility, starting before the five-day mean reversion period daily_returns = np.log(close[1:-6]) - np.log(close[0:-7]) out[:] = daily_returns.std(axis = 0) class VolumeMinimum(CustomFactor): inputs = [USEquityPricing.volume] ; window_length = 42 def compute(self, today, assets, out, volume): volume = nanfill(volume) out[:] = np.min(np.array(volume), axis=0) #.astype(int) class VolumeMin(CustomFactor): inputs = [USEquityPricing.volume] ; window_length = 42 def compute(self, today, assets, out, volume): volume = nanfill(volume) out[:] = np.min(volume, axis=0) # & VolumeMin().top(200) class VolumeMax(CustomFactor): inputs = [USEquityPricing.volume] ; window_length = 42 def compute(self, today, assets, out, volume): volume = nanfill(volume) out[:] = np.max(volume, axis=0) class VolumeMean(CustomFactor): inputs = [USEquityPricing.volume] ; window_length = 42 def compute(self, today, assets, out, volume): volume = nanfill(volume) out[:] = np.mean(volume, axis=0) ''' Extracted from https://www.quantopian.com/help/fundamentals (count: 929) accession_number accession_number accounts_payable accounts_receivable accretion_on_preferred_stock accrued_interest_receivable accrued_investment_income accrued_liabilities_total accrued_preferred_stock_dividends accruedand_deferred_income accruedand_deferred_income_current accruedand_deferred_income_non_current accumulated_depreciation acquired_in_process_rn_d acquired_in_process_rn_d_income acquiredin_process_rn_d_income_banks acquisition_expense additional_paid_in_capital adjusted_geography_segment_data adjustmentsfor_undistributed_profitsof_associates administrative_expense advance_from_federal_home_loan_banks advancesfrom_central_banks agency_fees agency_fees_and_commissions allowance_for_doubtful_accounts_receivable allowance_for_funds_construction allowance_for_loans_and_lease_losses allowance_for_notes_receivable allowances_for_construction amortization amortization amortization_of_deferred_acquisition_costs amortization_of_financing_costs_and_discounts amortization_of_intangibles amortization_of_intangibles amortization_of_securities asset_impairment_charge assets_held_for_sale assets_of_discontinued_operations assets_turnover available_for_sale_securities average_dilution_earn bank_acceptance_executed_and_outstanding bank_indebtedness bank_loan bank_loans_current bank_loans_non_current bank_loans_total bank_owned_life_insurance basic_accounting_change basic_average_shares basic_continuous_operations basic_discontinuous_operations basic_eps basic_eps_other_gains_losses basic_extraordinary beginning_cash_position book_value_per_share book_value_yield buildings_and_improvements business_country_id buy_back_yield calls_maturities_of_maturity_securities cannaics cap_ex_reported capital_expenditure capital_lease_obligations capital_stock capitaln_business_taxes cash cash_advancesand_loans_madeto_other_parties cash_and_cash_equivalents cash_and_due_from_banks cash_cash_equivalents_and_federal_funds_sold cash_cash_equivalents_and_marketable_securities cash_conversion_cycle cash_dividends_paid cash_equivalents cash_flow_from_continuing_financing_activities cash_flow_from_continuing_investing_activities cash_flow_from_continuing_operating_activities cash_flow_from_discontinued_operation cash_flowsfromusedin_operating_activities_direct cash_from_discontinued_financing_activities cash_from_discontinued_investing_activities cash_from_discontinued_operating_activities cash_receiptsfrom_paymentsfor_financial_derivative_contracts cash_receiptsfrom_repaymentof_advancesand_loans_madeto_other_parties cash_return cash_value_of_life_insurance cashand_balanceswith_central_banks casualty_claims ceded_premiums ceded_unearned_premiums cf_yield cfo_per_share change_in_account_payable change_in_accrued_expense change_in_accrued_investment_income change_in_deferred_acquisition_costs change_in_deferred_charges change_in_dividend_payable change_in_federal_funds_and_securities_sold_for_repurchase change_in_funds_withheld change_in_income_tax_payable change_in_interest_payable change_in_inventory change_in_loans change_in_loss_and_loss_adjustment_expense_reserves change_in_other_current_assets change_in_other_current_liabilities change_in_other_working_capital change_in_payable change_in_payables_and_accrued_expense change_in_premiums_receivable change_in_prepaid_assets change_in_prepaid_reinsurance_premiums change_in_receivables change_in_reinsurance_receivable_on_paid_losses change_in_reinsurance_recoverable_on_paid_and_unpaid_losses change_in_reinsurance_recoverable_on_unpaid_losses change_in_restricted_cash change_in_tax_payable change_in_trading_account_securities change_in_unearned_premiums change_in_unearned_premiums_ceded change_in_working_capital changein_accrued_income changein_deferred_income changein_insurance_contract_assets changein_investment_contract changein_reinsurance_receivables changes_in_account_receivables changes_in_cash changesin_inventoriesof_finished_goodsand_workin_progress cik claims_outstanding claimsand_paid_incurred classesof_cash_payments classesof_cash_receiptsfrom_operating_activities com_tre_sha_num commercial_loan commercial_paper commission_expenses commission_revenue common_equity_to_assets common_stock common_stock_dividend_paid common_stock_equity common_stock_issuance common_stock_payments common_stocks_available_for_sale common_utility_plant company_status construction_grants construction_in_progress consumer_loan contact_email continuing_and_discontinued_basic_eps continuing_and_discontinued_diluted_eps convertible_loans_current cost_of_revenue country_id credit_card credit_losses_provision credit_risk_provisions cumulative_effect_of_accounting_change cumulative_effect_of_accounting_change currency_id current_accrued_expenses current_assets current_capital_lease_obligation current_debt current_debt_and_capital_lease_obligation current_deferred_assets current_deferred_liabilities current_deferred_revenue current_deferred_taxes_assets current_deferred_taxes_liabilities current_liabilities current_notes_payable current_provisions current_ratio customer_acceptances customer_accounts days_in_inventory days_in_payment days_in_sales debt_securities debt_securitiesin_issue debt_total debtto_assets decreasein_interest_bearing_depositsin_bank deferred_acquisition_costs deferred_assets deferred_cost_current deferred_costs deferred_financing_costs deferred_income_tax deferred_policy_acquisition_costs deferred_tax deferred_tax_assets deferred_tax_liabilities_total defined_pension_benefit depletion depletion depositary_receipt_ratio deposits_madeunder_assumed_reinsurance_contract deposits_receivedunder_ceded_insurance_contract depositsby_bank depreciation depreciation depreciation_amortization_depletion depreciation_amortization_depletion depreciation_and_amortization depreciation_and_amortization derivative_assets derivative_product_liabilities development_expense diluted_accounting_change diluted_average_shares diluted_cont_eps_growth diluted_continuous_operations diluted_discontinuous_operations diluted_eps diluted_eps_growth diluted_eps_other_gains_losses diluted_extraordinary distribution_costs dividend_income dividend_paid_cfo dividend_per_share dividend_rate dividend_received_cfo dividend_yield dividends_paid_direct dividends_payable dividends_received_cfi dividends_received_direct domestic_sales dps_growth earning_loss_of_equity_investments earning_yield earnings_from_equity_interest earnings_losses_from_equity_investments earningsfrom_equity_interest_net_of_tax ebit ebit_margin ebitda ebitda_margin effect_of_exchange_rate_changes electric_revenue electric_utility_plant employee_benefits end_cash_position enterprise_value equipment equity_attributable_to_owners_of_parent equity_investments equity_per_share_growth equity_shares_investments esop_debt_guarantee ev_to_ebitda exceptional_items excess_tax_benefit_from_stock_based_compensation exchange_id excise_taxes exploration_development_and_mineral_property_lease_expenses extraordinary_items facilities_and_other fcf_per_share fcf_ratio fcf_yield federal_funds_purchased federal_funds_purchased_and_securities_sold_under_agreement_to_repurchase federal_funds_sold federal_funds_sold_and_securities_purchase_under_agreements_to_resell federal_home_loan_bank_stock fee_revenue_and_other_income fees fees_and_commissions feesand_commission_expense feesand_commission_income file_date file_date finance_lease_receivables_current finance_lease_receivables_non_current financial_assets financial_assets_designatedas_fair_value_through_profitor_loss_total financial_health_grade financial_instruments_sold_under_agreements_to_repurchase financial_leverage financial_liabilities_current financial_liabilities_designatedas_fair_value_through_profitor_loss_total financial_liabilities_measuredat_amortized_cost_total financial_liabilities_non_current financing_cash_flow finished_goods fiscal_year_end fix_assets_turonver fixed_maturities_available_for_sale fixed_maturities_held_to_maturity fixed_maturities_trading fixed_maturity_investments flight_fleet_vehicle_and_related_equipments foreclosed_assets foreign_component foreign_currency_translation_adjustments foreign_exchange_trading_gains foreign_sales form_type form_type forward_dividend_yield forward_earning_yield forward_pe_ratio free_cash_flow fuel fuel_and_natural_gas fuel_and_purchase_power future_policy_benefits gain_loss_on_investment_securities gain_loss_on_sale_of_business gain_loss_on_sale_of_ppe gain_losson_derecognitionof_available_for_sale_financial_assets gain_losson_derecognitionof_non_current_assets_not_heldfor_sale_total gain_losson_financial_instruments_designatedas_cash_flow_hedges gain_losson_saleof_assets gain_on_sale_of_business gain_on_sale_of_ppe gain_on_sale_of_security gainon_extinguishmentof_debt gainon_investment_properties gainon_redemptionand_extinguishmentof_debt gainon_saleof_investment_property gainon_saleof_loans gains_loss_on_disposal_of_discontinued_operations gains_losses_not_affecting_retained_earnings gas_revenue general_account_assets general_and_administrative_expense general_expense general_partnership_capital goodwill goodwill_and_other_intangible_assets gross_accounts_receivable gross_dividend_payment gross_loan gross_margin gross_notes_receivable gross_ppe gross_premiums_written gross_profit growth_grade growth_score guaranteed_investment_contract headquarter_address_line1 headquarter_address_line2 headquarter_address_line3 headquarter_address_line4 headquarter_city headquarter_country headquarter_fax headquarter_homepage headquarter_phone headquarter_postal_code headquarter_province hedging_assets_current hedging_assets_non_current hedging_liabilities_current hedging_liabilities_non_current held_to_maturity_securities impairment_loss_reversal_recognizedin_profitor_loss impairment_losses_reversals_financial_instruments_net impairment_of_capital_assets impairmentof_capital_assets_income income_tax_paid_supplemental_data income_tax_payable income_taxes_refund_paid_cff income_taxes_refund_paid_cfi incomefrom_associatesand_other_participating_interests incomefrom_sharesin_subsidiaries_group_undertakings increase_decrease_in_deposit increase_decrease_in_net_unearned_premium_reserves increase_decreasein_lease_financing increasein_interest_bearing_depositsin_bank increasein_lease_financing industry_template_code insurance_and_claims insurance_and_premiums insurance_contract_assets insurance_contract_liabilities insurance_funds_non_current insurance_payables insurance_receivables interest_bearing_borrowings_current interest_bearing_borrowings_non_current interest_bearing_borrowings_total interest_bearing_deposits_assets interest_bearing_deposits_liabilities interest_coverage interest_credited_on_policyholder_deposits interest_expense interest_expense_for_capitalized_lease_obligations interest_expense_for_deposit interest_expense_for_federal_funds_sold_and_securities_purchase_under_agreements_to_resell interest_expense_for_long_term_debt interest_expense_for_long_term_debt_and_capital_securities interest_expense_for_short_term_debt interest_expense_non_operating interest_expense_operating interest_income interest_income_after_provision_for_loan_loss interest_income_from_deposits interest_income_from_federal_funds_sold_and_securities_purchase_under_agreements_to_resell interest_income_from_interest_bearing_deposits interest_income_from_investment_securities interest_income_from_leases interest_income_from_loans interest_income_from_loans_and_lease interest_income_from_other_money_market_investments interest_income_from_securities interest_income_from_trading_account_securities interest_income_non_operating interest_income_operating interest_income_other_operating_income interest_paid_cff interest_paid_cfo interest_paid_direct interest_paid_supplemental_data interest_payable interest_received_cfi interest_received_cfo interest_received_direct interestand_similar_income inventories_adjustments_allowances inventory inventory_turnover invested_capital investing_cash_flow investment_banking_profit investment_contract_liabilities investment_id investment_properties investment_tax_credits investmentin_financial_assets investments_and_advances investments_in_affiliates_subsidiaries_associates_and_joint_ventures investments_in_other_ventures_under_equity_method investments_in_variable_interest_entity investmentsin_associatesat_cost investmentsin_joint_venturesat_cost investmentsin_subsidiariesat_cost ipo_date is_depositary_receipt is_direct_invest is_dividend_reinvest is_primary_share issuance_of_capital_stock issuance_of_debt issuance_paymentof_other_equity_instruments_net issue_expenses land_and_improvements leases legal_name legal_name_language_code liabilities_heldfor_sale_current liabilities_heldfor_sale_non_current liabilities_heldfor_sale_total liabilities_of_discontinued_operations life_annuity_premiums limited_partnership limited_partnership_capital line_of_credit loan_capital loans_held_for_resell loans_held_for_sale loans_receivable loansand_advancesto_bank loansand_advancesto_customer long_term_capital_lease_obligation long_term_contracts long_term_debt long_term_debt_and_capital_lease_obligation long_term_debt_equity_ratio long_term_debt_issuance long_term_debt_payments long_term_debt_total_capital_ratio long_term_investments long_term_provisions loss_adjustment_expense loss_and_loss_adjustment_expected_incurred losson_extinguishmentof_debt machinery_furniture_equipment maintenance_and_repairs market_cap marketing_expense materials_and_supplies mineral_properties minimum_pension_liabilities minority_interest minority_interest minority_interests misc_other_special_charges miscellaneous_other_operating_income money_market_investments morningstar_economy_sphere_code morningstar_industry_code morningstar_industry_group_code morningstar_sector_code mortgage_and_consumerloans mortgage_loan nace naics natural_gas_fuel_and_other natural_resource_assets negative_goodwill_immediately_recognized net_assets net_business_purchase_and_sale net_capital_expenditure_disposals net_common_stock_issuance net_debt net_foreign_currency_exchange_gain_loss net_foreign_exchange_gain_loss net_income net_income net_income_common_stockholders net_income_cont_ops_growth net_income_continuous_operations net_income_discontinuous_operations net_income_extraordinary net_income_from_continuing_and_discontinued_operation net_income_from_continuing_operation_net_minority_interest net_income_from_continuing_operations net_income_from_other_gains_losses net_income_from_tax_loss_carryforward net_income_growth net_income_including_noncontrolling_interests net_intangibles_purchase_and_sale net_interest_income net_investment_income net_investment_purchase_and_sale net_issuance_payments_of_debt net_loan net_long_term_debt_issuance net_margin net_non_operating_interest_income_expense net_occupancy_expense net_operating_interest_income_expense net_other_financing_charges net_other_investing_changes net_other_unrealized_gain_loss net_outward_loans net_policyholder_benefits_and_claims net_ppe net_ppe_purchase_and_sale net_preferred_stock_issuance net_premiums_written net_proceeds_payment_for_loan net_realized_gain_loss_on_investments net_short_term_debt_issuance net_tangible_assets net_technology_purchase_and_sale net_unrealized_gain_loss_foreign_currency net_unrealized_gain_loss_investments net_utility_plant non_current_accounts_receivable non_current_accrued_expenses non_current_deferred_assets non_current_deferred_liabilities non_current_deferred_revenue non_current_deferred_taxes_assets non_current_deferred_taxes_liabilities non_current_note_receivables non_current_pension_and_other_postretirement_benefit_plans non_current_prepaid_assets non_interest_bearing_borrowings_current non_interest_bearing_borrowings_non_current non_interest_bearing_deposits non_interest_expense non_interest_income non_operating_expenses non_operating_income non_recurring_operation_expense normalized_basic_eps normalized_diluted_eps normalized_income normalized_net_profit_margin notes_receivable occupancy_and_equipment operating_cash_flow operating_expense operating_gains_losses operating_income operating_revenue operating_taxesn_licenses operation_and_maintenance operation_income_growth operation_margin operation_revenue_growth3_month_avg ordinary_shares_number other_adjustmentsfor_which_cash_effects_are_investingor_financing_cash_flow other_assets other_capital_stock other_cash_paymentsfrom_operating_activities other_cash_receiptsfrom_operating_activities other_current_assets other_current_borrowings other_current_liabilities other_customer_services other_deferred_costs other_deposits other_equity_adjustments other_equity_interest other_financing other_gain_loss_from_disposition_of_discontinued_operations other_impairment_of_capital_assets other_income_expense other_intangible_assets other_interest_earning_assets other_interest_expense other_interest_income other_inventories other_invested_assets other_liabilities other_loan_assets other_loans_current other_loans_non_current other_loans_total other_non_cash_items other_non_current_assets other_non_current_liabilities other_non_interest_expense other_non_interest_income other_non_operating_expenses other_non_operating_income other_non_operating_income_expenses other_operating_expenses other_operating_income_total other_operating_inflows_outflowsof_cash other_operating_revenue other_payable other_properties other_real_estate_owned other_receivables other_reserves other_short_term_investments other_special_charges other_staff_costs other_taxes other_write_down other_write_off otherunder_preferred_stock_dividend participating_policyholder_equity patents payables payables_and_accrued_expenses payment_for_loans payment_turnover paymentof_bills paymentsfor_premiumsand_claims_annuitiesand_other_policy_benefits paymentsof_other_equity_instruments paymentson_behalfof_employees paymentsto_acquire_held_to_maturity_investments paymentsto_suppliersfor_goodsand_services payout_ratio pb_ratio pcf_ratio pe_ratio peg_payback peg_ratio pension_and_employee_benefit_expense pension_and_other_postretirement_benefit_plans_total pension_costs pensionand_other_post_retirement_benefit_plans_current period_ending_date period_ending_date placementwith_banksand_other_financial_institutions policy_acquisition_expense policy_fees policy_loans policy_reserves_benefits policyholder_and_reinsurer_accounts policyholder_benefits_ceded policyholder_benefits_gross policyholder_dividends policyholder_funds policyholder_interest pre_tre_sha_num preferred_securities_outside_stock_equity preferred_shares_number preferred_stock preferred_stock_dividend_paid preferred_stock_dividends preferred_stock_equity preferred_stock_issuance preferred_stock_of_subsidiary preferred_stock_payments preferred_stocks_available_for_sale premium_taxes_credit premiums_receivable prepaid_assets prepaid_reinsurance_premiums pretax_income pretax_margin primary_exchange_id primary_share_class_id primary_symbol principle_investment_gain_loss principle_transaction_revenue proceeds_from_issuance_of_warrants proceeds_from_loans proceeds_from_stock_option_exercised proceeds_payment_federal_funds_sold_and_securities_purchased_under_agreement_to_resell proceeds_payment_in_interest_bearing_deposits_in_bank proceedsfrom_disposalof_held_to_maturity_investments proceedsfrom_government_grants_cff proceedsfrom_government_grants_cfi proceedsfrom_issuing_other_equity_instruments professional_expense_and_contract_services_expense profitability_grade profiton_disposals promotion_and_advertising properties property_casualty_premiums property_liability_insurance_claims provision_for_doubtful_accounts provision_for_gain_loss_on_disposal provision_for_loan_lease_and_other_losses provisionand_write_offof_assets provisions_total ps_ratio purchase_of_business purchase_of_equity_securities purchase_of_fixed_maturity_securities purchase_of_intangibles purchase_of_investment purchase_of_long_term_investments purchase_of_ppe purchase_of_short_term_investments purchase_of_technology purchased_components purchased_transportation_services purchaseof_joint_venture_associate purchaseof_subsidiaries quick_ratio raw_materials real_estate real_estate_and_real_estate_joint_ventures_held_for_investment real_estate_held_for_sale realized_capital_gain realized_gain_loss_on_sale_of_loans_and_lease receiptsfrom_customers receivable_turnover receivables receivables_adjustments_allowances reconciled_cost_of_revenue reconciled_depreciation redeemable_preferred_stock regulatory_assets regulatory_liabilities reinsurance_assets reinsurance_balances_payable reinsurance_receivables reinsurance_recoverable reinsurance_recoverable_for_paid_losses reinsurance_recoverable_for_unpaid_losses reinsurance_recoveries_claimsand_benefits reinsurance_shareof_insurance_contract rent_and_landing_fees reorganization_other_costs repayment_of_debt repaymentin_lease_financing repurchase_of_capital_stock research_and_development research_expense restricted_cash restricted_cash_and_cash_equivalents restricted_cash_and_investments restricted_common_stock restricted_investments restructring_and_mn_a_income restructuring_and_merger_and_acquisition_income restructuring_and_mergern_acquisition retained_earnings revenue_growth revenues_cargo revenues_passenger roa roe roic salaries_and_wages sale_of_business sale_of_intangibles sale_of_investment sale_of_long_term_investments sale_of_ppe sale_of_short_term_investments sale_of_technology saleof_joint_venture_associate saleof_subsidiaries sales_of_equity_securities sales_of_fixed_maturity_securities sales_per_employee sales_per_share sales_yield securities_activities securities_amortization securities_and_investments securities_lending_collateral securities_lending_payable securities_loaned security_agree_to_be_resell security_borrowed security_sold_not_yet_repurchased security_type selling_and_marketing_expense selling_expense selling_general_and_administration separate_account_assets separate_account_business service_charge_on_depositor_accounts share_based_payments share_class_description share_class_level_shares_outstanding share_class_status share_issued shareof_associates shareof_operating_profit_lossfrom_joint_ventures_and_associates shares_outstanding short_description short_name short_term_debt_issuance short_term_debt_payments short_term_investments_available_for_sale short_term_investments_held_to_maturity short_term_investments_trading sic size_score social_security_costs special_charge special_income special_income_charges staff_costs standard_name stock_based_compensation stock_type stockholders_equity student_loan style_box style_score subordinated_liabilities sustainable_growth_rate symbol tangible_book_value tangible_book_value_per_share tangible_bv_per_share3_yr_avg tangible_bv_per_share5_yr_avg tax_assets_total tax_effect_of_unusual_items tax_loss_carryforward_basic_eps tax_loss_carryforward_diluted_eps tax_provision tax_rate tax_rate_for_calcs taxes_assets_current taxes_receivable taxes_refund_paid taxes_refund_paid_direct time_deposits_placed time_deposits_placed total_adjustmentsfor_non_cash_items total_assets total_capitalization total_debt total_debt_equity_ratio total_deferred_credits_and_other_non_current_liabilities total_deposits total_employee_number total_equity total_equity_gross_minority_interest total_expenses total_investments total_liabilities total_liabilities_net_minority_interest total_money_market_investments total_non_current_assets total_non_current_liabilities total_non_current_liabilities_net_minority_interest total_other_finance_cost total_partnership_capital total_premiums_earned total_revenue total_tax_payable total_unusual_items total_unusual_items_excluding_goodwill total_yield tradeand_other_payables_non_current trading_and_other_receivable trading_assets trading_gain_loss trading_liabilities trading_securities tradingand_financial_liabilities transportation_revenue treasury_shares_number treasury_stock trust_feesby_commissions trust_preferred_securities unbilled_receivables unclassified_current_assets underwriting_expenses unearned_income unearned_premiums unpaid_loss_and_loss_reserve unrealized_gain_loss unrealized_gain_loss_on_investment_securities unrealized_gains_losses_on_derivatives value_score wagesand_salaries water_production work_in_process work_performedby_entityand_capitalized working_capital working_capital_per_share working_capital_per_share3_yr_avg working_capital_per_share5_yr_avg write_down write_off ''' def pvr(context, data): ''' Minimal custom chart of profit_vs_risk returns ''' c = context # Brevity, readability, less typing, higher mileage and world peace. if 'pvr' not in c: c.pvr = { 'start' : c.portfolio.starting_cash, 'cash_low' : c.portfolio.starting_cash, 'chart_pvr' : 1, 'chart_cash_low' : 0, 'chart_max_shrt' : 0, 'chart_max_risk' : 0, 'chart_max_lvrg' : 1, 'chart_lvrg' : 1, 'chart_pvr_avg' : 1, 'pvravg' : [0], 'max_shrt' : 0, 'max_risk' : 0, 'max_lvrg' : 0, } long_now = (sum([z.amount * z.last_sale_price for s, z in c.portfolio.positions.items() if z.amount > 0])) shrt_now = -(sum([z.amount * z.last_sale_price for s, z in c.portfolio.positions.items() if z.amount < 0])) c.pvr['cash_low'] = min(c.pvr['cash_low'], c.portfolio.cash) c.pvr['max_shrt'] = max(c.pvr['max_shrt'], shrt_now) c.pvr['max_risk'] = max(c.pvr['max_risk'], abs(shrt_now) + long_now) c.pvr['max_lvrg'] = max(c.pvr['max_lvrg'], c.account.leverage) def pvr_chart(context, data): # Profit_vs_Risk returns based on max amount actually invested, risked, long or short # More info: http://www.quantopian.com/posts/pvr c = context if c.pvr['max_risk'] != 0: # Avoid zero-divide pvr = 100 * (c.portfolio.portfolio_value - c.pvr['start']) / c.pvr['max_risk'] c.pvr['pvravg'].append(pvr) if c.pvr['chart_pvr']: record(PvR = pvr) if get_environment('end').date() == get_datetime().date(): log.info('PvRAvg {}'.format(sum(c.pvr['pvravg']) / len(c.pvr['pvravg']))) if c.pvr['chart_cash_low']: record(CashLow = c.pvr['cash_low']) if c.pvr['chart_max_shrt']: record(MxShrt = c.pvr['max_shrt']) if c.pvr['chart_max_risk']: record(MxRisk = c.pvr['max_risk']) if c.pvr['chart_max_lvrg']: record(MxLv = c.pvr['max_lvrg']) if c.pvr['chart_lvrg' ]: record(Lv = c.account.leverage) if c.pvr['chart_pvr_avg' ]: record(PvRAvg = sum(c.pvr['pvravg']) / len(c.pvr['pvravg'])) There was a runtime error. Blue!! Is Always very interesting to read your posts! On my view are necessay different parameters to evaluate an algo but, some are MORE important then others: Just imagine an algo wich invest all the starting capital just one day a month; and the Alpha extract is less then another algo wich is all days invested. All the backtest parameters in Q tell me that the second one is better the first one. But on my opinion is NOT. again following the example of my previous post Just imagine to sum the 10% of starting capital spended each of the 2519 days + the 200% of 1 only day (of starting capital), and to divide that sum by 2520 (days we are invested): now we have a "correct" weight of the$ amount invested (in daily frame). Now we can compare that value with the algo Return. Thats the way I usually do.

Honestly I do that calculation in a approximativy and intuitivly way, I'm not a programmer, just "lucky" hobbist not "yet" able to modify your routine..

Hi Blue,

Really appreciate all of your efforts with the sample coding. I tried to implement the short hand PVR code snipet you've provided above. Can you please provide some color as to how I can interpret my results?

BackTest run: 6/30/2018 - 7/30/2018 $10,000,000 Initial Capital As of 7.30.2018 Lv = 1.03 MxLV = 1.10 PvRAvg = 15.66 PvR = 27.52 Thank you. Lv -- leverage at the end of each day in the custom chart (keep in mind that record is daily resolution) MxLv -- the maximum leverage ever seen in the backtest (keeps track by watching every minute all throughout). PvRAvg -- Average PvR throughout the run, intended to be used for comparing algorithms to each other. Added as an afterthought and I don't use that very often. PvR -- Profit vs Risk says: If the initial capital had been all invested without going over, the returns would be this value. So, if only half of the initial capital is invested, PvR will be two times the value seen in the chart for Returns. Inversely, if initial capital is$100 but $200 is put at risk (as margin and/or excess shorting), since it went beyond the original$100, PvR will be just half of the Returns value shown.

It's a way to keep track of actual profitability. If one were to start with \$10 and buy a share of SPY every day, that would show fantastic returns but would be due to margin. PvR would show returns as if that margin had not happened (if starting capital matched the amount actually utilized). Makes cloning/evaluating algos a snap with no need to manually adjust for unused cash or margin or shorting spikes.

I like the percent per day value, see PvR 0.7512 %/day above from the end of the run. It could also be charted. No matter how long various backtests run, they can be saved locally copying that number for the first thing in file names and then sorting later to find algos run previously that did best. Can use an abbreviation for pvr percent per day like pd0.7512_and_the_rest_of_the_filename.py Some of mine from the past (except a more disciplined consistency in filenames would be recommendable):

ppd0.5587.py
ppd1.1018 p555 2 yr ver 3 liq work dump worst.py
ppd1.6888 p851 ver 3 liq work etc.py
ppd2.3997 the 44 million.py
ppd3.0550.py


Or drop the ppd and start the filenames with just the numbers. A filename can be constructed automatically for copy/paste from the end of the run with a little work. I use some code like that in a tool more extensive.
0.0688_p41_q19_b0.18_d6_2015-01-05_to_2017-05-22_10M.py
p41 is PvR. Since it is higher than the Q returns q19, that indicates not all of this 10M context.portfolio.starting_cash was invested.
Beta b0.18 and Drawdown d6 (percent)