{"id":6378,"date":"2025-06-04T05:26:12","date_gmt":"2025-06-04T05:26:12","guid":{"rendered":"https:\/\/eodhd.com\/financial-academy\/?p=6378"},"modified":"2025-10-14T14:48:06","modified_gmt":"2025-10-14T14:48:06","slug":"add-multiple-strategies-to-telegram-trading-bot","status":"publish","type":"post","link":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot","title":{"rendered":"Add Multiple Strategies to Telegram Trading Bot"},"content":{"rendered":"\n<p class=\"has-text-align-center\"><a class=\"maxbutton-1 maxbutton maxbutton-subscribe-to-api external-css btn\" href=\"https:\/\/eodhd.com\/register\"><span class='mb-text'>Register &amp; Get Data<\/span><\/a><\/p>\n\n\n\n\n\n\n<p>When I first architected and developed the Telegram trading bot, I approached it as a practical prototype: fetch OHLC data, apply a simple moving average (SMA) crossover, and send a concise \u201cBuy\u201d or \u201cSell\u201d notifications via Telegram. At that stage, rapid iteration and an MVP was my priority. However, as I began integrating additional indicators\u2014RSI, MACD, Bollinger Bands, and so on\u2014the code started to exhibit signs of strain. Each new strategy required duplicating portions of signal\u2010generation logic, renaming rolling averages, and ensuring consistency across multiple modules. Although the initial implementation was functional, it soon became evident that a cleaner, more maintainable architecture was essential. In short, I needed to elevate the bot from a proof\u2010of\u2010concept to something that adhered to established development practices.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-embracing-solid-principles-and-pep-8\">Embracing SOLID Principles and PEP 8<\/h2>\n\n\n\n<p>Rather than simply patching over incremental complexity, I decided on a concerted refactor guided by two pillars: PEP8 conventions and the SOLID design principles. My objectives were:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Single Responsibility<\/strong>: Ensure each module or class handles one clear aspect of functionality\u2014no more tangled responsibilities in a single file.<br><\/li>\n\n\n\n<li><strong>Open\/Closed<\/strong>: Allow the code to be extended with new strategies without modifying existing, well\u2010tested logic.<br><\/li>\n\n\n\n<li><strong>Liskov Substitution<\/strong>: Guarantee that any strategy implementation could be swapped in for another without altering downstream behaviour.<br><\/li>\n\n\n\n<li><strong>Interface Segregation<\/strong>: Define minimal, specific interfaces that reflect an individual component\u2019s needs rather than a monolithic interface that tries to do everything.<br><\/li>\n\n\n\n<li><strong>Dependency Inversion<\/strong>: Depend on abstractions (strategy interfaces), not concrete implementations (specific moving averages).<\/li>\n<\/ul>\n\n\n\n<p>Complementing these principles, I committed to consistent naming and formatting\u2014snake_case for functions and variables, PascalCase for classes, clear docstrings, and proper line lengths. This ensured that every contributor (including my future self) could navigate the codebase without wrestling with idiosyncratic style choices.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-defining-a-clear-strategy-interface-and-factory\">Defining a Clear Strategy Interface and Factory<\/h2>\n\n\n\n<p>The first step was to isolate all indicator logic into a single module, strategy.py. At its core lies an abstract base class:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">from abc import ABC, abstractmethod\nimport pandas as pd\n\nclass Strategy(ABC):\n    @abstractmethod\n    def generate_signals(self, data: pd.DataFrame) -&gt; pd.DataFrame:<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>By specifying exactly one method, I guarantee that any subclass\u2014whether it computes an SMA crossover, an RSI threshold, or something more exotic\u2014will honour the same contract. That means the remainder of the system can treat them interchangeably.<\/p>\n\n\n\n<p>Using this interface, I implemented five concrete strategies, each in its own well\u2010named class:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>SMA Crossover<\/strong> (SmaCrossoverStrategy)<br><\/li>\n\n\n\n<li><strong>EMA Crossover<\/strong> (EmaCrossoverStrategy)<br><\/li>\n\n\n\n<li><strong>RSI\u2010Based<\/strong> (RsiStrategy)<br><\/li>\n\n\n\n<li><strong>MACD<\/strong> (MacdStrategy)<br><\/li>\n\n\n\n<li><strong>Bollinger Bands Breakout<\/strong> (BollingerBandsStrategy)<\/li>\n<\/ol>\n\n\n\n<p>Rather than re\u2010implementing every rolling window or exponential average by hand, and risk subtle off\u2010by\u2010one errors or NaN handling issues, I leveraged the well\u2010maintained <strong>pandas_ta<\/strong> library. For example, the EMA crossover class now looks like this:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">import pandas_ta as ta\n\nclass EmaCrossoverStrategy(Strategy):\n    def __init__(self, short_span: int = 12, long_span: int = 26):\n        self.short_span = short_span\n        self.long_span = long_span\n\n    def generate_signals(self, data: pd.DataFrame) -&gt; pd.DataFrame:\n        df = data.copy()\n        df[f'ema_{self.short_span}'] = ta.ema(df['close'], length=self.short_span)\n        df[f'ema_{self.long_span}'] = ta.ema(df['close'], length=self.long_span)\n        df['signal'] = 0\n        df.loc[df[f'ema_{self.short_span}'] &gt; df[f'ema_{self.long_span}'], 'signal'] = 1\n        df.loc[df[f'ema_{self.short_span}'] &lt; df[f'ema_{self.long_span}'], 'signal'] = -1\n        df['position'] = df['signal'].diff().fillna(0)\n        return df<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>A single line of pandas_ta replaces what used to be a multi\u2010line rolling\u2010average implementation, greatly reducing maintenance overhead. I applied the same pattern to RSI, MACD and Bollinger Bands, keeping each class limited to its own calculation and threshold logic.<\/p>\n\n\n\n<p>To manage instantiation by name, I introduced a simple StrategyFactory:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">class StrategyFactory:\n    _strategies = {\n        'sma': SmaCrossoverStrategy,\n        'ema': EmaCrossoverStrategy,\n        'rsi': RsiStrategy,\n        'macd': MacdStrategy,\n        'bbands': BollingerBandsStrategy,\n    }\n\n    @classmethod\n    def list_strategies(cls) -&gt; list[str]:\n        return list(cls._strategies.keys())\n\n    @classmethod\n    def create_strategy(cls, name: str, **kwargs) -&gt; Strategy:\n        name = name.lower()\n        if name not in cls._strategies:\n            raise ValueError(f'Unknown strategy: {name}')\n        return cls._strategies[name](**kwargs)<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>With this factory, adding a sixth strategy later is simply a matter of implementing a new subclass and registering it in _strategies. The rest of the code &#8211; handlers, notifier, job scheduling remains untouched, fulfilling the <strong>Open\/Closed<\/strong> principle.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-structuring-telegram-handlers-for-clarity\">Structuring Telegram Handlers for Clarity<\/h2>\n\n\n\n<p>Next, I focused on handlers.py, ensuring each function has a single, obvious responsibility:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>start<\/strong>: issue a welcome message upon \/start.<br><\/li>\n\n\n\n<li><strong>set_symbol<\/strong>: parse \/set_symbol &lt;SYMBOL&gt;, store it in a module\u2010level variable (uppercase by convention), and confirm.<br><\/li>\n\n\n\n<li><strong>set_interval<\/strong>: handle \/set_interval &lt;INTERVAL&gt; similarly.<br><\/li>\n\n\n\n<li><strong>get_price<\/strong>: fetch only the most recent quote when you need a quick check.<br><\/li>\n\n\n\n<li><strong>list_strategies<\/strong>: display all registered strategy keys.<br><\/li>\n\n\n\n<li><strong>set_strategy<\/strong>: allow strategy selection\u2014detailed below.<br><\/li>\n\n\n\n<li><strong>current_strategy<\/strong>: reply with which strategy is active.<br><\/li>\n\n\n\n<li><strong>analyse_market<\/strong>: the scheduled job that fetches OHLC data, runs the active strategy, and sends \u201cBuy\u201d or \u201cSell\u201d as needed.<\/li>\n<\/ul>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">def get_price(update: Update, context: CallbackContext) -&gt; None:\n    fetcher = DataFetcher(symbol, interval)\n    price = fetcher.fetch_price()\n    if price is not None:\n        update.message.reply_text(f'Current price of {symbol}: {price}')\n    else:\n        update.message.reply_text('Failed to fetch data.')<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>Notice that none of these handler functions attempt to compute signals or manage strategy internals. They simply orchestrate inputs and outputs, deferring the core logic to the StrategyFactory and notifier.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-enhancing-strategy-selection-with-inline-keyboards\">Enhancing Strategy Selection with Inline Keyboards<\/h2>\n\n\n\n<p>Originally, choosing a strategy required an exact command, for example:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">\/set_strategy ema<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>That step was functional, but I wanted a more intuitive developer experience\u2014especially if I didn\u2019t recall every available key by heart. Therefore, I extended the \/set_strategy handler so that if no argument is provided, the bot sends an inline keyboard:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">from telegram import InlineKeyboardButton, InlineKeyboardMarkup\n\ndef set_strategy(update: Update, context: CallbackContext) -&gt; None:\n    global current_strategy_name, strategy_params\n    names = StrategyFactory.list_strategies()\n\n    if context.args:\n        name = context.args[0].lower()\n        if name in names:\n            current_strategy_name = name\n            strategy_params = {}\n            update.message.reply_text(f'Strategy set to \"{name}\".')\n        else:\n            update.message.reply_text(f'Unknown strategy \"{name}\".')\n        return\n\n    buttons = [\n        InlineKeyboardButton(text=nm.upper(), callback_data=f'setstrat:{nm}')\n        for nm in names\n    ]\n    # Arrange buttons in two columns\n    rows = [buttons[i:i+2] for i in range(0, len(buttons), 2)]\n    reply_markup = InlineKeyboardMarkup(rows)\n    update.message.reply_text('Please choose a strategy:', reply_markup=reply_markup)<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>That command now displays a neatly laid\u2010out grid of buttons\u2014\u201cSMA\u201d, \u201cEMA\u201d, \u201cRSI\u201d, and so on. When the user taps a button, Telegram sends a callback query with data set to something like setstrat:ema. I handle these callbacks with:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">def strategy_button(update: Update, context: CallbackContext) -&gt; None:\n    global current_strategy_name, strategy_params\n\n    query = update.callback_query\n    query.answer()  # Dismisses the \u201cLoading...\u201d spinner\n\n    data = query.data or ''\n    _, chosen = data.split(':', 1)\n    if chosen in StrategyFactory.list_strategies():\n        current_strategy_name = chosen\n        strategy_params = {}\n        query.edit_message_text(f'Strategy set to \"{chosen}\".')\n    else:\n        query.edit_message_text(f'Unknown strategy \"{chosen}\".')<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>Crucially, query.answer() must be called to immediately clear the loading indicator. Otherwise, users might tap a button only to see an indefinite spinner. By editing the original message with a confirmation, I provide immediate feedback that the selection was successful. In main.py, the order of handler registration is:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">dp.add_handler(CommandHandler(\"set_strategy\", set_strategy))\ndp.add_handler(CallbackQueryHandler(strategy_button, pattern=r\"^setstrat:\"))<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>Ensuring the CallbackQueryHandler is registered after the command handlers guarantees that button presses will be caught. With this setup, the user can simply type:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">\/set_strategy<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>and tap \u201cEMA\u201d, rather than remember the exact \u201cema\u201d key.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-improving-notifications-handling-blocked-bot-scenarios\">Improving Notifications: Handling Blocked\u2010Bot Scenarios<\/h2>\n\n\n\n<p>In production, one of the most common pitfalls is uncaught exceptions due to a user blocking the bot. At one point, my scheduler would crash with:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">telegram.error.Unauthorized: Forbidden: bot was blocked by the user<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>Because I sent messages directly in the job callback, any blocked\u2010bot error terminated the entire process. To harden against this, I refactored all message\u2010sending logic into a dedicated TelegramNotifier:<\/p>\n\n\n\n            <div class=\"code__wrapper\">\n                <div class=\"code__content\">\n                    \n<pre class=\"wp-block-code\"><code class=\"\">import logging\nfrom telegram.error import Unauthorized, BadRequest\nfrom telegram import Bot\n\nclass TelegramNotifier:\n    def __init__(self, bot_token: str, default_chat_id: int = None):\n        self._bot = Bot(bot_token)\n        self._default_chat_id = default_chat_id\n\n    def send_message(self, text: str, chat_id: int = None) -&gt; None:\n        target = chat_id or self._default_chat_id\n        if not target:\n            logging.error(\"No chat_id provided; cannot send message.\")\n            return\n\n        try:\n            self._bot.send_message(chat_id=target, text=text)\n        except Unauthorized:\n            logging.warning(f\"Failed to send to {target}: bot was blocked.\")\n            # TODO: remove this chat_id from scheduled jobs or notify an admin\n        except BadRequest as e:\n            logging.error(f\"BadRequest sending to {target}: {e}\")\n        except Exception as e:\n            logging.exception(f\"Unexpected error sending to {target}: {e}\")<\/code><\/pre>\n\n                <\/div>\n                <div class=\"code__btns\">\n                    <button class=\"code__copy\" class=\"copy\" title=\"Copy url\">\n                        <svg class=\"code__copy__icon\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                            <use xlink:href=\"\/img\/icons\/copy.svg#copy\"><\/use>\n                        <\/svg>\n                        <img decoding=\"async\" class=\"code__copy__approve\" alt=\"\" src=\"\/img\/approve_ico.svg\" loading=\"eager\">\n                    <\/button>\n                <\/div>\n            <\/div>\n        \n\n\n<p>Now, even if a user blocks the bot, the Unauthorized exception is caught and logged, and the scheduler continues without interruption. If I maintain a list of active chat IDs or schedule jobs per chat, I can remove blocked chats from that list at this point.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-bringing-it-all-together\">Bringing It All Together<\/h2>\n\n\n\n<p>With these refinements, the bot\u2019s architecture is neatly partitioned:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>strategy.py<\/strong><br>\n<ul class=\"wp-block-list\">\n<li>Defines a single Strategy interface.<br><\/li>\n\n\n\n<li>Implements five concrete strategies (SMA, EMA, RSI, MACD, Bollinger Bands), each using pandas_ta.<br><\/li>\n\n\n\n<li>Provides a StrategyFactory to list and instantiate strategies by name.<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>handlers.py<\/strong><br>\n<ul class=\"wp-block-list\">\n<li>Houses Telegram command handlers (\/start, \/set_symbol, \/set_interval, \/get_price, \/list_strategies, \/set_strategy, \/current_strategy).<br><\/li>\n\n\n\n<li>Includes inline\u2010keyboard logic for \/set_strategy when no argument is provided.<br><\/li>\n\n\n\n<li>Contains the scheduled job callback analyse_market, which fetches OHLC data, invokes the active strategy via StrategyFactory, and uses TelegramNotifier to dispatch buy\/sell alerts.<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>main.py<\/strong><br>\n<ul class=\"wp-block-list\">\n<li>Registers all CommandHandler instances.<br><\/li>\n\n\n\n<li>Registers a CallbackQueryHandler for inline buttons with pattern=r&#8221;^setstrat:&#8221;.<br><\/li>\n\n\n\n<li>Schedules analyse_market on a repeating interval (e.g., 60 seconds).<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p>By adhering to the <strong>Single Responsibility<\/strong> principle, each component focuses on one aspect E.g., strategy logic, Telegram interaction, or scheduling, not muddling concerns together. The <strong>Open\/Closed<\/strong> principle is satisfied because adding a new strategy never forces me to alter existing handler or notifier code; I simply implement a new subclass of Strategy and register it in _strategies. Types remain substitutable, honouring <strong>Liskov Substitution<\/strong>, and the code depends on abstractions rather than concrete classes, in line with <strong>Dependency Inversion<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-eodhdalerts-in-action\">EODHDAlerts in Action<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"975\" height=\"1024\" src=\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1-975x1024.png\" alt=\"\" class=\"wp-image-6416\" srcset=\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1-975x1024.png 975w, https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1-286x300.png 286w, https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1-768x807.png 768w, https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1-1462x1536.png 1462w, https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1-57x60.png 57w, https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1-143x150.png 143w, https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/Screenshot-2025-06-06-at-18.18.34-1.png 1607w\" sizes=\"auto, (max-width: 975px) 100vw, 975px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-reflections-and-next-steps\">Reflections and Next Steps<\/h2>\n\n\n\n<p>Working through this refactor reinforced how valuable a clear architecture can be, even for a relatively compact project. By isolating strategy logic into an interface and factory, I can now introduce complex, data\u2010driven models or custom indicators without touching the Telegram\u2010specific code. The inline keyboards in \/set_strategy create a smoother experience, no more guesswork over exact strategy names and wrapping send_message in robust error handling ensures that blocked\u2010bot scenarios don\u2019t bring the entire scheduler down.<\/p>\n\n\n\n<p>Moving forward, a few enhancements are on my radar:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Per\u2010chat persistence<\/strong>: store each chat\u2019s chosen symbol, interval and strategy in a lightweight database or JSON file so that settings persist across bot restarts.<br><\/li>\n\n\n\n<li><strong>Parameterised commands<\/strong>: extend \/set_strategy to accept parameters (for example, \/set_strategy sma 10 30 to choose custom window lengths).<br><\/li>\n\n\n\n<li><strong>Back\u2010testing integration<\/strong>: reuse the same Strategy classes to run historical simulations on OHLC data and generate performance reports.<br><\/li>\n\n\n\n<li><strong>Additional strategies<\/strong>: including machine\u2010learning classifiers or composite oscillators, simply by subclassing Strategy and registering.<\/li>\n<\/ul>\n\n\n\n<p>Ultimately, this refactor has allowed me to maintain both flexibility and stability. I can deploy new ideas rapidly, while ensuring the codebase remains understandable and maintainable. For anyone building a Telegram trading assistant or any system that needs to swap algorithms on the fly, this approach demonstrates how even a modest project benefits from well\u2010established design principles.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When I first architected and developed the Telegram trading bot, I approached it as a practical prototype: fetch OHLC data, apply a simple moving average (SMA) crossover, and send a concise \u201cBuy\u201d or \u201cSell\u201d notifications via Telegram. At that stage, rapid iteration and an MVP was my priority. However, as I began integrating additional indicators\u2014RSI, [&hellip;]<\/p>\n","protected":false},"author":8,"featured_media":6380,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[64],"tags":[],"coding-language":[30],"ready-to-go-solution":[56],"qualification":[31],"financial-apis-category":[36],"financial-apis-manuals":[81],"class_list":["post-6378","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-building-stocks-apps-examples","coding-language-python","ready-to-go-solution-eodhd-python-financial-library","qualification-experienced","financial-apis-category-stock-market-prices","financial-apis-manuals-stock-market-screener","has_thumb"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v21.9 (Yoast SEO v26.7) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Add Multiple Strategies to Telegram Trading Bot | EODHD APIs Academy<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Add Multiple Strategies to Telegram Trading Bot\" \/>\n<meta property=\"og:description\" content=\"When I first architected and developed the Telegram trading bot, I approached it as a practical prototype: fetch OHLC data, apply a simple moving average (SMA) crossover, and send a concise \u201cBuy\u201d or \u201cSell\u201d notifications via Telegram. At that stage, rapid iteration and an MVP was my priority. However, as I began integrating additional indicators\u2014RSI, [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot\" \/>\n<meta property=\"og:site_name\" content=\"Financial Academy\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/eodhistoricaldata\" \/>\n<meta property=\"article:published_time\" content=\"2025-06-04T05:26:12+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-10-14T14:48:06+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1464\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Michael Whittle\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@EOD_data\" \/>\n<meta name=\"twitter:site\" content=\"@EOD_data\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Michael Whittle\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#article\",\"isPartOf\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot\"},\"author\":{\"name\":\"Michael Whittle\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#\/schema\/person\/50784c270b6267df5969514d80d510ad\"},\"headline\":\"Add Multiple Strategies to Telegram Trading Bot\",\"datePublished\":\"2025-06-04T05:26:12+00:00\",\"dateModified\":\"2025-10-14T14:48:06+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot\"},\"wordCount\":1414,\"publisher\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#organization\"},\"image\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage\"},\"thumbnailUrl\":\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg\",\"articleSection\":[\"Building Stocks Apps Examples\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot\",\"url\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot\",\"name\":\"Add Multiple Strategies to Telegram Trading Bot | EODHD APIs Academy\",\"isPartOf\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage\"},\"image\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage\"},\"thumbnailUrl\":\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg\",\"datePublished\":\"2025-06-04T05:26:12+00:00\",\"dateModified\":\"2025-10-14T14:48:06+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage\",\"url\":\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg\",\"contentUrl\":\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg\",\"width\":2560,\"height\":1464,\"caption\":\"Abstract Background chart stock market. Generative AI\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/eodhd.com\/financial-academy\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Add Multiple Strategies to Telegram Trading Bot\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#website\",\"url\":\"https:\/\/eodhd.com\/financial-academy\/\",\"name\":\"Financial APIs Academy | EODHD\",\"description\":\"Financial Stock Market Academy\",\"publisher\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/eodhd.com\/financial-academy\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#organization\",\"name\":\"EODHD (EOD Historical Data)\",\"url\":\"https:\/\/eodhd.com\/financial-academy\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2023\/12\/EODHD-Logo.png\",\"contentUrl\":\"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2023\/12\/EODHD-Logo.png\",\"width\":159,\"height\":82,\"caption\":\"EODHD (EOD Historical Data)\"},\"image\":{\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/eodhistoricaldata\",\"https:\/\/x.com\/EOD_data\",\"https:\/\/www.reddit.com\/r\/EODHistoricalData\/\",\"https:\/\/eod-historical-data.medium.com\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#\/schema\/person\/50784c270b6267df5969514d80d510ad\",\"name\":\"Michael Whittle\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/eodhd.com\/financial-academy\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5076af85c7ee0445454257247cad4970ae8cf5d7d4940d2b32c521f51c0a0f5a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5076af85c7ee0445454257247cad4970ae8cf5d7d4940d2b32c521f51c0a0f5a?s=96&d=mm&r=g\",\"caption\":\"Michael Whittle\"},\"description\":\"Solution architect, developer, and analyst with over 20+ years experience (TOP author on Medium).\",\"url\":\"https:\/\/eodhd.com\/financial-academy\/author\/michaelwhittle\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Add Multiple Strategies to Telegram Trading Bot | EODHD APIs Academy","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot","og_locale":"en_US","og_type":"article","og_title":"Add Multiple Strategies to Telegram Trading Bot","og_description":"When I first architected and developed the Telegram trading bot, I approached it as a practical prototype: fetch OHLC data, apply a simple moving average (SMA) crossover, and send a concise \u201cBuy\u201d or \u201cSell\u201d notifications via Telegram. At that stage, rapid iteration and an MVP was my priority. However, as I began integrating additional indicators\u2014RSI, [&hellip;]","og_url":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot","og_site_name":"Financial Academy","article_publisher":"https:\/\/www.facebook.com\/eodhistoricaldata","article_published_time":"2025-06-04T05:26:12+00:00","article_modified_time":"2025-10-14T14:48:06+00:00","og_image":[{"width":2560,"height":1464,"url":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg","type":"image\/jpeg"}],"author":"Michael Whittle","twitter_card":"summary_large_image","twitter_creator":"@EOD_data","twitter_site":"@EOD_data","twitter_misc":{"Written by":"Michael Whittle","Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#article","isPartOf":{"@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot"},"author":{"name":"Michael Whittle","@id":"https:\/\/eodhd.com\/financial-academy\/#\/schema\/person\/50784c270b6267df5969514d80d510ad"},"headline":"Add Multiple Strategies to Telegram Trading Bot","datePublished":"2025-06-04T05:26:12+00:00","dateModified":"2025-10-14T14:48:06+00:00","mainEntityOfPage":{"@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot"},"wordCount":1414,"publisher":{"@id":"https:\/\/eodhd.com\/financial-academy\/#organization"},"image":{"@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage"},"thumbnailUrl":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg","articleSection":["Building Stocks Apps Examples"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot","url":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot","name":"Add Multiple Strategies to Telegram Trading Bot | EODHD APIs Academy","isPartOf":{"@id":"https:\/\/eodhd.com\/financial-academy\/#website"},"primaryImageOfPage":{"@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage"},"image":{"@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage"},"thumbnailUrl":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg","datePublished":"2025-06-04T05:26:12+00:00","dateModified":"2025-10-14T14:48:06+00:00","breadcrumb":{"@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#primaryimage","url":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg","contentUrl":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg","width":2560,"height":1464,"caption":"Abstract Background chart stock market. Generative AI"},{"@type":"BreadcrumbList","@id":"https:\/\/eodhd.com\/financial-academy\/building-stocks-apps-examples\/add-multiple-strategies-to-telegram-trading-bot#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/eodhd.com\/financial-academy\/"},{"@type":"ListItem","position":2,"name":"Add Multiple Strategies to Telegram Trading Bot"}]},{"@type":"WebSite","@id":"https:\/\/eodhd.com\/financial-academy\/#website","url":"https:\/\/eodhd.com\/financial-academy\/","name":"Financial APIs Academy | EODHD","description":"Financial Stock Market Academy","publisher":{"@id":"https:\/\/eodhd.com\/financial-academy\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/eodhd.com\/financial-academy\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/eodhd.com\/financial-academy\/#organization","name":"EODHD (EOD Historical Data)","url":"https:\/\/eodhd.com\/financial-academy\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/eodhd.com\/financial-academy\/#\/schema\/logo\/image\/","url":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2023\/12\/EODHD-Logo.png","contentUrl":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2023\/12\/EODHD-Logo.png","width":159,"height":82,"caption":"EODHD (EOD Historical Data)"},"image":{"@id":"https:\/\/eodhd.com\/financial-academy\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/eodhistoricaldata","https:\/\/x.com\/EOD_data","https:\/\/www.reddit.com\/r\/EODHistoricalData\/","https:\/\/eod-historical-data.medium.com\/"]},{"@type":"Person","@id":"https:\/\/eodhd.com\/financial-academy\/#\/schema\/person\/50784c270b6267df5969514d80d510ad","name":"Michael Whittle","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/eodhd.com\/financial-academy\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5076af85c7ee0445454257247cad4970ae8cf5d7d4940d2b32c521f51c0a0f5a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5076af85c7ee0445454257247cad4970ae8cf5d7d4940d2b32c521f51c0a0f5a?s=96&d=mm&r=g","caption":"Michael Whittle"},"description":"Solution architect, developer, and analyst with over 20+ years experience (TOP author on Medium).","url":"https:\/\/eodhd.com\/financial-academy\/author\/michaelwhittle"}]}},"jetpack_featured_media_url":"https:\/\/eodhd.com\/financial-academy\/wp-content\/uploads\/2025\/06\/AdobeStock_576489335-scaled.jpeg","jetpack_shortlink":"https:\/\/wp.me\/pdOdVT-1ES","jetpack_sharing_enabled":true,"acf":[],"_links":{"self":[{"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/posts\/6378","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/comments?post=6378"}],"version-history":[{"count":9,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/posts\/6378\/revisions"}],"predecessor-version":[{"id":6568,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/posts\/6378\/revisions\/6568"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/media\/6380"}],"wp:attachment":[{"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/media?parent=6378"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/categories?post=6378"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/tags?post=6378"},{"taxonomy":"coding-language","embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/coding-language?post=6378"},{"taxonomy":"ready-to-go-solution","embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/ready-to-go-solution?post=6378"},{"taxonomy":"qualification","embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/qualification?post=6378"},{"taxonomy":"financial-apis-category","embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/financial-apis-category?post=6378"},{"taxonomy":"financial-apis-manuals","embeddable":true,"href":"https:\/\/eodhd.com\/financial-academy\/wp-json\/wp\/v2\/financial-apis-manuals?post=6378"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}