mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-22 12:40:30 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
803823eac0 | ||
|
|
5a18e09730 | ||
|
|
dd41f8c006 | ||
|
|
7a2a4817fe | ||
|
|
105522e5ab | ||
|
|
a52e201f86 | ||
|
|
697ff54125 | ||
|
|
4bcd34bd78 | ||
|
|
64c52c7a3c | ||
|
|
1c143eb9ea | ||
|
|
1da4ccfb8c | ||
|
|
a61b22b821 | ||
|
|
d07e0edd88 | ||
|
|
c2821ab77b |
@@ -16,6 +16,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
from catalyst import run_algorithm
|
||||||
from catalyst.api import (
|
from catalyst.api import (
|
||||||
order_target_value,
|
order_target_value,
|
||||||
symbol,
|
symbol,
|
||||||
@@ -26,7 +27,7 @@ from catalyst.api import (
|
|||||||
|
|
||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
context.ASSET_NAME = 'btc_usdt'
|
context.ASSET_NAME = 'btc_usd'
|
||||||
context.TARGET_HODL_RATIO = 0.8
|
context.TARGET_HODL_RATIO = 0.8
|
||||||
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
|
context.RESERVE_RATIO = 1.0 - context.TARGET_HODL_RATIO
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ def handle_data(context, data):
|
|||||||
|
|
||||||
# Check if still buying and could (approximately) afford another purchase
|
# Check if still buying and could (approximately) afford another purchase
|
||||||
if context.is_buying and cash > price:
|
if context.is_buying and cash > price:
|
||||||
|
print('buying')
|
||||||
# Place order to make position in asset equal to target_hodl_value
|
# Place order to make position in asset equal to target_hodl_value
|
||||||
order_target_value(
|
order_target_value(
|
||||||
context.asset,
|
context.asset,
|
||||||
@@ -91,12 +93,13 @@ def analyze(context=None, results=None):
|
|||||||
buys = trans.ix[
|
buys = trans.ix[
|
||||||
[t[0]['amount'] > 0 for t in trans.transactions]
|
[t[0]['amount'] > 0 for t in trans.transactions]
|
||||||
]
|
]
|
||||||
ax2.plot(
|
ax2.scatter(
|
||||||
buys.index,
|
buys.index.to_pydatetime(),
|
||||||
results.price[buys.index],
|
results.price[buys.index],
|
||||||
'^',
|
marker='^',
|
||||||
markersize=10,
|
s=100,
|
||||||
color='g',
|
c='g',
|
||||||
|
label=''
|
||||||
)
|
)
|
||||||
|
|
||||||
ax3 = plt.subplot(613, sharex=ax1)
|
ax3 = plt.subplot(613, sharex=ax1)
|
||||||
@@ -135,3 +138,17 @@ def analyze(context=None, results=None):
|
|||||||
plt.gcf().set_size_inches(18, 8)
|
plt.gcf().set_size_inches(18, 8)
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_algorithm(
|
||||||
|
capital_base=10000,
|
||||||
|
data_frequency='minute',
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=handle_data,
|
||||||
|
analyze=analyze,
|
||||||
|
exchange_name='bitfinex',
|
||||||
|
algo_namespace='buy_and_hodl',
|
||||||
|
base_currency='usd',
|
||||||
|
start=pd.to_datetime('2017-11-01', utc=True),
|
||||||
|
end=pd.to_datetime('2017-11-10', utc=True),
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from logbook import Logger
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
from catalyst import run_algorithm
|
||||||
|
from catalyst.api import (order, record, symbol, order_target_percent,
|
||||||
|
get_open_orders)
|
||||||
|
from catalyst.exchange.stats_utils import extract_transactions
|
||||||
|
|
||||||
|
NAMESPACE = 'dual_moving_average'
|
||||||
|
log = Logger(NAMESPACE)
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
context.i = 0
|
||||||
|
context.asset = symbol('ltc_usd')
|
||||||
|
context.base_price = None
|
||||||
|
|
||||||
|
|
||||||
|
def handle_data(context, data):
|
||||||
|
# define the windows for the moving averages
|
||||||
|
short_window = 50
|
||||||
|
long_window = 200
|
||||||
|
|
||||||
|
# Skip as many bars as long_window to properly compute the average
|
||||||
|
context.i += 1
|
||||||
|
if context.i < long_window:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Compute moving averages calling data.history() for each
|
||||||
|
# moving average with the appropriate parameters. We choose to use
|
||||||
|
# minute bars for this simulation -> freq="1m"
|
||||||
|
# Returns a pandas dataframe.
|
||||||
|
short_mavg = data.history(context.asset, 'price',
|
||||||
|
bar_count=short_window, frequency="1m").mean()
|
||||||
|
long_mavg = data.history(context.asset, 'price',
|
||||||
|
bar_count=long_window, frequency="1m").mean()
|
||||||
|
|
||||||
|
# Let's keep the price of our asset in a more handy variable
|
||||||
|
price = data.current(context.asset, 'price')
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Save values for later inspection
|
||||||
|
record(price=price,
|
||||||
|
cash=context.portfolio.cash,
|
||||||
|
price_change=price_change,
|
||||||
|
short_mavg=short_mavg,
|
||||||
|
long_mavg=long_mavg)
|
||||||
|
|
||||||
|
# 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.asset)
|
||||||
|
if len(orders) > 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Exit if we cannot trade
|
||||||
|
if not data.can_trade(context.asset):
|
||||||
|
return
|
||||||
|
|
||||||
|
# We check what's our position on our portfolio and trade accordingly
|
||||||
|
pos_amount = context.portfolio.positions[context.asset].amount
|
||||||
|
|
||||||
|
# Trading logic
|
||||||
|
if short_mavg > long_mavg and pos_amount == 0:
|
||||||
|
# we buy 100% of our portfolio for this asset
|
||||||
|
order_target_percent(context.asset, 1)
|
||||||
|
elif short_mavg < long_mavg and pos_amount > 0:
|
||||||
|
# we sell all our positions for this asset
|
||||||
|
order_target_percent(context.asset, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze(context, perf):
|
||||||
|
|
||||||
|
# Get the base_currency that was passed as a parameter to the simulation
|
||||||
|
base_currency = context.exchanges.values()[0].base_currency.upper()
|
||||||
|
|
||||||
|
# First chart: Plot portfolio value using base_currency
|
||||||
|
ax1 = plt.subplot(411)
|
||||||
|
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
|
||||||
|
ax1.legend_.remove()
|
||||||
|
ax1.set_ylabel('Portfolio Value\n({})'.format(base_currency))
|
||||||
|
start, end = ax1.get_ylim()
|
||||||
|
ax1.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
||||||
|
|
||||||
|
# Second chart: Plot asset price, moving averages and buys/sells
|
||||||
|
ax2 = plt.subplot(412, sharex=ax1)
|
||||||
|
perf.loc[:, ['price','short_mavg','long_mavg']].plot(ax=ax2, label='Price')
|
||||||
|
ax2.legend_.remove()
|
||||||
|
ax2.set_ylabel('{asset}\n({base})'.format(
|
||||||
|
asset = context.asset.symbol,
|
||||||
|
base = base_currency
|
||||||
|
))
|
||||||
|
start, end = ax2.get_ylim()
|
||||||
|
ax2.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
||||||
|
|
||||||
|
transaction_df = extract_transactions(perf)
|
||||||
|
if not transaction_df.empty:
|
||||||
|
buy_df = transaction_df[transaction_df['amount'] > 0]
|
||||||
|
sell_df = transaction_df[transaction_df['amount'] < 0]
|
||||||
|
ax2.scatter(
|
||||||
|
buy_df.index.to_pydatetime(),
|
||||||
|
perf.loc[buy_df.index, 'price'],
|
||||||
|
marker='^',
|
||||||
|
s=100,
|
||||||
|
c='green',
|
||||||
|
label=''
|
||||||
|
)
|
||||||
|
ax2.scatter(
|
||||||
|
sell_df.index.to_pydatetime(),
|
||||||
|
perf.loc[sell_df.index, 'price'],
|
||||||
|
marker='v',
|
||||||
|
s=100,
|
||||||
|
c='red',
|
||||||
|
label=''
|
||||||
|
)
|
||||||
|
|
||||||
|
# Third chart: Compare percentage change between our portfolio
|
||||||
|
# and the price of the asset
|
||||||
|
ax3 = plt.subplot(413, sharex=ax1)
|
||||||
|
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
|
||||||
|
ax3.legend_.remove()
|
||||||
|
ax3.set_ylabel('Percent Change')
|
||||||
|
start, end = ax3.get_ylim()
|
||||||
|
ax3.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
||||||
|
|
||||||
|
# Fourth chart: Plot our cash
|
||||||
|
ax4 = plt.subplot(414, sharex=ax1)
|
||||||
|
perf.cash.plot(ax=ax4)
|
||||||
|
ax4.set_ylabel('Cash\n({})'.format(base_currency))
|
||||||
|
start, end = ax4.get_ylim()
|
||||||
|
ax4.yaxis.set_ticks(np.arange(0, end, end/5))
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_algorithm(
|
||||||
|
capital_base=1000,
|
||||||
|
data_frequency='minute',
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=handle_data,
|
||||||
|
analyze=analyze,
|
||||||
|
exchange_name='bitfinex',
|
||||||
|
algo_namespace=NAMESPACE,
|
||||||
|
base_currency='usd',
|
||||||
|
start=pd.to_datetime('2017-9-22', utc=True),
|
||||||
|
end=pd.to_datetime('2017-9-23', utc=True),
|
||||||
|
)
|
||||||
@@ -132,7 +132,7 @@ class Exchange:
|
|||||||
|
|
||||||
def get_symbol(self, asset):
|
def get_symbol(self, asset):
|
||||||
"""
|
"""
|
||||||
The the exchange specific symbol of the specified market.
|
The exchange specific symbol of the specified market.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@@ -203,10 +203,12 @@ class Exchange:
|
|||||||
assets = self.assets if not is_local else self.local_assets
|
assets = self.assets if not is_local else self.local_assets
|
||||||
|
|
||||||
for key in assets:
|
for key in assets:
|
||||||
if not asset and assets[key].symbol.lower() == symbol.lower() and (
|
has_data = (data_frequency == 'minute'
|
||||||
not data_frequency or (
|
and assets[key].end_minute is not None) \
|
||||||
data_frequency == 'minute' and assets[
|
or (data_frequency == 'daily'
|
||||||
key].end_minute is not None)):
|
and assets[key].end_daily is not None)
|
||||||
|
if not asset and assets[key].symbol.lower() == symbol.lower() \
|
||||||
|
and (not data_frequency or has_data):
|
||||||
asset = assets[key]
|
asset = assets[key]
|
||||||
|
|
||||||
return asset
|
return asset
|
||||||
@@ -226,18 +228,19 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
asset = None
|
asset = None
|
||||||
|
|
||||||
log.debug('searching asset {} on the server')
|
log.debug('searching asset {} on the server'.format(symbol))
|
||||||
asset = self._find_asset(asset, symbol, data_frequency, False)
|
asset = self._find_asset(asset, symbol, data_frequency, False)
|
||||||
|
|
||||||
log.debug('asset {} not found on the server, searching local assets')
|
log.debug('asset {} not found on the server, searching local '
|
||||||
|
'assets'.format(symbol))
|
||||||
asset = self._find_asset(asset, symbol, data_frequency, True)
|
asset = self._find_asset(asset, symbol, data_frequency, True)
|
||||||
|
|
||||||
if not asset:
|
if not asset:
|
||||||
all_values = list(self.assets.values()) + \
|
all_values = list(self.assets.values()) + \
|
||||||
list(self.local_assets.values())
|
list(self.local_assets.values())
|
||||||
supported_symbols = [
|
supported_symbols = sorted([
|
||||||
asset.symbol for asset in all_values
|
asset.symbol for asset in all_values
|
||||||
]
|
])
|
||||||
|
|
||||||
raise SymbolNotFoundOnExchange(
|
raise SymbolNotFoundOnExchange(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
|
|||||||
@@ -159,13 +159,17 @@ You can now test your algorithm using cryptoassets' historical pricing data,
|
|||||||
``catalyst`` provides three interfaces:
|
``catalyst`` provides three interfaces:
|
||||||
|
|
||||||
- A command-line interface (CLI),
|
- A command-line interface (CLI),
|
||||||
- the ``IPython Notebook`` magic,
|
- a :func:`~catalyst.run_algorithm()` that you can call from other
|
||||||
- and a :func:`~catalyst.run_algorithm` that you can call from other
|
Python scripts,
|
||||||
Python scripts.
|
- and the ``Jupyter Notebook`` magic.
|
||||||
|
|
||||||
We'll start with the CLI, and introduce the ``IPython Notebook`` below. Some of
|
|
||||||
the :doc:`example algorithms <example-algos>` provide instructions on how to run
|
We'll start with the CLI, and introduce the ``run_algorithm()`` in the last
|
||||||
them both from the CLI, and using the :func:`~catalyst.run_algorithm` function.
|
example of this tutorial. Some of the :doc:`example algorithms <example-algos>`
|
||||||
|
provide instructions on how to run them both from the CLI, and using the
|
||||||
|
:func:`~catalyst.run_algorithm` function. For the third method, refer to the
|
||||||
|
corresponding section on :doc:`Catalyst & Jupyter Notebook <jupyter>` after you
|
||||||
|
have assimilated the contents of this tutorial.
|
||||||
|
|
||||||
Command line interface
|
Command line interface
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -263,7 +267,7 @@ command line args all the time.
|
|||||||
Thus, to execute our algorithm from above and save the results to
|
Thus, to execute our algorithm from above and save the results to
|
||||||
``buy_btc_simple_out.pickle`` we would call ``catalyst run`` as follows:
|
``buy_btc_simple_out.pickle`` we would call ``catalyst run`` as follows:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: bash
|
||||||
|
|
||||||
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2017-9-30 -c usd --capital-base 100000 -o buy_btc_simple_out.pickle
|
catalyst run -f buy_btc_simple.py -x bitfinex --start 2016-1-1 --end 2017-9-30 -c usd --capital-base 100000 -o buy_btc_simple_out.pickle
|
||||||
|
|
||||||
@@ -560,78 +564,235 @@ If the short-mavg crosses from above we exit the positions as we assume
|
|||||||
the stock to go down further.
|
the stock to go down further.
|
||||||
|
|
||||||
As we need to have access to previous prices to implement this strategy
|
As we need to have access to previous prices to implement this strategy
|
||||||
we need a new concept: History
|
we need a new concept: History. ``data.history()`` is a convenience function
|
||||||
|
that keeps a rolling window of data for you. The first argument is the number
|
||||||
|
of bars you want to collect, the second argument is the unit (either ``'1d'``
|
||||||
|
for daily or ``'1m'`` for minute frequency, but note that you need to have
|
||||||
|
minute-level data when using ``1m``). This is a function we use in the
|
||||||
|
``handle_data()`` section.
|
||||||
|
|
||||||
``data.history()`` is a convenience function that keeps a rolling window of
|
You will note that the code below is substantially longer than the previous
|
||||||
data for you. The first argument is the number of bars you want to
|
examples. Don't get overwhelmed by it as the logic is fairly simple and easy to
|
||||||
collect, the second argument is the unit (either ``'1d'`` for ``'1m'``
|
follow. Most of the added some complexity has been added to beautify the output,
|
||||||
but note that you need to have minute-level data for using ``1m``). This is
|
which you can skim through for now. A copy of this algorithm is available in
|
||||||
a function we use in the ``handle_data()`` section:
|
the ``examples`` directory:
|
||||||
|
`dual_moving_average.py <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/dual_moving_average.py>`_.
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
%load_ext catalyst
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
%%catalyst --start 2016-4-1 --end 2017-9-30 -x bitfinex
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from logbook import Logger
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
from catalyst.api import order, record, symbol, order_target
|
from catalyst import run_algorithm
|
||||||
|
from catalyst.api import (order, record, symbol, order_target_percent,
|
||||||
|
get_open_orders)
|
||||||
|
from catalyst.exchange.stats_utils import extract_transactions
|
||||||
|
|
||||||
|
NAMESPACE = 'dual_moving_average'
|
||||||
|
log = Logger(NAMESPACE)
|
||||||
|
|
||||||
def initialize(context):
|
def initialize(context):
|
||||||
context.i = 0
|
context.i = 0
|
||||||
context.asset = symbol('btc_usd')
|
context.asset = symbol('ltc_usd')
|
||||||
|
context.base_price = None
|
||||||
|
|
||||||
|
|
||||||
def handle_data(context, data):
|
def handle_data(context, data):
|
||||||
# Skip first 150 days to get full windows
|
# define the windows for the moving averages
|
||||||
context.i += 1
|
short_window = 50
|
||||||
if context.i < 150:
|
long_window = 200
|
||||||
|
|
||||||
|
# Skip as many bars as long_window to properly compute the average
|
||||||
|
context.i += 1
|
||||||
|
if context.i < long_window:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Compute averages
|
# Compute moving averages calling data.history() for each
|
||||||
# data.history() has to be called with the same params
|
# moving average with the appropriate parameters. We choose to use
|
||||||
# from above and returns a pandas dataframe.
|
# minute bars for this simulation -> freq="1m"
|
||||||
short_mavg = data.history(context.asset, 'price', bar_count=50, frequency="1d").mean()
|
# Returns a pandas dataframe.
|
||||||
long_mavg = data.history(context.asset, 'price', bar_count=150, frequency="1d").mean()
|
short_mavg = data.history(context.asset, 'price',
|
||||||
|
bar_count=short_window, frequency="1m").mean()
|
||||||
|
long_mavg = data.history(context.asset, 'price',
|
||||||
|
bar_count=long_window, frequency="1m").mean()
|
||||||
|
|
||||||
# Trading logic
|
# Let's keep the price of our asset in a more handy variable
|
||||||
if short_mavg > long_mavg:
|
price = data.current(context.asset, 'price')
|
||||||
# order_target orders as many shares as needed to
|
|
||||||
# achieve the desired number of shares.
|
# If base_price is not set, we use the current value. This is the
|
||||||
order_target(context.asset, 100)
|
# price at the first bar which we reference to calculate price_change.
|
||||||
elif short_mavg < long_mavg:
|
if context.base_price is None:
|
||||||
order_target(context.asset, 0)
|
context.base_price = price
|
||||||
|
price_change = (price - context.base_price) / context.base_price
|
||||||
|
|
||||||
|
# Save values for later inspection
|
||||||
|
record(price=price,
|
||||||
|
cash=context.portfolio.cash,
|
||||||
|
price_change=price_change,
|
||||||
|
short_mavg=short_mavg,
|
||||||
|
long_mavg=long_mavg)
|
||||||
|
|
||||||
|
# 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.asset)
|
||||||
|
if len(orders) > 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Exit if we cannot trade
|
||||||
|
if not data.can_trade(context.asset):
|
||||||
|
return
|
||||||
|
|
||||||
|
# We check what's our position on our portfolio and trade accordingly
|
||||||
|
pos_amount = context.portfolio.positions[context.asset].amount
|
||||||
|
|
||||||
|
# Trading logic
|
||||||
|
if short_mavg > long_mavg and pos_amount == 0:
|
||||||
|
# we buy 100% of our portfolio for this asset
|
||||||
|
order_target_percent(context.asset, 1)
|
||||||
|
elif short_mavg < long_mavg and pos_amount > 0:
|
||||||
|
# we sell all our positions for this asset
|
||||||
|
order_target_percent(context.asset, 0)
|
||||||
|
|
||||||
# Save values for later inspection
|
|
||||||
record(btc=data.current(context.asset, 'price'),
|
|
||||||
short_mavg=short_mavg,
|
|
||||||
long_mavg=long_mavg)
|
|
||||||
|
|
||||||
def analyze(context, perf):
|
def analyze(context, perf):
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
fig = plt.figure(figsize=(12,12))
|
|
||||||
ax1 = fig.add_subplot(211)
|
|
||||||
perf.portfolio_value.plot(ax=ax1)
|
|
||||||
ax1.set_ylabel('portfolio value in $')
|
|
||||||
|
|
||||||
ax2 = fig.add_subplot(212)
|
# Get the base_currency that was passed as a parameter to the simulation
|
||||||
perf['btc'].plot(ax=ax2)
|
base_currency = context.exchanges.values()[0].base_currency.upper()
|
||||||
perf[['short_mavg', 'long_mavg']].plot(ax=ax2)
|
|
||||||
|
|
||||||
perf_trans = perf.ix[[t != [] for t in perf.transactions]]
|
# First chart: Plot portfolio value using base_currency
|
||||||
buys = perf_trans.ix[[t[0]['amount'] > 0 for t in perf_trans.transactions]]
|
ax1 = plt.subplot(411)
|
||||||
sells = perf_trans.ix[
|
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
|
||||||
[t[0]['amount'] < 0 for t in perf_trans.transactions]]
|
ax1.legend_.remove()
|
||||||
ax2.plot(buys.index, perf.short_mavg.ix[buys.index],
|
ax1.set_ylabel('Portfolio Value\n({})'.format(base_currency))
|
||||||
'^', markersize=10, color='m')
|
start, end = ax1.get_ylim()
|
||||||
ax2.plot(sells.index, perf.short_mavg.ix[sells.index],
|
ax1.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
||||||
'v', markersize=10, color='k')
|
|
||||||
ax2.set_ylabel('price in $')
|
|
||||||
plt.legend(loc=0)
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
Here we are explicitly defining an ``analyze()`` function that gets
|
# Second chart: Plot asset price, moving averages and buys/sells
|
||||||
automatically called once the backtest is done.
|
ax2 = plt.subplot(412, sharex=ax1)
|
||||||
|
perf.loc[:, ['price','short_mavg','long_mavg']].plot(ax=ax2, label='Price')
|
||||||
|
ax2.legend_.remove()
|
||||||
|
ax2.set_ylabel('{asset}\n({base})'.format(
|
||||||
|
asset = context.asset.symbol,
|
||||||
|
base = base_currency
|
||||||
|
))
|
||||||
|
start, end = ax2.get_ylim()
|
||||||
|
ax2.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
||||||
|
|
||||||
|
transaction_df = extract_transactions(perf)
|
||||||
|
if not transaction_df.empty:
|
||||||
|
buy_df = transaction_df[transaction_df['amount'] > 0]
|
||||||
|
sell_df = transaction_df[transaction_df['amount'] < 0]
|
||||||
|
ax2.scatter(
|
||||||
|
buy_df.index.to_pydatetime(),
|
||||||
|
perf.loc[buy_df.index, 'price'],
|
||||||
|
marker='^',
|
||||||
|
s=100,
|
||||||
|
c='green',
|
||||||
|
label=''
|
||||||
|
)
|
||||||
|
ax2.scatter(
|
||||||
|
sell_df.index.to_pydatetime(),
|
||||||
|
perf.loc[sell_df.index, 'price'],
|
||||||
|
marker='v',
|
||||||
|
s=100,
|
||||||
|
c='red',
|
||||||
|
label=''
|
||||||
|
)
|
||||||
|
|
||||||
|
# Third chart: Compare percentage change between our portfolio
|
||||||
|
# and the price of the asset
|
||||||
|
ax3 = plt.subplot(413, sharex=ax1)
|
||||||
|
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
|
||||||
|
ax3.legend_.remove()
|
||||||
|
ax3.set_ylabel('Percent Change')
|
||||||
|
start, end = ax3.get_ylim()
|
||||||
|
ax3.yaxis.set_ticks(np.arange(start, end, (end-start)/5))
|
||||||
|
|
||||||
|
# Fourth chart: Plot our cash
|
||||||
|
ax4 = plt.subplot(414, sharex=ax1)
|
||||||
|
perf.cash.plot(ax=ax4)
|
||||||
|
ax4.set_ylabel('Cash\n({})'.format(base_currency))
|
||||||
|
start, end = ax4.get_ylim()
|
||||||
|
ax4.yaxis.set_ticks(np.arange(0, end, end/5))
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_algorithm(
|
||||||
|
capital_base=1000,
|
||||||
|
data_frequency='minute',
|
||||||
|
initialize=initialize,
|
||||||
|
handle_data=handle_data,
|
||||||
|
analyze=analyze,
|
||||||
|
exchange_name='bitfinex',
|
||||||
|
algo_namespace=NAMESPACE,
|
||||||
|
base_currency='usd',
|
||||||
|
start=pd.to_datetime('2017-9-22', utc=True),
|
||||||
|
end=pd.to_datetime('2017-9-23', utc=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
In order to run the code above, you have to ingest the needed data first:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
catalyst ingest-exchange -x bitfinex -f minute -i ltc_usd
|
||||||
|
|
||||||
|
And then run the code above with the following command:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
catalyst run -f dual_moving_average.py -x bitfinex -s 2017-9-22 -e 2017-9-23 --capital-base 1000 --base-currency usd --data-frequency minute -o out.pickle
|
||||||
|
|
||||||
|
Alternatively, we can make use of the ``run_algorithm()`` function included at
|
||||||
|
the end of the file, where we can specify all the simulation parameters, and
|
||||||
|
execute this file as a Python script:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python dual_moving_average.py
|
||||||
|
|
||||||
|
Either way, we obtain the following charts:
|
||||||
|
|
||||||
|
.. image:: https://s3.amazonaws.com/enigmaco-docs/github.io/tutorial_dual_moving_average.png
|
||||||
|
|
||||||
|
|
||||||
|
A few comments on the code above:
|
||||||
|
|
||||||
|
At the beginning of our code, we import a number of Python libraries that we
|
||||||
|
will be using in different parts of our script. It's good practice to keep all
|
||||||
|
imports at the beginning of the file, as they are available globally
|
||||||
|
throughout our script. All the libraries imported in this example are already
|
||||||
|
present in your environment since they are prerequisites for the Catalyst
|
||||||
|
installation.
|
||||||
|
|
||||||
|
Focus on the code that is inside ``handle_data()`` that is where all the
|
||||||
|
trading logic occurs. You can safely dismiss most of the code in the
|
||||||
|
``analyze()`` section, which is mostly to customize the visualization of the
|
||||||
|
performance of our algorithm using the matplotlib library. You can copy and
|
||||||
|
paste this whole section into other algorithms to obtain a similar display.
|
||||||
|
|
||||||
|
Inside the ``handle_data()``, we also used the ``order_target_percent()``
|
||||||
|
function above. This and other functions like it can make order management
|
||||||
|
and portfolio rebalancing much easier.
|
||||||
|
|
||||||
|
The ``ltc_usd`` asset was arbitrarily chosen. The values of 50 and 200 for the
|
||||||
|
``short_window`` and ``long_window`` parameters are fairly common for a dual
|
||||||
|
moving average crossover strategy from the world of traditional stocks (but
|
||||||
|
bear in mind that they are usually used with daily bars instead of minute
|
||||||
|
bars). The ``start`` and ``end`` dates have been chosen so as to demonstrate
|
||||||
|
how our strategy can both perform better (blue line above green line on the
|
||||||
|
``Percent Change`` chart) and worse (green line above blue line towards the end) than the
|
||||||
|
price of the asset we are trading.
|
||||||
|
|
||||||
|
You can change any of these parameters: ``asset``, ``short_window``,
|
||||||
|
``long_window``, ``start_date`` and ``end_date`` and compare the results, and
|
||||||
|
you will see that in most cases, the performance is either worse than the
|
||||||
|
price of the asset, or you are overfitting to one specific case. As we said
|
||||||
|
at the beginning of this section, this strategy is probably not used by any
|
||||||
|
serious trader anymore, but its educational purpose.
|
||||||
|
|
||||||
Although it might not be directly apparent, the power of ``history()``
|
Although it might not be directly apparent, the power of ``history()``
|
||||||
(pun intended) can not be under-estimated as most algorithms make use of
|
(pun intended) can not be under-estimated as most algorithms make use of
|
||||||
@@ -643,21 +804,13 @@ the ``scikit-learn`` functions require ``numpy.ndarray``\ s rather than
|
|||||||
``pandas.DataFrame``\ s, so you can simply pass the underlying
|
``pandas.DataFrame``\ s, so you can simply pass the underlying
|
||||||
``ndarray`` of a ``DataFrame`` via ``.values``).
|
``ndarray`` of a ``DataFrame`` via ``.values``).
|
||||||
|
|
||||||
We also used the ``order_target()`` function above. This and other
|
|
||||||
functions like it can make order management and portfolio rebalancing
|
|
||||||
much easier.
|
|
||||||
|
|
||||||
|
Next steps
|
||||||
Conclusions
|
~~~~~~~~~~
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
We hope that this tutorial gave you a little insight into the
|
We hope that this tutorial gave you a little insight into the
|
||||||
architecture, API, and features of ``catalyst``. For next steps, check
|
architecture, API, and features of Catalyst. For next steps, check
|
||||||
out some of the
|
out some of the other :doc:`example algorithms<example-algos>`.
|
||||||
`examples <https://github.com/enigmampc/catalyst/tree/master/catalyst/examples>`__.
|
|
||||||
The natural next step would be too look into the
|
|
||||||
`buy_and_hodl <https://github.com/enigmampc/catalyst/blob/master/catalyst/examples/buy_and_hodl.py>`_
|
|
||||||
example, which is a more elaborated and realistic version of the ``buy_btc_simple`` example presented in this tutorial.
|
|
||||||
|
|
||||||
Feel free to ask questions on the ``#catalyst_dev`` channel of our
|
Feel free to ask questions on the ``#catalyst_dev`` channel of our
|
||||||
`Discord group <https://discord.gg/SJK32GY>`__ and report
|
`Discord group <https://discord.gg/SJK32GY>`__ and report
|
||||||
|
|||||||
+50
-25
@@ -2,6 +2,32 @@
|
|||||||
Release Notes
|
Release Notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
Version 0.3.10
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-11-28
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Fixed issue with fetching assets with daily frequency
|
||||||
|
|
||||||
|
Version 0.3.9
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
**Release Date**: 2017-11-28
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Fixed sortino warning issues (:issue:`77`)
|
||||||
|
- Adjusted computation of last candle of data.history (:issue:`71`)
|
||||||
|
|
||||||
|
Build
|
||||||
|
~~~~~
|
||||||
|
- Added capital_base parameter to live mode to limit cash (:issue:`79`)
|
||||||
|
- Added support for csv ingestion (:issue:`65`)
|
||||||
|
- Improved cash display in running stats (:issue:`80`)
|
||||||
|
|
||||||
|
|
||||||
Version 0.3.8
|
Version 0.3.8
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
**Release Date**: 2017-11-14
|
**Release Date**: 2017-11-14
|
||||||
@@ -59,7 +85,7 @@ Bug Fixes
|
|||||||
- Fixed issue with sell orders in backtesting
|
- Fixed issue with sell orders in backtesting
|
||||||
- Fixed data frequency issues with data.history() in backtesting
|
- Fixed data frequency issues with data.history() in backtesting
|
||||||
- Fixed an issue with can_trade()
|
- Fixed an issue with can_trade()
|
||||||
- Reduced the commission and slippage values to account for lower volume
|
- Reduced the commission and slippage values to account for lower volume
|
||||||
transactions
|
transactions
|
||||||
|
|
||||||
Build
|
Build
|
||||||
@@ -71,17 +97,17 @@ Documentation
|
|||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
- Improved installation notes for Windows C++ compiler and Conda
|
- Improved installation notes for Windows C++ compiler and Conda
|
||||||
- Addition of
|
- Addition of
|
||||||
`Jupyter Notebook guide <https://enigmampc.github.io/catalyst/jupyter.html>`_
|
`Jupyter Notebook guide <https://enigmampc.github.io/catalyst/jupyter.html>`_
|
||||||
- Addition of
|
- Addition of
|
||||||
`Live Trading page <https://enigmampc.github.io/catalyst/live-trading.html>`_
|
`Live Trading page <https://enigmampc.github.io/catalyst/live-trading.html>`_
|
||||||
- Addition of
|
- Addition of
|
||||||
`Videos page <https://enigmampc.github.io/catalyst/videos.html>`_
|
`Videos page <https://enigmampc.github.io/catalyst/videos.html>`_
|
||||||
- Addition of
|
- Addition of
|
||||||
`Resources page <https://enigmampc.github.io/catalyst/resources.html>`_
|
`Resources page <https://enigmampc.github.io/catalyst/resources.html>`_
|
||||||
- Addition of `Development Guidelines
|
- Addition of `Development Guidelines
|
||||||
<https://enigmampc.github.io/catalyst/development-guidelines.html>`_
|
<https://enigmampc.github.io/catalyst/development-guidelines.html>`_
|
||||||
- Addition of
|
- Addition of
|
||||||
`Release Notes <https://enigmampc.github.io/catalyst/releases.html>`_
|
`Release Notes <https://enigmampc.github.io/catalyst/releases.html>`_
|
||||||
- Updated code docstrings
|
- Updated code docstrings
|
||||||
|
|
||||||
@@ -132,10 +158,10 @@ Bug Fixes
|
|||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
- Fixed OS-dependent path issue in data bundle
|
- Fixed OS-dependent path issue in data bundle
|
||||||
- Changed handling of empty ``auth.json``, instead of throwing an error for
|
- Changed handling of empty ``auth.json``, instead of throwing an error for
|
||||||
missing file
|
missing file
|
||||||
- Updated ``etc/python2.7-environment.yml`` to work with Catalyst version 0.3
|
- Updated ``etc/python2.7-environment.yml`` to work with Catalyst version 0.3
|
||||||
- Updated ``catalyst/examples/buy_and_hodl.py`` and
|
- Updated ``catalyst/examples/buy_and_hodl.py`` and
|
||||||
``catalyst/examples/buy_low_sell_high.py`` to work with Catalyst version 0.3
|
``catalyst/examples/buy_low_sell_high.py`` to work with Catalyst version 0.3
|
||||||
|
|
||||||
|
|
||||||
@@ -155,18 +181,18 @@ Version 0.2.dev5
|
|||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
**Release Date**: 2017-10-03
|
**Release Date**: 2017-10-03
|
||||||
|
|
||||||
- Fixes bug in data.history function that was formatting 'volume' data as
|
- Fixes bug in data.history function that was formatting 'volume' data as
|
||||||
integers, now they are returned as floats with up to 9 decimals of precision.
|
integers, now they are returned as floats with up to 9 decimals of precision.
|
||||||
Data bundles redone.
|
Data bundles redone.
|
||||||
|
|
||||||
Version 0.2.dev4
|
Version 0.2.dev4
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
**Release Date**: 2017-09-20
|
**Release Date**: 2017-09-20
|
||||||
|
|
||||||
- Fixes bug in the pricing resolution of 1-minute data, now set to 8 decimal
|
- Fixes bug in the pricing resolution of 1-minute data, now set to 8 decimal
|
||||||
places. Pricing resolution of daily data remains set to 9 decimal places.
|
places. Pricing resolution of daily data remains set to 9 decimal places.
|
||||||
- The current data bundle takes 340MB compressed for download, and 460MB
|
- The current data bundle takes 340MB compressed for download, and 460MB
|
||||||
uncompressed on disk for Catalyst to use.
|
uncompressed on disk for Catalyst to use.
|
||||||
|
|
||||||
Version 0.2.dev3
|
Version 0.2.dev3
|
||||||
@@ -176,14 +202,14 @@ Version 0.2.dev3
|
|||||||
|
|
||||||
- 1-minute resolution OHLCV data bundle for backtesting from Poloniex exchange
|
- 1-minute resolution OHLCV data bundle for backtesting from Poloniex exchange
|
||||||
- Implementation of trading of fractional crypto assets (i.e. 0.01 BTC)
|
- Implementation of trading of fractional crypto assets (i.e. 0.01 BTC)
|
||||||
- Minimum trade size of a coin can be configured on a per-coin basis, defaults
|
- Minimum trade size of a coin can be configured on a per-coin basis, defaults
|
||||||
to 0.00000001 in backtesting (most exchanges set the minimum trade to larger
|
to 0.00000001 in backtesting (most exchanges set the minimum trade to larger
|
||||||
amounts, which will impact live trading)
|
amounts, which will impact live trading)
|
||||||
- Increased pricing resolution from 3 to 9 decimal places
|
- Increased pricing resolution from 3 to 9 decimal places
|
||||||
- The current data bundle takes 40MB compressed for download, and 99MB
|
- The current data bundle takes 40MB compressed for download, and 99MB
|
||||||
uncompressed on disk for Catalyst to use.
|
uncompressed on disk for Catalyst to use.
|
||||||
|
|
||||||
Version 0.2.dev2
|
Version 0.2.dev2
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
**Release Date**: 2017-09-07
|
**Release Date**: 2017-09-07
|
||||||
@@ -199,15 +225,15 @@ Version 0.2.dev1
|
|||||||
|
|
||||||
- Comprehensive trading functionality against exchanges Bitfinex and Bittrex.
|
- Comprehensive trading functionality against exchanges Bitfinex and Bittrex.
|
||||||
- Support for all trading pairs available on each exchange.
|
- Support for all trading pairs available on each exchange.
|
||||||
- Multiple algorithms can trade simultaneously against a single exchange
|
- Multiple algorithms can trade simultaneously against a single exchange
|
||||||
using the same account.
|
using the same account.
|
||||||
- Each algorithm has a persisted state (i.e. algorithm can be stopped and
|
- Each algorithm has a persisted state (i.e. algorithm can be stopped and
|
||||||
restarted preserving the state without data loss) that tracks all open
|
restarted preserving the state without data loss) that tracks all open
|
||||||
orders, executed transactions and portfolio positions.
|
orders, executed transactions and portfolio positions.
|
||||||
|
|
||||||
- Minute by minute portfolio performance metrics.
|
- Minute by minute portfolio performance metrics.
|
||||||
|
|
||||||
- Daily summary performance statistics compatible with pyfolio, a Python
|
- Daily summary performance statistics compatible with pyfolio, a Python
|
||||||
library for performance and risk analysis of financial portfolios
|
library for performance and risk analysis of financial portfolios
|
||||||
|
|
||||||
Version 0.1.dev9
|
Version 0.1.dev9
|
||||||
@@ -215,13 +241,13 @@ Version 0.1.dev9
|
|||||||
|
|
||||||
**Release Date**: 2017-08-28
|
**Release Date**: 2017-08-28
|
||||||
|
|
||||||
- Retrieval of crypto benchmark from bundle, instead of hitting Poloniex
|
- Retrieval of crypto benchmark from bundle, instead of hitting Poloniex
|
||||||
exchange directly
|
exchange directly
|
||||||
- Change of bundle storage provider from Dropbox to AWS
|
- Change of bundle storage provider from Dropbox to AWS
|
||||||
- Fix issue with 1/1000 scaling issue of prices in bundle
|
- Fix issue with 1/1000 scaling issue of prices in bundle
|
||||||
|
|
||||||
Version 0.1.dev8
|
Version 0.1.dev8
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
**Release Date**: 2017-08-18
|
**Release Date**: 2017-08-18
|
||||||
|
|
||||||
@@ -241,4 +267,3 @@ Version 0.1.dev6
|
|||||||
**Release Date**: 2017-07-13
|
**Release Date**: 2017-07-13
|
||||||
|
|
||||||
- Initial public release
|
- Initial public release
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user