mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-08 08:46:00 +08:00
BLD: polishing a sample algorithm
This commit is contained in:
@@ -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
|
||||
)
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user