Created
February 9, 2018 00:48
-
-
Save vonpupp/ac7c909ed80a3ec020846d5475bfe183 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
#!/usr/bin/env python2 | |
# Issue: | |
# https://github.com/enigmampc/catalyst/issues/216 | |
# Clear the cache | |
# catalyst clean-algo -n my-algo-namespace | |
import sys | |
import os | |
import pandas as pd | |
import signal | |
#import talib | |
from logbook import Logger | |
from catalyst import run_algorithm | |
from catalyst.api import ( | |
symbol, | |
record, | |
order, | |
order_target, | |
order_target_percent, | |
get_open_orders | |
) | |
from catalyst.finance import commission | |
#from base.telegrambot import TelegramBot | |
class GracefulKiller: | |
# Source: https://stackoverflow.com/a/31464349 | |
def __init__(self, context): | |
self.kill_now = False | |
self.signal = 0 | |
self.context = context | |
signal.signal(signal.SIGINT, self.exit_gracefully) | |
def exit_gracefully(self, signum, frame): | |
self.kill_now = True | |
self.signal = signum | |
if hasattr(self.context, 'telegram_bot') and self.context.telegram_bot is not None: | |
self.context.telegram_bot.updater.stop() | |
sys.exit(0) | |
def exit(self): | |
return self.kill_now | |
class SimulationParameters: | |
#MODE = 'backtest' | |
MODE = 'paper' | |
CAPITAL_BASE=1000 | |
""" | |
Capital base used on this simulation | |
""" | |
DATA_FREQUECY='minute' | |
EXCHANGE_NAME='bitfinex' | |
""" | |
Exchange used on this simulation | |
""" | |
DATA_DIR = './' | |
ALGO_NAMESPACE = os.path.basename(__file__).split('.')[0] | |
ALGO_NAMESPACE_IMAGE = '{}/{}/{}.png'.format(DATA_DIR, 'images', ALGO_NAMESPACE) | |
ALGO_NAMESPACE_RESULTS_TABLE = '{}/{}/{}.csv'.format(DATA_DIR, 'tables', ALGO_NAMESPACE+'_results') | |
ALGO_NAMESPACE_TRANSACTIONS_TABLE = '{}/{}/{}.csv'.format(DATA_DIR, 'tables', ALGO_NAMESPACE+'_transactions') | |
BASE_CURRENCY='usd' | |
# SHORT PERIOD | |
START_DATE='2017-09-15' | |
""" | |
Start date used on this simulation | |
""" | |
END_DATE='2017-09-20' | |
""" | |
End date used on this simulation | |
""" | |
SKIP_FIRST_CANDLES = 0 | |
CANDLES_SAMPLE_RATE = 120 | |
#CANDLES_SAMPLE_RATE = 60 | |
#CANDLES_SAMPLE_RATE = 30 | |
#CANDLES_SAMPLE_RATE = 1 | |
""" | |
Candle interval used on this simulation (in minutes) | |
""" | |
# http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases | |
# 30 minute interval ohlcv data (the standard data required for candlestick or | |
# indicators/signals) | |
# 30T means 30 minutes re-sampling of one minute data. | |
CANDLES_FREQUENCY = '120T' | |
#CANDLES_FREQUENCY = '60T' | |
#CANDLES_FREQUENCY = '30T' | |
#CANDLES_FREQUENCY = '1T' | |
CANDLES_BUFFER_SIZE = 48 | |
COIN_PAIR = 'neo_usd' | |
""" | |
Coin pair used on this simulation | |
""" | |
# TRANSACTIONS | |
COMMISSION_FEE = 0.0030 | |
BUY_MIN_AMOUNT = 5 # i.e: USD | |
SELL_MIN_AMOUNT = 0.001 # i.e: USD | |
BUY_SELL_PERCENTAGE = 1 #0.50 | |
BUY_PERCENTAGE = BUY_SELL_PERCENTAGE | |
SELL_PERCENTAGE = BUY_SELL_PERCENTAGE | |
BASE_PRICE = 'close' | |
""" | |
Base price used (close / Heiken Ashi) | |
""" | |
log = None | |
parameters = None | |
def print_facts(context): | |
context.log.info(""" | |
Index: {} | |
Date: {} | |
Candle: | |
O: {} | |
H: {} | |
L: {} | |
C: {} | |
V: {} | |
Metrics: | |
... | |
Portfolio: | |
Base price: {} | |
Base coin (coin2/usd): {} | |
Amount (coin1/btc): {} | |
""".format( | |
# Facts | |
context.i, | |
context.curr_minute, | |
context.candles_open[-1], | |
context.candles_high[-1], | |
context.candles_low[-1], | |
context.candles_close[-1], | |
context.candles_volume[-1], | |
# Metrics | |
# ... | |
# Portfolio | |
context.curr_base_price, | |
context.portfolio.cash, | |
context.portfolio.positions[context.coin_pair].amount, | |
)) | |
def print_facts_telegram(context): | |
price = context.curr_base_price | |
amount = context.portfolio.positions[context.coin_pair].amount | |
pnl = context.portfolio.pnl | |
capital_used = context.portfolio.capital_used | |
portfolio_value = context.portfolio.portfolio_value | |
portfolio_returns = context.portfolio.returns | |
starting_cash = context.portfolio.starting_cash | |
cash = context.portfolio.cash | |
msg = """ | |
Status... | |
Price: {} | |
Starting cash: {} | |
Cash: {} | |
Capital used: {} | |
Amount: {} | |
Portfolio value: {} | |
Returns: {} | |
PnL: {} | |
""".format( | |
price, | |
starting_cash, | |
cash, | |
capital_used, | |
amount, | |
portfolio_value, | |
portfolio_returns, | |
pnl, | |
) | |
if hasattr(context, 'telegram_bot') and context.telegram_bot is not None: | |
context.telegram_bot.msg(msg) | |
def default_initialize(context): | |
# FIXME: set_benchmark | |
#set_benchmark(symbol(context.parameters.COIN_PAIR)) | |
import ipdb; ipdb.set_trace() # BREAKPOINT | |
context.coin_pair = symbol(context.parameters.COIN_PAIR) | |
context.base_price = None | |
context.current_day = None | |
context.counter = -1 | |
context.i = 0 | |
context.candles_sample_rate = context.parameters.CANDLES_SAMPLE_RATE | |
context.candles_frequency = context.parameters.CANDLES_FREQUENCY | |
context.candles_buffer_size = context.parameters.CANDLES_BUFFER_SIZE | |
context.set_commission(commission.PerShare(cost=context.parameters.COMMISSION_FEE)) | |
def default_handle_data(context, data): | |
context.curr_minute = data.current_dt | |
context.counter += 1 | |
if context.candles_sample_rate == 1: | |
context.i += 1 | |
elif context.counter % context.candles_sample_rate != 0: | |
context.i += 1 | |
return | |
if context.i < context.parameters.SKIP_FIRST_CANDLES: | |
return | |
context.candles_open = data.history( | |
context.coin_pair, | |
'open', | |
bar_count=context.candles_buffer_size, | |
frequency=context.candles_frequency) | |
context.candles_high = data.history( | |
context.coin_pair, | |
'high', | |
bar_count=context.candles_buffer_size, | |
frequency=context.candles_frequency) | |
context.candles_low = data.history( | |
context.coin_pair, | |
'low', | |
bar_count=context.candles_buffer_size, | |
frequency=context.candles_frequency) | |
context.candles_close = data.history( | |
context.coin_pair, | |
'price', | |
bar_count=context.candles_buffer_size, | |
frequency=context.candles_frequency) | |
context.candles_volume = data.history( | |
context.coin_pair, | |
'volume', | |
bar_count=context.candles_buffer_size, | |
frequency=context.candles_frequency) | |
# FIXME: Here is the error! | |
# The candles_close frame shows more or less always a value of 94, while | |
# bitcoin price is very different from that | |
#print(context.candles_close) | |
context.base_prices = context.candles_close | |
cash = context.portfolio.cash | |
amount = context.portfolio.positions[context.coin_pair].amount | |
price = data.current(context.coin_pair, 'price') | |
order_id = None | |
context.last_base_price = context.base_prices[-2] | |
context.curr_base_price = context.base_prices[-1] | |
# TA calculations | |
# ... | |
# Sanity checks | |
#assert cash >= 0 | |
if cash < 0: | |
import ipdb; ipdb.set_trace() # BREAKPOINT | |
print_facts(context) | |
print_facts_telegram(context) | |
# Order management | |
net_shares = 0 | |
if context.counter == 2: | |
brute_shares = (cash / price) * context.parameters.BUY_PERCENTAGE | |
share_commission_fee = brute_shares * context.parameters.COMMISSION_FEE | |
net_shares = brute_shares - share_commission_fee | |
buy_order_id = order(context.coin_pair, net_shares) | |
if context.counter == 3: | |
brute_shares = amount * context.parameters.SELL_PERCENTAGE | |
share_commission_fee = brute_shares * context.parameters.COMMISSION_FEE | |
net_shares = -(brute_shares - share_commission_fee) | |
sell_order_id = order(context.coin_pair, net_shares) | |
# Record | |
record( | |
price=price, | |
#volume=current['volume'], | |
#price_change=price_change, | |
# Metrics | |
cash=cash, | |
#buy=context.buy, | |
#sell=context.sell | |
) | |
def default_analyze(context=None, perf=None): | |
pass | |
def initialize(context): | |
global log | |
context.parameters = parameters | |
context.log = Logger(context.parameters.ALGO_NAMESPACE) | |
log = context.log | |
default_initialize(context) | |
context.killer = GracefulKiller(context) | |
context.telegram_bot = None | |
#TELEGRAM_TOKEN='token' | |
#context.telegram_bot = TelegramBot() | |
#context.telegram_bot.initialize(TELEGRAM_TOKEN, context) | |
if __name__ == '__main__': | |
# Parameters: | |
parameters = SimulationParameters() | |
start_date=pd.to_datetime(parameters.START_DATE, utc=True) | |
end_date=pd.to_datetime(parameters.END_DATE, utc=True) | |
if parameters.MODE == 'backtest': | |
results = run_algorithm( | |
capital_base=parameters.CAPITAL_BASE, | |
data_frequency=parameters.DATA_FREQUECY, | |
initialize=initialize, | |
handle_data=default_handle_data, | |
analyze=default_analyze, | |
exchange_name=parameters.EXCHANGE_NAME, | |
algo_namespace=parameters.ALGO_NAMESPACE, | |
base_currency=parameters.BASE_CURRENCY, | |
start=start_date, | |
end=end_date, | |
live=False, | |
live_graph=False | |
) | |
returns_daily = results | |
results.to_csv('{}'.format(parameters.ALGO_NAMESPACE_RESULTS_TABLE)) | |
# returns_daily = returns_minutely.add(1).groupby(pd.TimeGrouper('24H')).prod().add(-1) | |
# FIXME: pyfolio integration | |
#pf_data = pyfolio.utils.extract_rets_pos_txn_from_zipline(results) | |
#pf_data = pyfolio.utils.extract_rets_pos_txn_from_zipline(results[:'2017-01-01']) | |
# pyfolio.create_full_tear_sheet(*pf_data) | |
elif parameters.MODE == 'paper': | |
results = run_algorithm( | |
capital_base=parameters.CAPITAL_BASE, | |
data_frequency=parameters.DATA_FREQUECY, | |
initialize=initialize, | |
handle_data=default_handle_data, | |
analyze=default_analyze, | |
exchange_name=parameters.EXCHANGE_NAME, | |
algo_namespace=parameters.ALGO_NAMESPACE, | |
base_currency=parameters.BASE_CURRENCY, | |
live=True, | |
simulate_orders=True, | |
live_graph=False | |
) | |
elif parameters.MODE == 'live': | |
results = run_algorithm( | |
initialize=initialize, | |
handle_data=default_handle_data, | |
analyze=default_analyze, | |
exchange_name=parameters.EXCHANGE_NAME, | |
algo_namespace=parameters.ALGO_NAMESPACE, | |
base_currency=parameters.BASE_CURRENCY, | |
live=True, | |
live_graph=True | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment