bt = get_backtest('56a8157633749711029e987b')
from __future__ import division
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy as sp
import numpy as np
import pyfolio as pf
from collections import OrderedDict, defaultdict, deque
def groupby_directional(txn):
out = []
for sym, t in txn.groupby('symbol'):
t = t.sort_index()
t['order_sign'] = t.amount > 0
t['block'] = (t.order_sign.shift(1) != t.order_sign).astype(int).cumsum()
t.index.name = 'dt'
grouped = t.reset_index()\
.groupby('block')\
.agg({'price': 'mean',
'amount': 'sum',
'symbol': 'first',
'dt': 'first'})
out.append(grouped)
out = pd.concat(out)
out = out.set_index('dt')
return out
def groupby_consecutive(txn, min_gap=pd.Timedelta('1m')):
out = []
for sym, t in txn.groupby('symbol'):
t = t.sort_index()
t.index.name = 'dt'
t = t.reset_index()
t['order_sign'] = t.amount > 0
t['block_dir'] = (t.order_sign.shift(1) != t.order_sign).astype(int).cumsum()
t['block_time'] = ((t.dt - t.dt.shift(1)) > min_gap).astype(int).cumsum()
grouped = t.groupby(['block_dir', 'block_time'])\
.agg({'price': 'mean',
'amount': 'sum',
'symbol': 'first',
'dt': 'first'})
out.append(grouped)
out = pd.concat(out)
out = out.set_index('dt')
return out
def extract_round_trips_stack(transactions, groupby=groupby_consecutive):
#transactions = pf.round_trips.split_trades(transactions)
transactions = groupby(transactions)
port_stack = defaultdict(deque)
roundtrips = []
for sym, trans_sym in transactions.groupby('symbol'):
trans_sym = trans_sym.sort_index()
sym_stack = port_stack[sym]
# Create list of
for dt, t in trans_sym.iterrows():
signed_price = t.price * np.sign(t.amount)
abs_amount = int(abs(t.amount))
indiv_prices = [signed_price] * abs_amount
if (len(sym_stack) == 0) or \
(np.sign(sym_stack[-1][1]) == np.sign(t.amount)):
for price in indiv_prices:
sym_stack.append((dt, price))
else:
# Close round-trip
pnl = 0
invested = 0
cur_durations = []
cur_open_dts = []
cur_close_dts = []
for price in indiv_prices:
if len(sym_stack) != 0:
# Retrieve last dt, stock-price pair from stack
prev_dt, prev_price = sym_stack.pop()
pnl += -(price + prev_price)
cur_durations.append(dt - prev_dt)
cur_open_dts.append(prev_dt)
invested += np.abs(prev_price)
else:
# Push additional stock-prices onto stack
sym_stack.append((dt, price))
roundtrips.append({'pnl': pnl,
'duration': np.median(cur_durations),
'open_dt': cur_open_dts[len(cur_open_dts) // 2],
'close_dt': dt,
'long': price < 0,
'returns': pnl / invested,
'symbol': sym,
})
roundtrips = pd.DataFrame(roundtrips)
return roundtrips
# Add symbols
transactions = bt.transactions
transactions['symbol'] = map(lambda x: x.symbol, symbols(transactions.sid))
trades = extract_round_trips_stack(transactions)
trades.head()
trade_stats = OrderedDict([('Total profit', lambda x: x.sum()),
('Gross profit', lambda x: x[x>0].sum()),
('Gross loss', lambda x: x[x<0].sum()),
('Profit factor', lambda x: x[x>0].sum() / x[x<0].abs().sum()),
('Total number of trades', 'count'),
('Percent profitable', lambda x: len(x[x>0]) / len(x)),
('Winning trades', lambda x: len(x[x>0])),
('Losing trades', lambda x: len(x[x<0])),
('Even trades', lambda x: len(x[x==0])),
('Avg. trade net profit', 'mean'),
('Avg. winning trade', lambda x: x[x>0].mean()),
('Avg. losing trade', lambda x: x[x<0].mean()),
('Ratio Avg. Win:Avg. Loss', lambda x: x[x>0].mean() / x[x<0].abs().mean()),
('Largest winning trade', 'max'),
('Largest losing trade', 'min')])
stats = trades.assign(ones=np.ones(len(trades))).groupby('ones')['pnl'].agg(trade_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
stats2 = trades.groupby('long')['pnl'].agg(trade_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
stats = stats.join(stats2)
#pd.set_option('display.float_format', lambda x: '$%.2f' % x)
stats[['All trades', 'Long trades', 'Short trades']]
def create_round_trip_tear_sheet(trades, ndays):
#transactions_closed = pf.round_trips.add_closing_transactions(positions,
# transactions)
#trades = round_trips.extract_round_trips(transactions_closed)
#trades = extract_round_trips_stack(transactions)
if len(trades) < 5:
warnings.warn(
"""Fewer than 5 round-trip trades made.
Skipping round trip tearsheet.""", UserWarning)
return
#ndays = len(positions)
print(trades.drop(['open_dt', 'close_dt', 'symbol'],
axis='columns').describe())
print('Percent of round trips profitable = {:.4}%'.format(
(trades.pnl > 0).mean() * 100))
winning_round_trips = trades[trades.pnl > 0]
losing_round_trips = trades[trades.pnl < 0]
print('Mean return per winning round trip = {:.4}'.format(
winning_round_trips.returns.mean()))
print('Mean return per losing round trip = {:.4}'.format(
losing_round_trips.returns.mean()))
print('A decision is made every {:.4} days.'.format(float(ndays) / len(trades)))
print('{:.4} trading decisions per day.'.format(len(trades) * 1. / ndays))
print('{:.4} trading decisions per month.'.format(
len(trades) * 1. / (ndays / 21)))
create_round_trip_tear_sheet(trades, len(bt.positions))