286 lines
9.9 KiB
Plaintext
286 lines
9.9 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"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."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from telegram import __version__ as TG_VER\n",
|
|
"\n",
|
|
"try:\n",
|
|
" from telegram import __version_info__\n",
|
|
"except ImportError:\n",
|
|
" __version_info__ = (0, 0, 0, 0, 0)\n",
|
|
"\n",
|
|
"if __version_info__ >= (20, 0, 0, \"alpha\", 1):\n",
|
|
" raise RuntimeError(f\"This example is not compatible with your current PTB version {TG_VER}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from vectorbtpro import *\n",
|
|
"# whats_imported()\n",
|
|
"\n",
|
|
"import logging"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)\n",
|
|
"logger = logging.getLogger(__name__)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Telegram\n",
|
|
"vbt.settings.messaging['telegram']['token'] = \"YOUR_TOKEN\"\n",
|
|
"\n",
|
|
"# Giphy\n",
|
|
"vbt.settings.messaging['giphy']['api_key'] = \"YOUR_API_KEY\"\n",
|
|
"\n",
|
|
"# Data\n",
|
|
"SYMBOLS = ['BTC/USDT', 'ETH/USDT', 'ADA/USDT']\n",
|
|
"START = '1 hour ago UTC'\n",
|
|
"TIMEFRAME = '1m'\n",
|
|
"UPDATE_EVERY = vbt.utils.datetime_.interval_to_ms(TIMEFRAME) // 1000 # in seconds\n",
|
|
"DT_FORMAT = '%d %b %Y %H:%M:%S %z'\n",
|
|
"IND_PARAMS = dict(\n",
|
|
" timeperiod=20, \n",
|
|
" nbdevup=2, \n",
|
|
" nbdevdn=2\n",
|
|
")\n",
|
|
"CHANGE_NBDEV = 2"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"data = vbt.CCXTData.pull(SYMBOLS, start=START, timeframe=TIMEFRAME)\n",
|
|
"\n",
|
|
"print(data.wrapper.index)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def get_bbands(data):\n",
|
|
" return vbt.IndicatorFactory.from_talib('BBANDS').run(\n",
|
|
" data.get('Close'), **IND_PARAMS, hide_params=list(IND_PARAMS.keys()))\n",
|
|
"\n",
|
|
"\n",
|
|
"def get_info(bbands):\n",
|
|
" info = dict()\n",
|
|
" info['last_price'] = bbands.close.iloc[-1]\n",
|
|
" info['last_change'] = (bbands.close.iloc[-1] - bbands.close.iloc[-2]) / bbands.close.iloc[-1]\n",
|
|
" info['last_crossed_above_upper'] = bbands.close_crossed_above(bbands.upperband).iloc[-1]\n",
|
|
" info['last_crossed_below_upper'] = bbands.close_crossed_below(bbands.upperband).iloc[-1]\n",
|
|
" info['last_crossed_below_lower'] = bbands.close_crossed_below(bbands.lowerband).iloc[-1]\n",
|
|
" info['last_crossed_above_lower'] = bbands.close_crossed_above(bbands.lowerband).iloc[-1]\n",
|
|
" info['bw'] = (bbands.upperband - bbands.lowerband) / bbands.middleband\n",
|
|
" info['last_bw_zscore'] = info['bw'].vbt.zscore().iloc[-1]\n",
|
|
" info['last_change_zscore'] = bbands.close.vbt.pct_change().vbt.zscore().iloc[-1]\n",
|
|
" info['last_change_pos'] = info['last_change_zscore'] >= CHANGE_NBDEV\n",
|
|
" info['last_change_neg'] = info['last_change_zscore'] <= -CHANGE_NBDEV\n",
|
|
" return info\n",
|
|
"\n",
|
|
"\n",
|
|
"def format_symbol_info(symbol, info):\n",
|
|
" last_change = info['last_change'][symbol]\n",
|
|
" last_price = info['last_price'][symbol]\n",
|
|
" last_bw_zscore = info['last_bw_zscore'][symbol]\n",
|
|
" return \"{} ({:.2%}, {}, {:.2f})\".format(symbol, last_change, last_price, last_bw_zscore)\n",
|
|
"\n",
|
|
"\n",
|
|
"def format_signals_info(emoji, signals, info):\n",
|
|
" symbols = signals.index[signals]\n",
|
|
" symbol_msgs = []\n",
|
|
" for symbol in symbols:\n",
|
|
" symbol_msgs.append(format_symbol_info(symbol, info))\n",
|
|
" return \"{} {}\".format(emoji, ', '.join(symbol_msgs))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from telegram.ext import CommandHandler\n",
|
|
"\n",
|
|
"class MyTelegramBot(vbt.TelegramBot):\n",
|
|
" def __init__(self, data, **kwargs):\n",
|
|
" super().__init__(data=data, **kwargs)\n",
|
|
" \n",
|
|
" self.data = data\n",
|
|
" self.update_ts = data.wrapper.index[-1]\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def custom_handlers(self):\n",
|
|
" return (CommandHandler('info', self.info_callback),)\n",
|
|
" \n",
|
|
" def info_callback(self, update, context):\n",
|
|
" chat_id = update.effective_chat.id\n",
|
|
" if len(context.args) != 1:\n",
|
|
" await self.send_message(chat_id, \"Please provide one symbol.\")\n",
|
|
" return\n",
|
|
" symbol = context.args[0]\n",
|
|
" if symbol not in SYMBOLS:\n",
|
|
" await self.send_message(chat_id, f\"There is no such symbol as \\\"{symbol}\\\".\")\n",
|
|
" return\n",
|
|
" \n",
|
|
" bbands = get_bbands(self.data)\n",
|
|
" info = get_info(bbands)\n",
|
|
" messages = [format_symbol_info(symbol, info)]\n",
|
|
" message = '\\n'.join([\"{}:\".format(self.update_ts.strftime(DT_FORMAT))] + messages)\n",
|
|
" await self.send_message(chat_id, message)\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def start_message(self):\n",
|
|
" index = self.data.wrapper.index\n",
|
|
" return f\"\"\"Hello! \n",
|
|
"\n",
|
|
"Starting with {len(index)} rows from {index[0].strftime(DT_FORMAT)} to {index[-1].strftime(DT_FORMAT)}.\"\"\"\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def help_message(self):\n",
|
|
" return \"\"\"Message format:\n",
|
|
"[event] [symbol] ([price change], [new price], [bandwidth z-score])\n",
|
|
" \n",
|
|
"Event legend:\n",
|
|
"⬆️ - Price went above upper band\n",
|
|
"⤵️ - Price retraced below upper band\n",
|
|
"⬇️ - Price went below lower band\n",
|
|
"⤴️ - Price retraced above lower band\n",
|
|
"\n",
|
|
"GIF is sent once a band is crossed and the price change is 2 stds from the mean.\"\"\""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"telegram_bot = MyTelegramBot(data)\n",
|
|
"telegram_bot.start(in_background=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class MyDataUpdater(vbt.DataUpdater):\n",
|
|
" _expected_keys=None\n",
|
|
" \n",
|
|
" def __init__(self, data, telegram_bot, **kwargs):\n",
|
|
" super().__init__(data, telegram_bot=telegram_bot, **kwargs)\n",
|
|
" \n",
|
|
" self.telegram_bot = telegram_bot\n",
|
|
" self.update_ts = data.wrapper.index[-1]\n",
|
|
" \n",
|
|
" def update(self):\n",
|
|
" super().update()\n",
|
|
" self.update_ts = vbt.timestamp(tz=self.update_ts.tz)\n",
|
|
" self.telegram_bot.data = self.data\n",
|
|
" self.telegram_bot.update_ts = self.update_ts\n",
|
|
" \n",
|
|
" bbands = get_bbands(self.data)\n",
|
|
" info = get_info(bbands)\n",
|
|
" \n",
|
|
" messages = []\n",
|
|
" if info['last_crossed_above_upper'].any():\n",
|
|
" messages.append(format_signals_info('⬆️', info['last_crossed_above_upper'], info))\n",
|
|
" if info['last_crossed_below_upper'].any():\n",
|
|
" messages.append(format_signals_info('⤵️', info['last_crossed_below_upper'], info))\n",
|
|
" if info['last_crossed_below_lower'].any():\n",
|
|
" messages.append(format_signals_info('⬇️', info['last_crossed_below_lower'], info))\n",
|
|
" if info['last_crossed_above_lower'].any():\n",
|
|
" messages.append(format_signals_info('⤴️', info['last_crossed_above_lower'], info))\n",
|
|
" \n",
|
|
" if len(messages) > 0:\n",
|
|
" message = '\\n'.join([\"{}:\".format(self.update_ts.strftime(DT_FORMAT))] + messages)\n",
|
|
" self.telegram_bot.send_message_to_all(message)\n",
|
|
" if (info['last_crossed_above_upper'] & info['last_change_pos']).any():\n",
|
|
" self.telegram_bot.send_giphy_to_all(\"launch\")\n",
|
|
" if (info['last_crossed_below_lower'] & info['last_change_neg']).any():\n",
|
|
" self.telegram_bot.send_giphy_to_all(\"fall\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"data_updater = MyDataUpdater(data, telegram_bot)\n",
|
|
"data_updater.update_every(UPDATE_EVERY)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"telegram_bot.stop()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.9.12"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|