In this example, we will build a Telegram bot that sends a signal once any Bollinger Band has been crossed. We will periodically query for the latest OHLCV data of the selected cryptocurrencies and append this data to our data pool. Additionally to receiving signals, any Telegram user can join the group and ask the bot to provide him with the current information. If the price change is higher than some number of standard deviations from the mean, while crossing the band, the bot sends a funny GIF.
import pandas as pd
import vectorbt as vbt
import logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)
# Telegram
vbt.settings.messaging['telegram']['token'] = "YOUR_TOKEN"
# Giphy
vbt.settings.messaging['giphy']['api_key'] = "YOUR_API_KEY"
# Data
SYMBOLS = ['BTC/USDT', 'ETH/USDT', 'ADA/USDT']
START = '1 hour ago UTC'
TIMEFRAME = '1m'
UPDATE_EVERY = vbt.utils.datetime.interval_to_ms(TIMEFRAME) // 1000 # in seconds
DT_FORMAT = '%d %b %Y %H:%M:%S %z'
IND_PARAMS = dict(
timeperiod=20,
nbdevup=2,
nbdevdn=2
)
CHANGE_NBDEV = 2
data = vbt.CCXTData.download(SYMBOLS, start=START, timeframe=TIMEFRAME)
print(data.wrapper.index)
2021-03-23 17:07:34.598000+00:00 - 2021-03-23 18:07:00+00:00: : 1it [00:00, 1.91it/s] 2021-03-23 17:07:36.639000+00:00 - 2021-03-23 18:07:00+00:00: : 1it [00:00, 2.26it/s] 2021-03-23 17:07:38.885000+00:00 - 2021-03-23 18:07:00+00:00: : 1it [00:00, 1.80it/s]
DatetimeIndex(['2021-03-23 17:08:00+00:00', '2021-03-23 17:09:00+00:00', '2021-03-23 17:10:00+00:00', '2021-03-23 17:11:00+00:00', '2021-03-23 17:12:00+00:00', '2021-03-23 17:13:00+00:00', '2021-03-23 17:14:00+00:00', '2021-03-23 17:15:00+00:00', '2021-03-23 17:16:00+00:00', '2021-03-23 17:17:00+00:00', '2021-03-23 17:18:00+00:00', '2021-03-23 17:19:00+00:00', '2021-03-23 17:20:00+00:00', '2021-03-23 17:21:00+00:00', '2021-03-23 17:22:00+00:00', '2021-03-23 17:23:00+00:00', '2021-03-23 17:24:00+00:00', '2021-03-23 17:25:00+00:00', '2021-03-23 17:26:00+00:00', '2021-03-23 17:27:00+00:00', '2021-03-23 17:28:00+00:00', '2021-03-23 17:29:00+00:00', '2021-03-23 17:30:00+00:00', '2021-03-23 17:31:00+00:00', '2021-03-23 17:32:00+00:00', '2021-03-23 17:33:00+00:00', '2021-03-23 17:34:00+00:00', '2021-03-23 17:35:00+00:00', '2021-03-23 17:36:00+00:00', '2021-03-23 17:37:00+00:00', '2021-03-23 17:38:00+00:00', '2021-03-23 17:39:00+00:00', '2021-03-23 17:40:00+00:00', '2021-03-23 17:41:00+00:00', '2021-03-23 17:42:00+00:00', '2021-03-23 17:43:00+00:00', '2021-03-23 17:44:00+00:00', '2021-03-23 17:45:00+00:00', '2021-03-23 17:46:00+00:00', '2021-03-23 17:47:00+00:00', '2021-03-23 17:48:00+00:00', '2021-03-23 17:49:00+00:00', '2021-03-23 17:50:00+00:00', '2021-03-23 17:51:00+00:00', '2021-03-23 17:52:00+00:00', '2021-03-23 17:53:00+00:00', '2021-03-23 17:54:00+00:00', '2021-03-23 17:55:00+00:00', '2021-03-23 17:56:00+00:00', '2021-03-23 17:57:00+00:00', '2021-03-23 17:58:00+00:00', '2021-03-23 17:59:00+00:00', '2021-03-23 18:00:00+00:00', '2021-03-23 18:01:00+00:00', '2021-03-23 18:02:00+00:00', '2021-03-23 18:03:00+00:00', '2021-03-23 18:04:00+00:00', '2021-03-23 18:05:00+00:00', '2021-03-23 18:06:00+00:00', '2021-03-23 18:07:00+00:00'], dtype='datetime64[ns, UTC]', name='Open time', freq='T')
def get_bbands(data):
return vbt.IndicatorFactory.from_talib('BBANDS').run(
data.get('Close'), **IND_PARAMS, hide_params=list(IND_PARAMS.keys()))
def get_info(bbands):
info = dict()
info['last_price'] = bbands.close.iloc[-1]
info['last_change'] = (bbands.close.iloc[-1] - bbands.close.iloc[-2]) / bbands.close.iloc[-1]
info['last_crossed_above_upper'] = bbands.close_crossed_above(bbands.upperband).iloc[-1]
info['last_crossed_below_upper'] = bbands.close_crossed_below(bbands.upperband).iloc[-1]
info['last_crossed_below_lower'] = bbands.close_crossed_below(bbands.lowerband).iloc[-1]
info['last_crossed_above_lower'] = bbands.close_crossed_above(bbands.lowerband).iloc[-1]
info['bw'] = (bbands.upperband - bbands.lowerband) / bbands.middleband
info['last_bw_zscore'] = info['bw'].vbt.zscore().iloc[-1]
info['last_change_zscore'] = bbands.close.vbt.pct_change().vbt.zscore().iloc[-1]
info['last_change_pos'] = info['last_change_zscore'] >= CHANGE_NBDEV
info['last_change_neg'] = info['last_change_zscore'] <= -CHANGE_NBDEV
return info
def format_symbol_info(symbol, info):
last_change = info['last_change'][symbol]
last_price = info['last_price'][symbol]
last_bw_zscore = info['last_bw_zscore'][symbol]
return "{} ({:.2%}, {}, {:.2f})".format(symbol, last_change, last_price, last_bw_zscore)
def format_signals_info(emoji, signals, info):
symbols = signals.index[signals]
symbol_msgs = []
for symbol in symbols:
symbol_msgs.append(format_symbol_info(symbol, info))
return "{} {}".format(emoji, ', '.join(symbol_msgs))
from telegram.ext import CommandHandler
class MyTelegramBot(vbt.TelegramBot):
def __init__(self, data, *args, **kwargs):
super().__init__(data=data, *args, **kwargs)
self.data = data
self.update_ts = data.wrapper.index[-1]
@property
def custom_handlers(self):
return (CommandHandler('info', self.info_callback),)
def info_callback(self, update, context):
chat_id = update.effective_chat.id
if len(context.args) != 1:
self.send_message(chat_id, "Please provide one symbol.")
return
symbol = context.args[0]
if symbol not in SYMBOLS:
self.send_message(chat_id, f"There is no such symbol as \"{symbol}\".")
return
bbands = get_bbands(self.data)
info = get_info(bbands)
messages = [format_symbol_info(symbol, info)]
message = '\n'.join(["{}:".format(self.update_ts.strftime(DT_FORMAT))] + messages)
self.send_message(chat_id, message)
@property
def start_message(self):
index = self.data.wrapper.index
return f"""Hello!
Starting with {len(index)} rows from {index[0].strftime(DT_FORMAT)} to {index[-1].strftime(DT_FORMAT)}."""
@property
def help_message(self):
return """Message format:
[event] [symbol] ([price change], [new price], [bandwidth z-score])
Event legend:
⬆️ - Price went above upper band
⤵️ - Price retraced below upper band
⬇️ - Price went below lower band
⤴️ - Price retraced above lower band
GIF is sent once a band is crossed and the price change is 2 stds from the mean."""
telegram_bot = MyTelegramBot(data)
telegram_bot.start(in_background=True)
2021-03-23 19:07:41,319 - vectorbt.utils.messaging - INFO - Initializing bot 2021-03-23 19:07:41,325 - vectorbt.utils.messaging - INFO - Loaded chat ids [447924619] 2021-03-23 19:07:41,444 - vectorbt.utils.messaging - INFO - Running bot vectorbt_bot 2021-03-23 19:07:41,461 - apscheduler.scheduler - INFO - Scheduler started 2021-03-23 19:07:41,629 - vectorbt.utils.messaging - INFO - 447924619 - Bot: "I'm back online!"
class MyDataUpdater(vbt.DataUpdater):
def __init__(self, data, telegram_bot, **kwargs):
super().__init__(data, telegram_bot=telegram_bot, **kwargs)
self.telegram_bot = telegram_bot
self.update_ts = data.wrapper.index[-1]
def update(self):
super().update()
self.update_ts = pd.Timestamp.now(tz=TZ_CONVERT)
self.telegram_bot.data = self.data
self.telegram_bot.update_ts = self.update_ts
bbands = get_bbands(self.data)
info = get_info(bbands)
messages = []
if info['last_crossed_above_upper'].any():
messages.append(format_signals_info('⬆️', info['last_crossed_above_upper'], info))
if info['last_crossed_below_upper'].any():
messages.append(format_signals_info('⤵️', info['last_crossed_below_upper'], info))
if info['last_crossed_below_lower'].any():
messages.append(format_signals_info('⬇️', info['last_crossed_below_lower'], info))
if info['last_crossed_above_lower'].any():
messages.append(format_signals_info('⤴️', info['last_crossed_above_lower'], info))
if len(messages) > 0:
message = '\n'.join(["{}:".format(self.update_ts.strftime(DT_FORMAT))] + messages)
self.telegram_bot.send_message_to_all(message)
if (info['last_crossed_above_upper'] & info['last_change_pos']).any():
self.telegram_bot.send_giphy_to_all("launch")
if (info['last_crossed_below_lower'] & info['last_change_neg']).any():
self.telegram_bot.send_giphy_to_all("fall")
data_updater = MyDataUpdater(data, telegram_bot)
data_updater.update_every(UPDATE_EVERY)
2021-03-23 19:07:41,670 - vectorbt.utils.schedule - INFO - Starting schedule manager with jobs [Every 60 seconds do update() (last run: [never], next run: 2021-03-23 19:08:41)] 2021-03-23 19:08:47,689 - vectorbt.data.updater - INFO - Updated data has 61 rows from 2021-03-23 17:08:00+00:00 to 2021-03-23 18:08:00+00:00 2021-03-23 19:08:47,822 - numexpr.utils - INFO - NumExpr defaulting to 4 threads. 2021-03-23 19:09:15,117 - vectorbt.utils.messaging - INFO - 447924619 - User: "/info BTC/USDT" 2021-03-23 19:09:15,281 - vectorbt.utils.messaging - INFO - 447924619 - Bot: "23 Mar 2021 18:08:47 +0000: BTC/USDT (0.08%, 55638.07, -1.06)" 2021-03-23 19:09:54,766 - vectorbt.data.updater - INFO - Updated data has 62 rows from 2021-03-23 17:08:00+00:00 to 2021-03-23 18:09:00+00:00 2021-03-23 19:10:08,781 - vectorbt.utils.schedule - INFO - Stopping schedule manager
telegram_bot.stop()
2021-03-23 19:10:08,791 - vectorbt.utils.messaging - INFO - Stopping bot 2021-03-23 19:10:08,793 - apscheduler.scheduler - INFO - Scheduler has been shut down