# Bollinger Bands Mean Reversion v3.0
''' CALL OF CHTULHU '''
# litepresence
# with rolling max drawdown
######################################################
# Sail with Caution: Chtulu Will Eat You!
######################################################
DEPTH = 2
MA_PERIOD = 40
STD_PERIOD = 40
THRESHOLD_1 = 20.00
THRESHOLD_2 = 1.000
THRESHOLD_3 = 1.000
THRESHOLD_4 = 1.000
THRESHOLD_5 = 1.000
THRESHOLD_6 = 1.000
THRESHOLD_7 = 1.000
THRESHOLD_8 = 2.000
THRESHOLD_9 = 2.000
MIN_WIDTH = False
MAX_DRAW = False
######################################################
######################################################
######################################################
def initialize():
storage.invested = False
storage.hold = storage.get('hold', False)
storage.trades = storage.get('trades', 0)
storage.order_fee = storage.get('order_fee', 0)
def tick():
# build fresh arrays on each tick
ma = []
std = []
upper = []
lower = []
width = []
price = []
for n in range(DEPTH):
ma.append(0)
std.append(0)
price.append(0)
upper.append(0)
lower.append(0)
width.append(0)
for n in range(DEPTH):
ma[-n] = (data[info.primary_pair][-n].ma(STD_PERIOD))
std[-n] = (data[info.primary_pair][-n].std(MA_PERIOD))
price[-n] = (float(data[info.primary_pair][-n].close))
for n in range(DEPTH):
upper[-n] = (ma[-n] + (Decimal(2) * std[-n]))
lower[-n] = (ma[-n] - (Decimal(2) * std[-n]))
for n in range(DEPTH):
width[-n] = (float(upper[-n] - lower[-n]))
# check arrays
if info.tick == 5:
log('std')
for n in range(DEPTH):
log('%.2f %s' % (std[-n], -n))
log('ma')
for n in range(DEPTH):
log('%.2f %s' % (ma[-n], -n))
log('upper')
for n in range(DEPTH):
log('%.2f %s' % (upper[-n], -n))
log('lower')
for n in range(DEPTH):
log('%.2f %s' % (lower[-n], -n))
log('width')
for n in range(DEPTH):
log('%.2f %s' % (width[-n], -n))
log('price')
for n in range(DEPTH):
log('%.2f %s' % (price[-n], -n))
min_width = float(min(upper)-max(lower))
if MIN_WIDTH:
diff = (width[0] - min_width) / (min_width + 0.000001)
else:
diff = (width[0] - width[-1]) / (width[-1] + 0.000001)
if diff > 1:
diff = 1
# PRICE IS CHANNELED:
# ENTER if price is 2 standard deviations BELOW moving average
# EXIT if price is 2 standard deviations ABOVE moving average
threshold = (float(DEPTH)/float(THRESHOLD_1))
if diff < threshold:
if storage.hold == False:
if THRESHOLD_2*price[0] < lower[0] and not storage.invested:
log('Buy Channel')
order = buy(info.primary_pair)
storage.invested = True
storage.hold = False
storage.trades += 1
storage.order_fee += (
order.fee.to_float() * float(order.price))
if storage.hold == False:
if THRESHOLD_3*price[0] > upper[0] and storage.invested:
log('Sell Channel')
order = sell(info.primary_pair)
storage.invested = False
storage.hold = False
storage.trades += 1
storage.order_fee += order.fee.to_float()
# PRICE IS TRENDING:
# If CHANNEL opens, ENTER and HOLD if price is ABOVE moving average
# If CHANNEL opens, EXIT and HOLD if price is BELOW moving average
else:
if (THRESHOLD_4*price[0] > ma[0]) and not storage.invested:
log('Buy Breakout')
order = buy(info.primary_pair)
storage.invested = True
storage.hold = True
storage.trades += 1
storage.order_fee += (
order.fee.to_float() * float(order.price))
if (THRESHOLD_5*price[0] < ma[0]) and storage.invested:
log('Sell Breakout')
order = sell(info.primary_pair)
storage.invested = False
storage.hold = True
storage.trades += 1
storage.order_fee += order.fee.to_float()
# TREND IS ENDING:
# If HOLDING FIAT trend, ENTER if price is ABOVE moving average
# If HOLDING BTC trend, EXIT if price is BELOW moving average
if storage.hold == True:
if not storage.invested:
if THRESHOLD_6*price[0] > ma[0]:
log('Buy Bottom')
order = buy(info.primary_pair)
storage.invested = True
storage.hold = False
storage.trades += 1
storage.order_fee += (
order.fee.to_float() * float(order.price))
elif storage.invested:
if THRESHOLD_7*price[0] < ma[0]:
log('Sell Top')
order = sell(info.primary_pair)
storage.invested = False
storage.hold = False
storage.trades += 1
storage.order_fee += order.fee.to_float()
if info.tick % (3600/info.interval) == 0:
plot('MA', ma[0])
plot('Upper', upper[0])
plot('Lower', lower[0])
plot('diff', diff, secondary=True)
plot('threshold', threshold, secondary=True)
if info.tick == 0:
plot('offset', 5, secondary=True)
if MAX_DRAW:
dd()
if MAX_DRAW:
import time
ROLLING = 30 # days
def dd():
if info.tick == 0:
storage.start = time.time()
dd, storage.max_dd = max_dd(0)
bnh_dd, storage.max_bnh_dd = bnh_max_dd(0)
rolling_dd, storage.max_rolling_dd = max_dd(
ROLLING*86400/info.interval)
rolling_bnh_dd, storage.max_rolling_bnh_dd = bnh_max_dd(
ROLLING*86400/info.interval)
plot('dd', dd, secondary=True)
plot('bnh_dd', bnh_dd, secondary=True)
plot('rolling_dd', rolling_dd, secondary=True)
plot('rolling_bnh_dd', rolling_bnh_dd, secondary=True)
plot('zero', 0, secondary=True)
if info.tick == 0:
plot('dd_floor', -200, secondary=True)
def max_dd(rolling):
port_value = float(portfolio.usd+portfolio.btc*data.btc_usd.price)
max_value = 'max_value_' + str(rolling)
values_since_max = 'values_since_max_' + str(rolling)
max_dd = 'max_dd_' + str(rolling)
storage[max_value] = storage.get(max_value, [port_value])
storage[values_since_max] = storage.get(values_since_max, [port_value])
storage[max_dd] = storage.get(max_dd, [0])
storage[max_value].append(port_value)
if port_value > max(storage[max_value]):
storage[values_since_max] = [port_value]
else:
storage[values_since_max].append(port_value)
storage[max_value] = storage[max_value][-rolling:]
storage[values_since_max] = storage[values_since_max][-rolling:]
dd = -100*(max(storage[max_value]) - storage[values_since_max][-1]
)/max(storage[max_value])
storage[max_dd].append(float(dd))
storage[max_dd] = storage[max_dd][-rolling:]
max_dd = min(storage[max_dd])
return (dd, max_dd)
def bnh_max_dd(rolling):
coin = data.btc_usd.price
bnh_max_value = 'bnh_max_value_' + str(rolling)
bnh_values_since_max = 'bnh_values_since_max_' + str(rolling)
bnh_max_dd = 'bnh_max_dd_' + str(rolling)
storage[bnh_max_value] = storage.get(bnh_max_value, [coin])
storage[bnh_values_since_max] = storage.get(bnh_values_since_max, [coin])
storage[bnh_max_dd] = storage.get(bnh_max_dd, [0])
storage[bnh_max_value].append(coin)
if coin > max(storage[bnh_max_value]):
storage[bnh_values_since_max] = [coin]
else:
storage[bnh_values_since_max].append(coin)
storage[bnh_max_value] = storage[bnh_max_value][-rolling:]
storage[bnh_values_since_max] = storage[bnh_values_since_max][-rolling:]
bnh_dd = -100*(max(storage[bnh_max_value]) - storage[bnh_values_since_max][-1]
)/max(storage[bnh_max_value])
storage[bnh_max_dd].append(float(bnh_dd))
storage[bnh_max_dd] = storage[bnh_max_dd][-rolling:]
bnh_max_dd = min(storage[bnh_max_dd])
return (bnh_dd, bnh_max_dd)
def stop():
if MAX_DRAW:
# MAX DRAW DOWN
#########################
log('ELAPSED TIME: %.1f sec' % (time.time() - storage.start))
log('MAX DD......: %.2f pct' % storage.max_dd)
log('R MAX DD....: %.2f pct' % storage.max_rolling_dd)
log('MAX BNH DD..: %.2f pct' % storage.max_bnh_dd)
log('R MAX BNH DD: %.2f pct' % storage.max_rolling_bnh_dd)
log('Total Trades.: %s' % storage.trades)
log('Exchange.....: %s' % info.primary_exchange)
log('Fee Percent..: %s' %
fees[exchanges[str(info.primary_exchange)]][info.primary_pair])
log('Fees Paid....: %.2f' % storage.order_fee)
log('Fees Paid BTC: %.2f' %
(storage.order_fee/float(data[info.primary_pair].price)))