BLD: polishing a sample algorithm

This commit is contained in:
fredfortier
2017-11-13 16:57:37 -05:00
parent 648be3969a
commit dce31b212b
3 changed files with 163 additions and 79 deletions
@@ -17,6 +17,9 @@ from catalyst.api import symbol, record, order_target_percent, \
# In this example, Catalyst will create the `.catalyst/data/live_algos`
# directory. If we stop and start the algorithm, Catalyst will resume its
# state using the files included in the folder.
from catalyst.exchange.stats_utils import crossunder, get_pretty_stats, \
extract_transactions
algo_namespace = 'momentum'
log = Logger(algo_namespace)
@@ -27,7 +30,7 @@ def initialize(context):
# trading pairs) you want to backtest. You'll also want to define any
# parameters or values you're going to use.
# In our example, we're looking at Ether in Bitcoin.
# In our example, we're looking at Ether in USD Tether.
context.eth_btc = symbol('eth_usdt')
context.max_amount = 0.01
context.base_price = None
@@ -42,7 +45,8 @@ def handle_data(context, data):
# We flag the first period of each day.
# Since cryptocurrencies trade 24/7 the `before_trading_starts` handle
# would only execute once.
# would only execute once. This method works with minute and daily
# frequencies.
today = data.current_dt.floor('1D')
if today != context.current_day:
context.traded_today = False
@@ -50,43 +54,67 @@ def handle_data(context, data):
# We're computing the volume-weighted-average-price of the security
# defined above, in the context.eth_btc variable. For this example, we're
# using three bars on the daily chart.
# using three bars on the 15 min bars.
# The frequency attribute determine the bar size. We use this convention
# for the frequency alias:
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
prices = data.history(
context.eth_btc,
fields='close',
bar_count=100,
frequency='30T'
)
# Use TA-Lib to calculate MACD data using calibrated settings
macd_raw, signal, macd_hist = talib.MACD(
prices.values, fastperiod=30, slowperiod=40, signalperiod=45
bar_count=220,
frequency='15T'
)
# Ta-lib calculates various technical indicator based on price and
# volume arrays.
# In this example, we are comp
rsi = talib.RSI(prices.values, timeperiod=4)
sma200 = talib.SMA(prices.values, timeperiod=200)
# We need a variable for the current price of the security to compare to
# the average.
# the average. Since we are requesting two fields, data.current()
# returns a DataFrame with
current = data.current(context.eth_btc, fields=['close', 'volume'])
price = current['close']
log.info(
'{}: price: {}, macd: {}'.format(data.current_dt, price, macd_raw[-1])
'{}: price: {}, rsi: {}, sma: {}'.format(
data.current_dt, price, rsi[-1], sma200[-1]
)
)
# If base_price is not set, we use the current value. This is the
# price at the first bar which we reference to calculate price_change.
if context.base_price is None:
context.base_price = price
price_change = (price - context.base_price) / context.base_price
price_change = (price - context.base_price) / context.base_price
cash = context.portfolio.cash
# Now that we've collected all current data for this frame, we use
# the record() method to save it. This data will be available as
# a parameter of the analyze() function for further analysis.
record(
price=price,
volume=current['volume'],
macd=macd_raw[-1],
signal=signal[-1],
sma200=sma200[-1],
price_change=price_change,
rsi=rsi[-1],
cash=cash
)
# We are trying to avoid over-trading by limiting our trades to
# one per day.
if context.traded_today:
log.info('skipping because we\'ve already trader today')
return
# Since we are using limit orders, some orders may not execute immediately
# we wait until all orders are executed before considering more trades.
orders = get_open_orders(context.eth_btc)
if len(orders) > 0:
log.info('skipping bar until all open orders execute')
log.info('skipping until all open orders execute')
return
# Another powerful built-in feature of the Catalyst backtester is the
@@ -95,18 +123,20 @@ def handle_data(context, data):
# how long or short our position is at this minute.
pos_amount = context.portfolio.positions[context.eth_btc].amount
if macd_hist[-1] > 0 and data.can_trade(context.eth_btc) \
and pos_amount == 0 and not context.traded_today:
order_target_percent(context.eth_btc, 0.75)
# Determining the entry and exit signals based on RSI and SMA
if rsi[-1] <= 30 and data.can_trade(context.eth_btc) \
and pos_amount == 0:
# and price > sma200[-1] and pos_amount == 0:
order_target_percent(context.eth_btc, 1)
context.traded_today = True
elif macd_hist[-1] < 0 and data.can_trade(context.eth_btc) \
and pos_amount > 0 and not context.traded_today:
elif (rsi[-1] >= 90 or crossunder(prices, sma200)) \
and data.can_trade(context.eth_btc) and pos_amount > 0:
order_target_percent(context.eth_btc, 0)
context.traded_today = True
def analyze(context=None, results=None):
def analyze(context=None, perf=None):
import matplotlib.pyplot as plt
# The base currency of the algo exchange
@@ -114,77 +144,70 @@ def analyze(context=None, results=None):
# Plot the portfolio value over time.
ax1 = plt.subplot(611)
results.loc[:, 'portfolio_value'].plot(ax=ax1)
perf.loc[:, 'portfolio_value'].plot(ax=ax1)
ax1.set_ylabel('Portfolio Value ({})'.format(base_currency))
# Plot the price increase or decrease over time.
ax2 = plt.subplot(612, sharex=ax1)
results.loc[:, 'price'].plot(ax=ax2)
perf.loc[:, 'price'].plot(ax=ax2, label='Price')
perf.loc[:, 'sma200'].plot(ax=ax2, label='SMA200')
ax2.set_ylabel('{asset} ({base})'.format(
asset=context.eth_btc.symbol, base=base_currency
))
# Compute indexes for buy and sell transactions
trans_list = results.transactions.values
all_trans = [t for sublist in trans_list for t in sublist]
all_trans.sort(key=lambda t: t['dt'])
buys = results.loc[[t['dt'] for t in all_trans if t['amount'] > 0], :]
sells = results.loc[[t['dt'] for t in all_trans if t['amount'] < 0], :]
transaction_df = extract_transactions(perf)
buy_df = transaction_df[transaction_df['amount'] > 0]
sell_df = transaction_df[transaction_df['amount'] < 0]
ax2.scatter(
buys.index,
results.loc[buys.index, 'price'],
buy_df.index,
perf.loc[buy_df.index, 'price'],
marker='^',
s=100,
c='green',
label=''
)
ax2.scatter(
sells.index,
results.loc[sells.index, 'price'],
sell_df.index,
perf.loc[sell_df.index, 'price'],
marker='v',
s=100,
c='red',
label=''
)
ax4 = plt.subplot(613, sharex=ax1)
results.loc[:, ['starting_cash', 'cash']].plot(ax=ax4)
ax4.set_ylabel('Base Currency ({})'.format(base_currency))
perf.loc[:, 'cash'].plot(
ax=ax4, label='Base Currency ({})'.format(base_currency)
)
ax4.set_ylabel('Cash ({})'.format(base_currency))
results['algorithm'] = results.loc[:, 'algorithm_period_return']
perf['algorithm'] = perf.loc[:, 'algorithm_period_return']
ax5 = plt.subplot(614, sharex=ax1)
results.loc[:, ['algorithm', 'price_change']].plot(ax=ax5)
perf.loc[:, ['algorithm', 'price_change']].plot(ax=ax5)
ax5.set_ylabel('Percent Change')
ax6 = plt.subplot(615, sharex=ax1)
results.loc[:, 'macd'].plot(ax=ax6, label='macd')
perf.loc[:, 'rsi'].plot(ax=ax6, label='RSI')
ax6.axhline(70, color='darkgoldenrod')
ax6.axhline(30, color='darkgoldenrod')
ax6.scatter(
buys.index,
results.loc[buys.index, 'macd'],
buy_df.index,
perf.loc[buy_df.index, 'rsi'],
marker='^',
s=100,
c='green',
label=''
)
ax6.scatter(
sells.index,
results.loc[sells.index, 'macd'],
sell_df.index,
perf.loc[sell_df.index, 'rsi'],
marker='v',
s=100,
c='red',
label=''
)
# handles, labels = plt.gca().get_legend_handles_labels()
# i = 1
# while i < len(labels):
# if labels[i] in labels[:i]:
# del (labels[i])
# del (handles[i])
# else:
# i += 1
plt.legend(loc=3)
# Show the plot.
@@ -193,16 +216,33 @@ def analyze(context=None, results=None):
pass
# Backtest
run_algorithm(
capital_base=1,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
algo_namespace=algo_namespace,
base_currency='usdt',
start=pd.to_datetime('2017-6-1', utc=True),
end=pd.to_datetime('2017-6-7', utc=True),
)
if __name__ == '__main__':
# The execution mode: backtest or live
MODE = 'backtest'
if MODE == 'backtest':
run_algorithm(
capital_base=1,
data_frequency='minute',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
algo_namespace=algo_namespace,
base_currency='usdt',
start=pd.to_datetime('2017-7-1', utc=True),
end=pd.to_datetime('2017-9-30', utc=True),
# end=pd.to_datetime('2017-7-7', utc=True),
)
elif MODE == 'live':
run_algorithm(
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='poloniex',
live=True,
algo_namespace=algo_namespace,
base_currency='usdt',
live_graph=True
)
+12 -6
View File
@@ -761,12 +761,18 @@ class ExchangeBundle:
The spot values for the gives assets, field and date. Reads from
the exchange data bundle.
:param assets:
:param field:
:param dt:
:param data_frequency:
:param reset_reader:
:return:
Parameters
----------
assets: list[TradingPair]
field: str
dt: pd.Timestamp
data_frequency: str
reset_reader:
Returns
-------
float
"""
values = []
try:
+44 -6
View File
@@ -1,3 +1,5 @@
import numbers
import numpy as np
import pandas as pd
@@ -44,14 +46,24 @@ def crossunder(source, target):
bool
"""
if source[-1] is np.nan or source[-2] is np.nan \
or target[-1] is np.nan or target[-2] is np.nan:
return False
if isinstance(target, numbers.Number):
if source[-1] is np.nan or source[-2] is np.nan \
or target is np.nan:
return False
if source[-1] < target[-1] and source[-2] > target[-2]:
return True
if source[-1] < target <= source[-2]:
return True
else:
return False
else:
return False
if source[-1] is np.nan or source[-2] is np.nan \
or target[-1] is np.nan or target[-2] is np.nan:
return False
if source[-1] < target[-1] and source[-2] >= target[-2]:
return True
else:
return False
def vwap(df):
@@ -161,3 +173,29 @@ def df_to_string(df):
pd.set_option('display.max_colwidth', 1000)
return df.to_string()
def extract_transactions(perf):
"""
Compute indexes for buy and sell transactions
Parameters
----------
perf: DataFrame
The algo performance DataFrame.
Returns
-------
DataFrame
A DataFrame of transactions.
"""
trans_list = perf.transactions.values
all_trans = [t for sublist in trans_list for t in sublist]
all_trans.sort(key=lambda t: t['dt'])
# transactions = perf.loc[[t['dt'] for t in all_trans], :]
transactions = pd.DataFrame(all_trans)
transactions.set_index('dt', inplace=True, drop=True)
return transactions