Returns on Algorithms remain at 0% for 2 days - Not sure what I'm doing wrong!?

Hi

I've got two algorithms running in "Quantopian paper trading" mode. It's been two days and I've seen no change in the performance of the algorithms (i.e. both have remained at 0%), while the Benchmark (SPY) data seems to be pulling through.

This is literally my third day on Quantopian, so perhaps I'm making a simple rookie error. Would appreciate any guidance on how I can resolve.

Thanks,

Bevan

14 responses

Hi Simon - Both algorithms have a status of "Live Trading". Is there an additional step I need to take?

I mean, have they placed any trades? This sounds like a bug in your algorithm...

It may very well be the case, but I'm not sure how to check. I'm referring to the "Sample Mean Reversion Algorithm" if that helps...

Sorry I am not familiar with all the examples and whatnot, but if the algo has not placed any trades, it starts to reason that its performance is 0%.

Thanks Simon. Have included the code below. Would you mind terrible to have a look if anything is amiss?

# This is a sample mean-reversion algorithm on Quantopian for you to test and adapt.

# Algorithm investment thesis:
# Top-performing stocks from last week will do worse this week, and vice-versa.

# Every Monday, we rank high-volume stocks based on their previous 5 day returns.
# We go long the bottom 15% of stocks with the WORST returns over the past 5 days.
# We go short the top 15% of stocks with the BEST returns over the past 5 days.

# This type of algorithm may be used in live trading and in the Quantopian Open.

# Import the libraries we will use here
import numpy as np

# The initialize function is the place to set your tradable universe and define any parameters.
def initialize(context):
# Use the top 0.5% of stocks defined by average daily trading volume.
set_universe(universe.DollarVolumeUniverse(99.5, 100))
# Set execution cost assumptions. For live trading with Interactive Brokers
# we will assume a $1.00 minimum per trade fee, with a per share cost of$0.0075.
# Set market impact assumptions. We limit the simulation to
# trade up to 2.5% of the traded volume for any one minute,
# and our price impact constant is 0.1.
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.10))
# Define the other variables
context.long_leverage = 1.0
context.short_leverage = -1.0
context.lower_percentile = 15
context.upper_percentile = 85
context.returns_lookback = 5
# Rebalance every Monday (or the first trading day if it's a holiday).
# At 11AM ET, which is 1 hour and 30 minutes after market open.
schedule_function(rebalance,
date_rules.week_start(days_offset=0),
time_rules.market_open(hours = 1, minutes = 30))

# The handle_data function is run every bar.
def handle_data(context,data):
# Record and plot the leverage of our portfolio over time.
record(leverage = context.account.leverage)

# We also want to monitor the number of long and short positions
# in our portfolio over time. This loop will check our positition sizes
# and add the count of longs and shorts to our plot.
longs = shorts = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
if position.amount < 0:
shorts += 1
record(long_count=longs, short_count=shorts)

# This rebalancing is called according to our schedule_function settings.
def rebalance(context,data):
# Get the last N days of prices for every stock in our universe.
prices = history(context.returns_lookback, '1d', 'price')
# Calculate the past 5 days' returns for each security.
returns = (prices.iloc[-1] - prices.iloc) / prices.iloc
# Remove stocks with missing prices.
# Remove any stocks we ordered last time that still have open orders.
# Get the cutoff return percentiles for the long and short portfolios.
returns = returns.dropna()
open_orders = get_open_orders()
if open_orders:
eligible_secs = [sec for sec in data if sec not in open_orders]
returns = returns[eligible_secs]

# Lower percentile is the threshhold for the bottom 15%, upper percentile is for the top 15%.
lower, upper = np.percentile(returns, [context.lower_percentile,
context.upper_percentile])
# Select the X% worst performing securities to go long.
long_secs = returns[returns <= lower]
# Select the Y% best performing securities to short.
short_secs = returns[returns >= upper]
# Set the allocations to even weights in each portfolio.
long_weight = context.long_leverage / len(long_secs)
short_weight = context.short_leverage / len(short_secs)
for security in data:
# Buy/rebalance securities in the long leg of our portfolio.
if security in long_secs:
order_target_percent(security, long_weight)
# Sell/rebalance securities in the short leg of our portfolio.
elif security in short_secs:
order_target_percent(security, short_weight)
# Close any positions that fell out of the list of securities to long or short.
else:
order_target(security, 0)
log.info("This week's longs: "+", ".join([long_.symbol for long_ in long_secs.index]))
log.info("This week's shorts: "  +", ".join([short_.symbol for short_ in short_secs.index]))



Sorry, I don't really have time to examine examples for bugs. Good luck.

This is your original code with some instrumentation added, for the default time range, at $100k. 13 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 # This is a sample mean-reversion algorithm on Quantopian for you to test and adapt. # Algorithm investment thesis: # Top-performing stocks from last week will do worse this week, and vice-versa. # Every Monday, we rank high-volume stocks based on their previous 5 day returns. # We go long the bottom 15% of stocks with the WORST returns over the past 5 days. # We go short the top 15% of stocks with the BEST returns over the past 5 days. # This type of algorithm may be used in live trading and in the Quantopian Open. # Import the libraries we will use here import numpy as np # The initialize function is the place to set your tradable universe and define any parameters. def initialize(context): # Use the top 0.5% of stocks defined by average daily trading volume. set_universe(universe.DollarVolumeUniverse(99.5, 100)) # Set execution cost assumptions. For live trading with Interactive Brokers # we will assume a$1.00 minimum per trade fee, with a per share cost of $0.0075. set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1.00)) # Set market impact assumptions. We limit the simulation to # trade up to 2.5% of the traded volume for any one minute, # and our price impact constant is 0.1. set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.10)) # Define the other variables context.long_leverage = 1.0 context.short_leverage = -1.0 context.lower_percentile = 15 context.upper_percentile = 85 context.returns_lookback = 5 # Rebalance every Monday (or the first trading day if it's a holiday). # At 11AM ET, which is 1 hour and 30 minutes after market open. schedule_function(rebalance, date_rules.week_start(days_offset=0), time_rules.market_open(hours = 1, minutes = 30)) schedule_function(info, date_rules.every_day(), time_rules.market_close()) c = context c.cash_low = c.portfolio.starting_cash c.max_lvrg = 0 c.max_shrt = 0 c.risk_hi = 0 c.date_prv = '' c.date_end = str(get_environment('end').date()) print '{} to {} {}'.format(str(get_datetime().date()) , c.date_end, int(c.cash_low)) # The handle_data function is run every bar. def handle_data(context,data): # Record and plot the leverage of our portfolio over time. #record(leverage = context.account.leverage) # We also want to monitor the number of long and short positions # in our portfolio over time. This loop will check our positition sizes # and add the count of longs and shorts to our plot. longs = shorts = 0 for position in context.portfolio.positions.itervalues(): if position.amount > 0: longs += 1 if position.amount < 0: shorts += 1 #record(long_count=longs, short_count=shorts) # This rebalancing is called according to our schedule_function settings. def rebalance(context,data): # Get the last N days of prices for every stock in our universe. prices = history(context.returns_lookback, '1d', 'price') # Calculate the past 5 days' returns for each security. returns = (prices.iloc[-1] - prices.iloc) / prices.iloc # Remove stocks with missing prices. # Remove any stocks we ordered last time that still have open orders. # Get the cutoff return percentiles for the long and short portfolios. returns = returns.dropna() open_orders = get_open_orders() if open_orders: eligible_secs = [sec for sec in data if sec not in open_orders] returns = returns[eligible_secs] # Lower percentile is the threshhold for the bottom 15%, upper percentile is for the top 15%. lower, upper = np.percentile(returns, [context.lower_percentile, context.upper_percentile]) # Select the X% worst performing securities to go long. long_secs = returns[returns <= lower] # Select the Y% best performing securities to short. short_secs = returns[returns >= upper] # Set the allocations to even weights in each portfolio. long_weight = context.long_leverage / len(long_secs) short_weight = context.short_leverage / len(short_secs) for security in data: # Buy/rebalance securities in the long leg of our portfolio. if security in long_secs: order_target_percent(security, long_weight) # Sell/rebalance securities in the short leg of our portfolio. elif security in short_secs: order_target_percent(security, short_weight) # Close any positions that fell out of the list of securities to long or short. else: order_target(security, 0) log.info("This week's longs: "+", ".join([long_.symbol for long_ in long_secs.index])) log.info("This week's shorts: " +", ".join([short_.symbol for short_ in short_secs.index])) def info(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_risk = 0 # Risked, maximum cash spent or shorts in excess of cash at any time record_risk_hi = 1 # Highest risk overall record_cash = 0 # Cash available record_cash_low = 1 # Any new lowest cash level logging = 1 # Also log to the logging window conditionally (1) or not (0) log_method = 'risk_hi' # 'daily' or 'risk_hi' c = context # For brevity new_cash_low = 0 # To trigger logging in cash_low case date = str(get_datetime().date()) # To trigger logging in daily case cash = c.portfolio.cash 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)) pvr_rtrn = 0 # Profit vs Risk returns based on maximum spent q_rtrn = 0 # Returns by Quantopian profit_loss = 0 # Profit-n-loss shorts = 0 # Shorts value start = c.portfolio.starting_cash cash_dip = int(max(0, start - cash)) if record_cash: record(cash = int(c.portfolio.cash)) # Cash if record_leverage: record(Lvrg = c.account.leverage) # Leverage if record_max_lvrg: if c.account.leverage > c.max_lvrg: c.max_lvrg = c.account.leverage record(MaxLvrg = 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 record_shorting: record(Shorts = shorts) # Shorts value as a positve # Shorts in excess of cash to cover them, a positive value shorts_excess = int(shorts - cash) if shorts > cash else 0 c.max_shrt = int(max(c.max_shrt, shorts_excess)) risk = int(max(cash_dip, shorts_excess, shorts)) if record_risk: record(Risk = risk) # Amount in play, maximum of shorts or cash used new_risk_hi = 0 if risk > c.risk_hi: c.risk_hi = risk new_risk_hi = 1 if record_risk_hi: record(Risk_hi = 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: q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start record(QRet = q_rtrn) # Quantopian returns to compare to pvr returns curve from pytz import timezone if logging: 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: mxlv = 'MaxLv ' + '%.1f' % c.max_lvrg if record_max_lvrg else '' qret = 'QRet ' + '%.1f' % q_rtrn if record_q_return else '' pvr = 'PvR_Ret ' + '%.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 '' 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, mxlv, qret, pvr, pnl, csh, csh_lw, 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. By watching PvR you can give yourself a significant advantage. (see Source) Using PvR I was able to pull 12.8 times more profit from your code. I'm now paper trading this. 13 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 # This is a sample mean-reversion algorithm on Quantopian to test and adapt. # Algorithm investment thesis: # Top-performing stocks from last week will do worse this week, and vice-versa. # Every Monday, rank high-volume stocks based on their previous 5 day returns. # Long the bottom 15% of stocks with the WORST returns over the past 5 days. # Short the top 15% of stocks with the BEST returns over the past 5 days. # This type of algorithm may be used in live trading and in the Quantopian Open. # Import the libraries to use import numpy as np # The initialize function is the place to set the tradable universe and define any parameters. def initialize(context): # Use the top 0.5% of stocks defined by average daily trading volume. set_universe(universe.DollarVolumeUniverse(99.5, 100)) # Set execution cost assumptions. For live trading with Interactive Brokers # assume a$1.00 minimum per trade fee, with a per share cost of \$0.0075.
# Set market impact assumptions. Limit the simulation to
#   trade up to 2.5% of the traded volume for any one minute,
#   and price impact constant is 0.1.
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.10))
# Define the other variables
context.long_leverage    =  .9
context.short_leverage   = -.3    # higher = lower beta
context.lower_percentile = 8
context.upper_percentile = 93
context.returns_lookback = 4
# Rebalance every Monday (or the first trading day if it's a holiday).
# At 11AM ET, which is 1 hour and 30 minutes after market open.
#schedule_function(rebalance,
#                  date_rules.week_start(days_offset=0),
#                  time_rules.market_open(hours = 1, minutes = 30))
schedule_function(rebalance,
date_rules.week_start(days_offset=0),
time_rules.market_close())

schedule_function(info, date_rules.every_day(), time_rules.market_close())
c = context
c.cash_low = c.portfolio.starting_cash
c.max_lvrg = 0
c.max_shrt = 0
c.risk_hi  = 0
c.date_prv = ''
c.date_end = str(get_environment('end').date())
print '{} to {}  {}'.format(str(get_datetime().date()) , c.date_end, int(c.cash_low))

def handle_data(context,data):
#record(leverage = context.account.leverage)

# Also want to monitor the number of long and short positions
# in the portfolio over time. This loop will check positition sizes
# and add the count of longs and shorts to the plot.
longs = shorts = 0
for position in context.portfolio.positions.itervalues():
if position.amount > 0:
longs += 1
if position.amount < 0:
shorts += 1
#record(long_count=longs, short_count=shorts)

def rebalance(context,data):
# Get the last N days of prices for every stock in the universe.
prices = history(context.returns_lookback, '1d', 'price')
# Calculate the past 5 days' returns for each security.
returns = (prices.iloc[-1] - prices.iloc) / prices.iloc
# Remove stocks with missing prices.
# Remove any stocks ordered last time that still have open orders.
# Get the cutoff return percentiles for the long and short portfolios.
returns = returns.dropna()
open_orders = get_open_orders()
if open_orders:
eligible_secs = [sec for sec in data if sec not in open_orders]
returns = returns[eligible_secs]

# Lower percentile is the threshhold for the bottom 15%, upper percentile is for the top 15%.
lower, upper = np.percentile(returns, [context.lower_percentile,
context.upper_percentile])
# Select the X% worst performing securities to go long.
long_secs = returns[returns <= lower]
# Select the Y% best performing securities to short.
short_secs = returns[returns >= upper]
# Set the allocations to even weights in each portfolio.
long_weight = context.long_leverage / len(long_secs)
short_weight = context.short_leverage / len(short_secs)
for security in data:
# Buy/rebalance securities in the long leg of the portfolio.
if security in long_secs:
if get_open_orders(security): continue
order_target_percent(security, long_weight)
# Sell/rebalance securities in the short leg of the portfolio.
elif security in short_secs:
if get_open_orders(security): continue
order_target_percent(security, short_weight)
# Close any positions that fell out of the list of securities to long or short.
else:
if get_open_orders(security): continue
if context.portfolio.positions[security].amount == 0: continue
order_target(security, 0)
log.info("This week's longs: "+", ".join([long_.symbol for long_ in long_secs.index]))
log.info("This week's shorts: "  +", ".join([short_.symbol for short_ in short_secs.index]))

def info(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 = 1          # 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_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
record_risk_hi  = 1          # Highest risk overall
record_cash     = 0          # Cash available
record_cash_low = 0          # Any new lowest cash level
logging         = 1          # Also log to the logging window conditionally (1) or not (0)
log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

c = context                          # For brevity
new_cash_low = 0                     # To trigger logging in cash_low case
date = str(get_datetime().date())    # To trigger logging in daily case
cash = c.portfolio.cash

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))

pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
q_rtrn        = 0        # Returns by Quantopian
profit_loss   = 0        # Profit-n-loss
shorts        = 0        # Shorts value
start         = c.portfolio.starting_cash
cash_dip      = int(max(0, start - cash))

if record_cash:
record(cash = int(c.portfolio.cash))  # Cash

if record_leverage:
record(Lvrg = c.account.leverage)     # Leverage

if record_max_lvrg:
if c.account.leverage > c.max_lvrg:
c.max_lvrg = c.account.leverage
record(MaxLvrg = 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 record_shorting:
record(Shorts = shorts)               # Shorts value as a positve

# Shorts in excess of cash to cover them, a positive value
shorts_excess = int(shorts - cash) if shorts > cash else 0
c.max_shrt    = int(max(c.max_shrt, shorts_excess))

risk = int(max(cash_dip, shorts_excess, shorts))
if record_risk:
record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

new_risk_hi = 0
if risk > c.risk_hi:
c.risk_hi = risk
new_risk_hi = 1

if record_risk_hi:
record(Risk_hi = 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:
q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

from pytz import timezone
if logging:
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:
mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
qret   = 'QRet '    + '%.1f' % q_rtrn       if record_q_return else ''
pvr    = 'PvR_Ret ' + '%.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 ''
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, mxlv, qret, pvr, pnl, csh, csh_lw, 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.

If by "Paper Trading", you mean "Live Trading" in the contest, then you won't trade anything until this upcoming Monday, due to the line:

   # Rebalance every Monday (or the first trading day if it's a holiday).
# At 11AM ET, which is 1 hour and 30 minutes after market open.
schedule_function(rebalance,
date_rules.week_start(days_offset=0),
time_rules.market_open(hours = 1, minutes = 30))



Change it to dailly trading with something like:
 schedule_function(rebalance, date_rules.every_day(), time_rules.market_close(hours=2, minutes=15))  Also, when you submit to the contest, you may not see anything on the leaderboard for a couple of days due to Qtop infrastructure delays.
alan

No problem Simon. Thanks for the assistance.

Thanks Garyha - Using PvR definitely makes all the difference, but the coding is way out of my league. Nevertheless - A luta continua!

Hi Bevan,

Alan's comment explains why your algo has not yet placed any trades. The schedule function is set to only trade at the first trading day of each week in that example. If you would like to see any transactions that your live algo has made, you can go to the live algo dashboard and look at the "Orders and Fills" table for recent orders.

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

PvR code can usually be dropped in with a single paste as the one continuous chunk below at the end of initialize().

    schedule_function(info, date_rules.every_day(), time_rules.market_close())
c = context
c.cash_low = c.portfolio.starting_cash
c.max_lvrg = 0
c.max_shrt = 0
c.risk_hi  = 0
c.date_prv = ''
c.date_end = str(get_environment('end').date())
print '{} to {}  {}'.format(str(get_datetime().date()) , c.date_end, int(c.cash_low))

def info(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_risk     = 0          # Risked, maximum cash spent or shorts in excess of cash at any time
record_risk_hi  = 1          # Highest risk overall
record_cash     = 0          # Cash available
record_cash_low = 1          # Any new lowest cash level
logging         = 1          # Also log to the logging window conditionally (1) or not (0)
log_method      = 'risk_hi'  # 'daily' or 'risk_hi'

c = context                          # For brevity
new_cash_low = 0                     # To trigger logging in cash_low case
date = str(get_datetime().date())    # To trigger logging in daily case
cash = c.portfolio.cash

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))

pvr_rtrn      = 0        # Profit vs Risk returns based on maximum spent
q_rtrn        = 0        # Returns by Quantopian
profit_loss   = 0        # Profit-n-loss
shorts        = 0        # Shorts value
start         = c.portfolio.starting_cash
cash_dip      = int(max(0, start - cash))

if record_cash:
record(cash = int(c.portfolio.cash))  # Cash

if record_leverage:
record(Lvrg = c.account.leverage)     # Leverage

if record_max_lvrg:
if c.account.leverage > c.max_lvrg:
c.max_lvrg = c.account.leverage
record(MaxLvrg = 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 record_shorting:
record(Shorts = shorts)               # Shorts value as a positve

# Shorts in excess of cash to cover them, a positive value
shorts_excess = int(shorts - cash) if shorts > cash else 0
c.max_shrt    = int(max(c.max_shrt, shorts_excess))

risk = int(max(cash_dip, shorts_excess, shorts))
if record_risk:
record(Risk = risk)                   # Amount in play, maximum of shorts or cash used

new_risk_hi = 0
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:
q_rtrn = 100 * (c.portfolio.portfolio_value - start) / start
record(QRet = q_rtrn)                 # Quantopian returns to compare to pvr returns curve

from pytz import timezone
if logging:
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
mxlv   = 'MaxLv '   + '%.1f' % c.max_lvrg   if record_max_lvrg else ''
pvr    = 'PvR_Ret ' + '%.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 ''
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, mxlv, qret, pvr, pnl, csh, csh_lw, 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


Everybody: Change that schedule_function() to match whatever schedule you're running (and last), or comment it and instead run info(context, data) maybe inside handle_data(). The key is that it needs to pick up the maximum amount of cash transacted for stocks (the amount you would need to supply to the account to achieve that result, for all practical purposes always higher or lower than starting capital).

Maybe I ought to put out a far more commented, explained version of that. If so, somebody let me know.