├── .gitignore ├── requirements.txt ├── __pycache__ ├── reverse_dca.cpython-310.pyc ├── reverse_dca.cpython-312.pyc ├── telegram_utils.cpython-310.pyc └── telegram_utils.cpython-312.pyc ├── run.bat ├── run.sh ├── telegram_utils.py ├── README.md ├── main.py └── reverse_dca.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | logs 3 | .env 4 | test.py 5 | vvv.py 6 | settings.ini 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-binance==1.0.22 2 | pandas==2.2.3 3 | numpy==2.1.3 4 | requests==2.32.3 -------------------------------------------------------------------------------- /__pycache__/reverse_dca.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trust412/reverse_dca_bot/HEAD/__pycache__/reverse_dca.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/reverse_dca.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trust412/reverse_dca_bot/HEAD/__pycache__/reverse_dca.cpython-312.pyc -------------------------------------------------------------------------------- /__pycache__/telegram_utils.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trust412/reverse_dca_bot/HEAD/__pycache__/telegram_utils.cpython-310.pyc -------------------------------------------------------------------------------- /__pycache__/telegram_utils.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trust412/reverse_dca_bot/HEAD/__pycache__/telegram_utils.cpython-312.pyc -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | set CONDAPATH=%CONDAPATH% 2 | set ENVNAME=rdca 3 | 4 | if %ENVNAME%==base (set ENVPATH=%CONDAPATH%) else (set ENVPATH=%CONDAPATH%\envs\%ENVNAME%) 5 | 6 | call %CONDAPATH%\Scripts\activate.bat %ENVPATH% 7 | 8 | cd /d %~dp0 9 | python main.py 10 | 11 | @pause 12 | 13 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CONDAPATH="$CONDAPATH" 4 | ENVNAME="rdca" 5 | 6 | if [ "$ENVNAME" == "base" ]; then 7 | ENVPATH="$CONDAPATH" 8 | else 9 | ENVPATH="$CONDAPATH/envs/$ENVNAME" 10 | fi 11 | 12 | source "$CONDAPATH/Scripts/activate" "$ENVPATH" 13 | 14 | cd "$(dirname "$0")" 15 | python main.py 16 | 17 | read -p "Press Enter to continue..." -------------------------------------------------------------------------------- /telegram_utils.py: -------------------------------------------------------------------------------- 1 | # from telegram import Bot 2 | import requests 3 | import os 4 | import datetime 5 | import logging 6 | import sys 7 | 8 | 9 | class TelegramBot: 10 | def __init__(self, api_token, chat_id_1, chat_id_2): 11 | self.api_token = api_token 12 | self.chat_id_1 = chat_id_1 13 | self.chat_id_2 = chat_id_2 14 | self.logs_dir_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs") 15 | self.file_name = "logs_" + datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".txt" 16 | if not os.path.exists(self.logs_dir_path): 17 | os.makedirs(self.logs_dir_path) 18 | 19 | # Set up logging configuration 20 | logging.basicConfig( 21 | level=logging.INFO, 22 | format='%(asctime)s || %(message)s', 23 | handlers=[ 24 | logging.FileHandler(os.path.join(self.logs_dir_path, self.file_name)), 25 | logging.StreamHandler() 26 | ] 27 | ) 28 | 29 | self.logger = logging.getLogger(__name__) 30 | # Set the unhandled exception hook 31 | sys.excepthook = self.handle_exception 32 | 33 | def send_message(self, message, print_terminal=True): 34 | api_url = f'https://api.telegram.org/bot{self.api_token}/sendMessage' 35 | 36 | try: 37 | if print_terminal: 38 | self.logger.info(message) 39 | 40 | response_1 = requests.post(api_url, json={'chat_id': self.chat_id_1, 'text': message}) 41 | response_2 = requests.post(api_url, json={'chat_id': self.chat_id_2, 'text': message}) 42 | # # print(response.text) 43 | # print(response_1.status_code) 44 | # print(response_2.status_code) 45 | except Exception as e: 46 | self.logger.error("Exception! Telegram Bot.", e) 47 | 48 | def handle_exception(self, exc_type, exc_value, exc_traceback): 49 | if issubclass(exc_type, KeyboardInterrupt): 50 | self.logger.warning("Keyboard interrupt detected, stopping the application.") 51 | sys.exit(1) 52 | else: 53 | self.logger.error("Unhandled exception", exc_info=(exc_type, exc_value, exc_traceback)) 54 | sys.exit(1) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Why? 2 | This is basically a reverse DCA strategy where DCA adds to a losing trade until the stop loss is reached. So regular DCA mostly wins smaller profits and lose a large bet which will eventually wipe out all the profits taken beforehand. Inversely we add to a winning trade until a take profit is reached so that smaller losses are a general flow of this strategy but when take profit is reached we cover the losses and take a larger profit. That's it. 3 | 4 | ### Logic: 5 | 6 | #### Coinfinder 7 | A coinfinder function goes over all listed coins on binance and filters them first before opening a position. 8 | 9 | #### Baseline: 10 | Price needs to be above for both 4H and 1D HMA (Hull Moving Average) for coinfinder to consider opening positions. 11 | 12 | #### Entry: 13 | If baseline returns true and if price crosses up Hull Moving average 1H timeframe for LONG, crossing down for SHORT --> the position is opened. 14 | Positions are opened until Max_positions variable (see below under settings) fills its max limit. This limit prevents opening positions indefinitely. If a position exits for a stop loss or take profit then coinfinder will look for new opportunities. Keep in mind only the top volume coins are filtered. 15 | 16 | #### Add positions: 17 | Positions are added by multiplying the previous position by volume_scale variable (see below) which is set at to 1.5x at every 2% gain and adds $20,30,45,67.5 respectively, reaching a balance of $162.5. 18 | 19 | #### Stop Loss: 20 | Stop loss works in 2 modes. 21 | 22 | 1) If the first position loses its value to stop_loss_pct variable then the position is stopped with a definite loss of that value. 23 | 2) If the first position + at least "one" increment position is opened, then breakeven_threshold_pct variable holds true. This value controls the distance from the average entry price. The reason why we're not using a simple stop loss for both conditions is because to prevent extra loss if for some reason let's say the position goes up to the 4th incremental order without take profit and crashes back down to the stop loss value, this value will keep the losses lower. 24 | 25 | #### Premature Position Exit: 26 | If any of the 1H, 4H or 1D HMA values ​​are broken, the position is closed immediately and scanning is restarted until max_positions are filled. 27 | 28 | #### Take Profit : 29 | Closing with a profit when it reaches the 9% point. (see take profit variable under settings - set to %9) 30 | 31 | 32 | Below is the setting file with explanations commented. 33 | 34 | ### [settings] 35 | 36 | #ticker = auto ; When the ticker is auto, the coinfinder mode is on. The normal mode is to run it by entering the name of a single coin. Like DOGEUSDT. 37 | ### ticker = auto 38 | 39 | #Max_positions = 3 ; It is activated when the ticker is auto. It is the maximum number of coins we will keep open at the same time. When this number drops, the scan starts again. 40 | ### Max_positions = 3 41 | 42 | #initial_direction = Sell; It indicates whether the bot will work long or short. It's short in this case. 43 | 44 | ### initial_direction = Sell 45 | 46 | #base_order_size = 20 ; The amount of the position opening in USD. ($20 in this case) 47 | ### base_order_size = 20 48 | 49 | #volume_scale = 1.5 ; The value of the multiplier by which we will multiply the previous amount in each position increase. For example. If you enter with 20 dollars, it will open positions as 20, 30, 45 ... etc. 50 | ### volume_scale = 1.5 51 | 52 | #increment_pct = 2 ; The step percent value which we'll increase the position. In this example, a new position is added every 2% gain until the take profit is reached. 53 | ### increment_pct = 2 54 | 55 | #stop_loss_pct = 2 ; This stop loss value is valid after the position is opened for the first entry price only. If the position reverts without making any buy-ins then it closes the position when it reaches this value. 56 | ### stop_loss_pct = 2 57 | 58 | #breakeven_threshold_pct = 0 ; This value activates after the first buy-in order is made. It's generally set as 0, its purpose is to prevent loss by exiting the position at the breakeven point with the slightest fall. If the loss reaches to 0%, it stop losses at 0% loss from the average entry price, not first entry price. 59 | ### breakeven_threshold_pct = 0 60 | 61 | #take_profit_pct = 9 ; The take profit target percent. Valid from the first entered price. 62 | ### take_profit_pct = 9 63 | 64 | #Moving Average type and periods. HMA1 position opening trigger, others baseline filter. 0 = disabled 65 | ### sma_period = 0 66 | ### hma1_period = 40 67 | ### hma2_period = 40 68 | ### hma3_period = 40 69 | 70 | #Moving Average time frames. 0 = disabled 71 | ### sma_timeframe = 0 72 | ### hma1_timeframe = 1H 73 | ### hma2_timeframe = 4H 74 | ### hma3_timeframe = 1D 75 | 76 | #Telegram and binance api key and secret points. 77 | ### [telegram] 78 | api_token = xx 79 | channel_chat_id_1 = yy 80 | channel_chat_id_2 = zz 81 | ### [binance] 82 | api_key = yy 83 | api_secret = zz 84 | 85 | ## How to Run (via miniconda/anaconda) 86 | * It is preferrable to use miniconda and create a new python 3.10 environment in it with the name of rdca 87 | * Install miniconda in the system. 88 | * Open the miniconda terminal and create a new Python 3.10 environment in it with the name of rdca using the following command: 89 | * conda create –name rdca python=3.10 90 | * Activate the newly created environment using the following command: 91 | * conda activate rdca 92 | * Install the required Python packages mentioned in requirements.txt in the root directory using the following command: 93 | * pip install -r requirements.txt 94 | * Set the following environment variable: 95 | * CONDAPATH: Path of the conda directory, e.g. D:/miniconda 96 | * Set the required settings in the settings.ini file in the root directory 97 | * Run _run.bat_ to start the execution 98 | 99 | ## How to Run (via system-wide python) 100 | * It's good to use python 3.10 environment for this bot. 101 | * Download and install Python version 3.10.10 (Windows or Mac). (When installing Python, make sure to check the All checkbox in the first window of the installation). 102 | * Also, to run your bot safely, download and install Visual Studio Code if you don't already have it installed. 103 | * Please install Python VS code extention in VS code environment. 104 | * Install the required Python packages mentioned in requirements.txt in the root directory using the following command: 105 | * pip install -r requirements.txt 106 | * Set the required settings in the settings.ini file in the root directory 107 | * open the python terminal at the root dir and star the execution via the following command: 108 | * python main.py 109 | * Or Run _run.bat_ to start the execution 110 | 111 | ## How to Run on Ubuntu Server 112 | * To install Python 3.10 on Ubuntu, follow these steps: 113 | * Open the Terminal and run the following command. 114 | 115 | Update Ubuntu Linux Before Installing Python 3.10 116 | ```shell 117 | sudo apt update && sudo apt upgrade 118 | ``` 119 | 120 | Install Python 3.10 on Ubuntu via APT Command 121 | ```shell 122 | sudo apt install python3.10 123 | ``` 124 | 125 | Verifying the Python 3.10 Installation on Ubuntu 126 | ```shell 127 | python3.10 --version 128 | ``` 129 | 130 | Installing Python Pip on Ubuntu via Python 3.10 131 | ```shell 132 | sudo apt install python3-pip 133 | ``` 134 | 135 | Upgrade Pip to the Latest Version on Ubuntu via Python 3.10 136 | ```shell 137 | python3 -m pip install --upgrade pip 138 | ``` 139 | * To install VS Code on Ubuntu, follow these steps: 140 | 141 | * Open the Terminal and run the following command. 142 | 143 | Install VS code using Snap package 144 | ```shell 145 | sudo snap install --classic code 146 | ``` 147 | * Install the required Python packages mentioned in requirements.txt in the root directory using the following command: 148 | 149 | ```shell 150 | pip install -r requirements.txt 151 | ``` 152 | ## Notes 153 | * The logs will be sent to the Telegram channel while the execution is running. 154 | * If the execution stops for any reason, close the running terminal and positions manually and restart the terminal. 155 | ## Contact ME 👋 156 | 157 | - [Telegram](https://t.me/trust4120) 158 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import threading 3 | import requests 4 | import asyncio 5 | import time 6 | import sys 7 | import os 8 | from binance.enums import * 9 | from telegram import Update 10 | from datetime import datetime 11 | from binance.client import Client 12 | from reverse_dca import ReverseDCA 13 | from telegram_utils import TelegramBot 14 | from telegram.ext import Application, CommandHandler, ContextTypes 15 | 16 | # Parsing settigns config file 17 | config = configparser.ConfigParser(inline_comment_prefixes=";") 18 | current_dir = os.path.dirname(os.path.abspath(__file__)) 19 | config_path = os.path.join(current_dir, 'settings.ini') 20 | config.read(config_path) 21 | telegram_api_token = config['telegram']['api_token'] 22 | application = Application.builder().token(telegram_api_token).build() 23 | 24 | is_active = False 25 | 26 | if sys.platform.startswith('win'): 27 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 28 | 29 | def get_top_volume_pairs(api_key, limit=800): 30 | url = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest" 31 | parameters = { 32 | 'start': '1', 33 | 'limit': str(limit), 34 | 'convert': 'USD', 35 | 'sort': 'volume_24h' 36 | } 37 | headers = { 38 | 'Accepts': 'application/json', 39 | 'X-CMC_PRO_API_KEY': api_key, 40 | } 41 | 42 | response = requests.get(url, headers=headers, params=parameters) 43 | 44 | if response.status_code == 200: 45 | data = response.json() 46 | top_pairs = [] 47 | for currency in data['data']: 48 | if not is_active: 49 | break 50 | if (currency['symbol'] != 'USDT') and (currency['symbol'] != 'FDUSD') and (currency['symbol'] != 'vBNB') and (currency['symbol'] != 'LUNA') and (currency['symbol'] != 'WETH') and (currency['symbol'] != 'PEPE') and (currency['symbol'] != 'WBTC'): 51 | pair_name = currency['symbol'] + 'USDT' 52 | volume = currency['quote']['USD']['volume_24h'] 53 | top_pairs.append((pair_name, volume)) 54 | 55 | top_pairs.sort(key=lambda x: x[1], reverse=True) 56 | return top_pairs[:limit] 57 | else: 58 | print(f"Error: {response.status_code} - {response.text}") 59 | return [] 60 | 61 | def set_active_status(status: bool): 62 | global is_active 63 | is_active = status 64 | print(f"set_is_active: {is_active}") 65 | ReverseDCA.set_active_status(status) 66 | 67 | async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): 68 | print("Bot is starting...") 69 | await update.message.reply_text('Bot is starting...') 70 | set_active_status(True) 71 | 72 | async def stop(update: Update, context: ContextTypes.DEFAULT_TYPE): 73 | await update.message.reply_text('Bot is stopping...') 74 | print("Bot is stopped. You can run it again with \"/start\".") 75 | set_active_status(False) 76 | 77 | def main(): 78 | global is_active 79 | while True: 80 | if not is_active: 81 | time.sleep(1) 82 | continue 83 | try: 84 | ticker = config['settings']['ticker'] 85 | max_position = config['settings']['Max_positions'] 86 | initial_direction = config['settings']['initial_direction'] 87 | base_order_size = int(config['settings']['base_order_size']) 88 | volume_scale = float(config['settings']['volume_scale']) 89 | breakeven_threshold_pct = float(config['settings']['breakeven_threshold_pct']) 90 | stop_loss_pct = float(config['settings']['stop_loss_pct']) 91 | increment_pct = float(config['settings']['increment_pct']) 92 | take_profit_pct = float(config['settings']['take_profit_pct']) 93 | sma_period = int(config['settings']['sma_period']) 94 | hma1_period = int(config['settings']['hma1_period']) 95 | hma2_period = int(config['settings']['hma2_period']) 96 | hma3_period = int(config['settings']['hma3_period']) 97 | sma_tf = config['settings']['sma_timeframe'] 98 | hma1_tf = config['settings']['hma1_timeframe'] 99 | hma2_tf = config['settings']['hma2_timeframe'] 100 | hma3_tf = config['settings']['hma3_timeframe'] 101 | 102 | telegram_api_token = config['telegram']['api_token'] 103 | telegram_channel_chat_id_1 = config['telegram']['channel_chat_id_1'] 104 | telegram_channel_chat_id_2 = config['telegram']['channel_chat_id_2'] 105 | telegram_bot = TelegramBot(telegram_api_token, telegram_channel_chat_id_1, telegram_channel_chat_id_2) 106 | 107 | binance_api_key = config['binance']['api_key'] 108 | binance_api_secret = config['binance']['api_secret'] 109 | # binance_client = Client(binance_api_key, binance_api_secret) 110 | # binance_api_key = config['binance']['test_api_key'] 111 | # binance_api_secret = config['binance']['test_api_secret'] 112 | 113 | print(f"api => {binance_api_key} secret=> {binance_api_secret}") 114 | 115 | if(ticker == "auto"): 116 | pairs = [] 117 | nowcount = 0 118 | past_time = datetime.now() 119 | delta_criteria= 3600 120 | while True: 121 | if not is_active: 122 | break 123 | delta = datetime.now()-past_time 124 | print(f'nowcount => {nowcount} delta=> {delta.total_seconds()}' ) 125 | if nowcount == 0 or delta.total_seconds()>delta_criteria: 126 | nowcount = 0 127 | past_time = datetime.now() 128 | coinmarket_api_key = '5e8e7147-6955-433e-892c-e765d5f9ee81' 129 | top_volume_pairs = get_top_volume_pairs(coinmarket_api_key) 130 | while len(pairs)>0: 131 | cur_bot = pairs.pop() 132 | cur_bot.reset() 133 | cur_bot.close_position() 134 | print(f'initial pairs => {pairs}') 135 | pairCount = 0 136 | for pair in top_volume_pairs: 137 | if not is_active: 138 | break 139 | ticker = pair[0] 140 | setting_message = ( 141 | f"Ticker = {ticker}\n" 142 | f"Initial Direction = {initial_direction}\n" 143 | f"Base Order Size = {base_order_size}\n" 144 | f"Volume Scale = {volume_scale}\n" 145 | f"Breakeven Percent = {breakeven_threshold_pct}\n" 146 | f"Stop Loss Percent = {stop_loss_pct}\n" 147 | f"Increment Percent = {increment_pct}\n" 148 | f"Take Profit Percent = {take_profit_pct}\n" 149 | f"SMA = {sma_period}, {sma_tf}\n" 150 | f"HMA 1 = {hma1_period}, {hma1_tf}\n" 151 | f"HMA 2 = {hma2_period}, {hma2_tf}\n" 152 | f"HMA 3 = {hma3_period}, {hma3_tf}" 153 | ) 154 | 155 | binance_client = Client(binance_api_key, binance_api_secret) 156 | obj_reverse_dca = ReverseDCA(binance_client, telegram_bot, ticker, 157 | initial_direction, base_order_size, 158 | volume_scale, breakeven_threshold_pct, 159 | stop_loss_pct, increment_pct, take_profit_pct, 160 | setting_message, sma_period, sma_tf, hma1_period, 161 | hma2_period, hma3_period, hma1_tf, hma2_tf, hma3_tf) 162 | # continue 163 | try: 164 | isOk = obj_reverse_dca.isAvailable() 165 | print(f"-----------------------------------\n{ticker} => IsAvailable => {isOk}") 166 | if isOk == False: 167 | print(f"count => {pairCount}") 168 | continue 169 | except Exception as e: 170 | print(f'Exception => {e}') 171 | continue 172 | try : 173 | pairCount = pairCount + 1 174 | pairs.append(obj_reverse_dca) 175 | print(f"count => {pairCount}") 176 | if pairCount >= int(max_position) : break 177 | except KeyboardInterrupt: 178 | telegram_bot.logger.warning("Keyboard interrupt detected, stopping the bot.") 179 | print("\nProgram interrupted by user.") 180 | print(f'after pairs => {pairs}') 181 | for ticker in pairs: 182 | if not is_active: 183 | break 184 | if nowcount % 3600 == 0: 185 | telegram_bot.send_message(ticker.setting_message) 186 | ticker.run() 187 | nowcount = nowcount + 1 188 | 189 | time.sleep(1) 190 | else : 191 | setting_message = ( 192 | f"Ticker = {ticker}\n" 193 | f"Initial Direction = {initial_direction}\n" 194 | f"Base Order Size = {base_order_size}\n" 195 | f"Volume Scale = {volume_scale}\n" 196 | f"Breakeven Percent = {breakeven_threshold_pct}\n" 197 | f"Stop Loss Percent = {stop_loss_pct}\n" 198 | f"Increment Percent = {increment_pct}\n" 199 | f"Take Profit Percent = {take_profit_pct}\n" 200 | f"SMA = {sma_period}, {sma_tf}\n" 201 | f"HMA 1 = {hma1_period}, {hma1_tf}\n" 202 | f"HMA 2 = {hma2_period}, {hma2_tf}\n" 203 | f"HMA 3 = {hma3_period}, {hma3_tf}" 204 | ) 205 | binance_client = Client(binance_api_key, binance_api_secret) 206 | 207 | # initializing and running the strategy 208 | obj_reverse_dca = ReverseDCA(binance_client, telegram_bot, ticker, 209 | initial_direction, base_order_size, 210 | volume_scale, breakeven_threshold_pct, 211 | stop_loss_pct, increment_pct, take_profit_pct, 212 | setting_message, sma_period, sma_tf, hma1_period, 213 | hma2_period, hma3_period, hma1_tf, hma2_tf, hma3_tf) 214 | # obj_reverse_dca.close_position() 215 | # obj_reverse_dca.cancel_all_open_orders() 216 | while True: 217 | if not is_active: 218 | break 219 | try: 220 | telegram_bot.send_message(obj_reverse_dca.setting_message) 221 | obj_reverse_dca.run() 222 | except KeyboardInterrupt: 223 | telegram_bot.logger.warning("Keyboard interrupt detected, stopping the bot.") 224 | print("\nProgram interrupted by user.") 225 | time.sleep(1) 226 | except Exception as e: 227 | print(f'Exception => {e}') 228 | time.sleep(1) 229 | 230 | if __name__ == '__main__': 231 | thread = threading.Thread(target=main) 232 | thread.start() 233 | 234 | application.add_handler(CommandHandler("start", start)) 235 | application.add_handler(CommandHandler("stop", stop)) 236 | application.run_polling() 237 | -------------------------------------------------------------------------------- /reverse_dca.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import pandas as pd 4 | import numpy as np 5 | from binance.enums import * 6 | 7 | class ReverseDCA: 8 | is_active = False 9 | def __init__(self, binance_client, telegram_bot, ticker, initial_direction, 10 | base_order_size, volume_scale, breakeven_threshold_pct, stop_loss_pct, 11 | increment_pct, take_profit_pct, setting_message, sma_period, sma_tf, hma1_period, hma2_period, hma3_period, hma1_tf, hma2_tf, hma3_tf): 12 | self.telegram_bot = telegram_bot 13 | self.binance_client = binance_client 14 | self.ticker = ticker 15 | self.initial_direction = initial_direction 16 | self.base_order_size = base_order_size 17 | self.volume_scale = volume_scale 18 | self.breakeven_threshold_pct = breakeven_threshold_pct / 100 19 | self.stop_loss_pct = stop_loss_pct / 100 20 | self.base_increment_pct = increment_pct / 100 21 | self.take_profit_pct = take_profit_pct / 100 22 | self.first_entry_price = 0 23 | self.filled_price = 0 24 | self.avg_entry_price = 0 25 | self.hit_tp = False 26 | self.current_volume = self.base_order_size 27 | self.stop_loss_order_id = 0 28 | self.take_profit_order_id = 0 29 | self.take_profit_price = 0 30 | self.setting_message = setting_message 31 | self.sma_period = sma_period 32 | self.sma_tf = sma_tf 33 | self.hma1_period = hma1_period 34 | self.hma2_period = hma2_period 35 | self.hma3_period = hma3_period 36 | self.hma1_tf = hma1_tf 37 | self.hma2_tf = hma2_tf 38 | self.hma3_tf = hma3_tf 39 | self.filter_met = False 40 | self.increment_num = 0 41 | self.result = [] 42 | 43 | if self.initial_direction.lower() == "buy": 44 | self.stop_market_direction = SIDE_SELL 45 | self.stop_market_side = 'LONG' 46 | elif self.initial_direction.lower() == "sell": 47 | self.stop_market_direction = SIDE_BUY 48 | self.stop_market_side = 'SHORT' 49 | else: 50 | self.telegram_bot.send_message("Invalid initial direction provided in the config. Exiting!") 51 | exit() 52 | 53 | try: 54 | futures_exchange_info = self.binance_client.futures_exchange_info() 55 | symbol_info = next(item for item in futures_exchange_info['symbols'] if item['symbol'] == ticker) 56 | self.quantity_precision = int(symbol_info['quantityPrecision']) 57 | self.price_precision = int(symbol_info['pricePrecision']) 58 | except Exception as e: 59 | print(f"Exception! Getting futures exchange info. {e}. Exiting!") 60 | 61 | @classmethod 62 | def set_active_status(cls, status): 63 | cls.is_active = status 64 | 65 | def get_tf_val(self, timeframe): 66 | if timeframe == "1M": 67 | return self.binance_client.KLINE_INTERVAL_1MINUTE 68 | elif timeframe == "5M": 69 | return self.binance_client.KLINE_INTERVAL_5MINUTE 70 | elif timeframe == "15M": 71 | return self.binance_client.KLINE_INTERVAL_15MINUTE 72 | elif timeframe == "30M": 73 | return self.binance_client.KLINE_INTERVAL_30MINUTE 74 | elif timeframe == "1H": 75 | return self.binance_client.KLINE_INTERVAL_1HOUR 76 | elif timeframe == "4H": 77 | return self.binance_client.KLINE_INTERVAL_4HOUR 78 | elif timeframe == "1D": 79 | return self.binance_client.KLINE_INTERVAL_1DAY 80 | elif timeframe == "1W": 81 | return self.binance_client.KLINE_INTERVAL_1WEEK 82 | elif timeframe == "0": 83 | return self.binance_client.KLINE_INTERVAL_1MINUTE 84 | else: 85 | self.telegram_bot.send_message("Invalid initial timeframe provided in the config. Exiting!") 86 | exit() 87 | 88 | def get_start_str(self, timeframe, period): 89 | if timeframe == "1M": 90 | return '{} minute ago UTC'.format(period+5) 91 | elif timeframe == "5M": 92 | return '{} minute ago UTC'.format((period+1)*5) 93 | elif timeframe == "15M": 94 | return '{} minute ago UTC'.format((period+1)*15) 95 | elif timeframe == "30M": 96 | return '{} minute ago UTC'.format((period+1)*30) 97 | elif timeframe == "1H": 98 | return '{} hour ago UTC'.format(period+5) 99 | elif timeframe == "4H": 100 | return '{} hour ago UTC'.format((period+1)*4) 101 | elif timeframe == "1D": 102 | return '{} day ago UTC'.format(period+5) 103 | elif timeframe == "1W": 104 | return '{} week ago UTC'.format(period+5) 105 | elif timeframe == "0": 106 | return '{} minute ago UTC'.format(period+5) 107 | else: 108 | self.telegram_bot.send_message("Invalid initial timeframe or period provided in the config. Exiting!") 109 | exit() 110 | 111 | def get_mark_price(self): 112 | while True: 113 | if not self.is_active: 114 | break 115 | try: 116 | return float(self.binance_client.futures_symbol_ticker(symbol=self.ticker)['price']) # futures price 117 | except Exception as e: 118 | self.telegram_bot.send_message(f"Exception! Getting mark price. Retrying... {e}") 119 | time.sleep(2) 120 | break 121 | 122 | def get_order_info(self, order_id): 123 | while True: 124 | if not self.is_active: 125 | break 126 | try: 127 | order = self.binance_client.futures_get_order(symbol=self.ticker, orderId=order_id) 128 | return order 129 | except Exception as e: 130 | return 'None' 131 | # self.telegram_bot.send_message(f"Exception! Getting Order information. Retrying...{e}") 132 | # time.sleep(2) 133 | 134 | def check_signal_reversal(self, current_signals_invalid): 135 | if current_signals_invalid: 136 | # Require confirmation over multiple periods 137 | self.reversal_counter += 1 138 | if self.reversal_counter >= self.reversal_confirmation_periods: 139 | self.close_position() 140 | self.reset() 141 | else: 142 | self.reversal_counter = 0 143 | 144 | def check_opened_position(self): 145 | while True: 146 | if not self.is_active: 147 | break 148 | try: 149 | open_positions = self.binance_client.futures_position_information(symbol=self.ticker) # returns details relevant to your position. 150 | print(f"open_positions => {open_positions}") 151 | if float(open_positions[0]['entryPrice']) == 0: 152 | return -1 153 | else : 154 | return open_positions[0] 155 | 156 | except Exception as e: 157 | return -1 158 | # time.sleep(2) 159 | 160 | def close_position(self): 161 | open_position = self.check_opened_position() 162 | print(f'Here is close_position : open_position => {open_position}') 163 | if open_position != -1: 164 | size = float(open_position['positionAmt']) 165 | current_positionSide = open_position['positionSide'] 166 | if size > 0: 167 | side = SIDE_SELL 168 | close_positionSide = "LONG" 169 | elif size < 0: 170 | side = SIDE_BUY 171 | close_positionSide = "SHORT" 172 | else: 173 | self.telegram_bot.send_message("Invalid position size!") 174 | # print("Invalid position size!") 175 | return 176 | 177 | while True: 178 | if not self.is_active: 179 | break 180 | try: 181 | order = self.binance_client.futures_create_order(symbol=self.ticker, side=side, 182 | type=ORDER_TYPE_MARKET, 183 | positionSide=self.price_precision, 184 | quantity=abs(size) 185 | ) # futures 186 | break 187 | except Exception as e: 188 | self.telegram_bot.send_message(f"Exception! Placing Close position market order. Retrying... {e}") 189 | time.sleep(10) 190 | break 191 | 192 | def validate_symbol(self): 193 | try: 194 | exchange_info = self.binance_client.futures_exchange_info() 195 | symbols = [s['symbol'] for s in exchange_info['symbols']] 196 | if self.ticker not in symbols: 197 | self.telegram_bot.send_message(f"Invalid or inactive symbol {self.ticker}!") 198 | return False 199 | # Check if the symbol is active for trading 200 | symbol_info = next(item for item in exchange_info['symbols'] if item['symbol'] == self.ticker) 201 | if symbol_info['status'] != 'TRADING': 202 | self.telegram_bot.send_message(f"Symbol {self.ticker} is not available for trading. Current status: {symbol_info['status']}") 203 | # self.result.append(f"Symbol {self.ticker} is not available for trading. Current status: {symbol_info['status']}\n") 204 | return False 205 | return True 206 | except Exception as e: 207 | self.telegram_bot.send_message(f"Error validating symbol: {e}") 208 | return False 209 | 210 | def place_market_order(self, size, direction, mark_price): 211 | if not self.validate_symbol(): 212 | return 213 | if direction.lower() == "buy": 214 | direction = SIDE_BUY 215 | if self.binance_client.futures_get_position_mode()['dualSidePosition']: 216 | position_side = 'LONG' 217 | else: 218 | position_side = 'BOTH' # Assuming NOT hedge mode; use BOTH in One-way Mode 219 | elif direction.lower() == "sell": 220 | direction = SIDE_SELL 221 | if self.binance_client.futures_get_position_mode()['dualSidePosition']: 222 | position_side = 'SHORT' 223 | else: 224 | position_side = 'BOTH' # Assuming NOT hedge mode; use BOTH in One-way Mode 225 | else: 226 | self.telegram_bot.send_message(f"Invalid direction {direction}!") 227 | return 228 | 229 | while True: 230 | if not self.is_active: 231 | break 232 | try: 233 | print(f"position_side ==>{position_side}") 234 | print(f"direction ==>{direction}") 235 | order = self.binance_client.futures_create_order(symbol=self.ticker, 236 | side=direction, 237 | type=ORDER_TYPE_MARKET, 238 | positionSide=position_side, 239 | quantity=abs(size) 240 | ) # futures 241 | break 242 | except Exception as e: 243 | self.telegram_bot.send_message(f"Exception! Placing market order. Retrying1... {e}") 244 | time.sleep(10) 245 | break 246 | 247 | time.sleep(1) 248 | _order = self.get_order_info(order['orderId']) 249 | 250 | order_status = _order['status'].lower() 251 | fill_price = float(_order['avgPrice']) 252 | self.filled_price = fill_price 253 | if order_status == "filled": 254 | open_position = self.check_opened_position() 255 | if open_position != -1: 256 | position_size = float(open_position['positionAmt']) 257 | self.avg_entry_price = float(open_position['entryPrice']) 258 | # self.telegram_bot.send_message(f"Market {direction} order of size {round(size, self.quantity_precision)} " 259 | # f"{self.ticker} filled at ${fill_price}. Market price at that time: ${mark_price}. " 260 | # f"Current {direction} position size: {position_size} {self.ticker}.\n") 261 | self.result.append(f"Market {direction} order of size {round(size, self.quantity_precision)} " 262 | f"{self.ticker} filled at ${fill_price}. Market price at that time: ${mark_price}. " 263 | f"Current {direction} position size: {position_size} {self.ticker}.\n") 264 | # for i in self.result: 265 | # self.telegram_bot.send_message(i) 266 | # self.result.clear() 267 | 268 | def cancel_order(self, order_id): 269 | order = self.get_order_info(order_id) # futures 270 | order_status = order['status'].lower() 271 | if order_status.lower() != "filled": 272 | while True: 273 | if not self.is_active: 274 | break 275 | try: 276 | self.binance_client.futures_cancel_order(symbol=self.ticker, orderId=order_id) 277 | break 278 | except Exception as e: 279 | self.telegram_bot.send_message(f"Exception! Canceling order. Retrying... {e}") 280 | time.sleep(10) 281 | break 282 | 283 | def place_stop_market_order(self, stop_price): 284 | open_position = self.check_opened_position() 285 | 286 | if open_position != -1: 287 | size = float(open_position['positionAmt']) 288 | 289 | # Determine position mode 290 | dual_side_position = self.binance_client.futures_get_position_mode()['dualSidePosition'] 291 | if self.stop_market_direction.lower() == "buy": 292 | position_side = 'LONG' if dual_side_position else 'BOTH' 293 | elif self.stop_market_direction.lower() == "sell": 294 | position_side = 'SHORT' if dual_side_position else 'BOTH' 295 | else: 296 | self.telegram_bot.send_message(f"Invalid direction {self.stop_market_direction}!") 297 | return 298 | 299 | stop_price = round(stop_price, self.price_precision) 300 | while True: 301 | if not self.is_active: 302 | break 303 | print(f"self.price_precision ==>{self.price_precision}") 304 | print(f"self.stop_market_direction ==>{self.stop_market_direction}") 305 | try: 306 | order = self.binance_client.futures_create_order(symbol=self.ticker, 307 | side=self.stop_market_direction, 308 | type=FUTURE_ORDER_TYPE_STOP_MARKET, 309 | stopPrice=stop_price, 310 | positionSide=position_side, 311 | quantity=abs(size) 312 | ) # futures 313 | break 314 | except Exception as e: 315 | self.telegram_bot.send_message(f"Exception! Placing market order. Retrying2... {e}") 316 | time.sleep(10) 317 | break 318 | 319 | self.stop_order_id = order['orderId'] 320 | # self.telegram_bot.send_message(f"Stop Loss Market {self.stop_market_direction} order of size " 321 | # f"{round(abs(size), self.quantity_precision)} {self.ticker} placed at {stop_price}\n") 322 | self.result.append(f"Stop Loss Market {self.stop_market_direction} order of size " 323 | f"{round(abs(size), self.quantity_precision)} {self.ticker} placed at {stop_price}\n") 324 | # self.telegram_bot.send_message('\n\n'.join(self.result)) 325 | # for i in self.result: 326 | # print(f"result.for loop==2==> {i}") 327 | # self.telegram_bot.send_message(i) 328 | # self.result.clear() 329 | 330 | return self.stop_order_id 331 | 332 | def place_take_profit_market_order(self, stop_price): 333 | open_position = self.check_opened_position() 334 | if open_position != -1: 335 | size = float(open_position['positionAmt']) 336 | else: 337 | # self.telegram_bot.send_message(f"No open position available to place take profit order!") 338 | self.result.append(f"No open position available to place take profit order!\n") 339 | # for i in self.result: 340 | # self.telegram_bot.send_message(i) 341 | # self.result.clear() 342 | # self.telegram_bot.send_message('\n\n'.join(self.result)) 343 | return 344 | 345 | # Determine position mode 346 | dual_side_position = self.binance_client.futures_get_position_mode()['dualSidePosition'] 347 | 348 | if self.stop_market_direction.lower() == "buy": 349 | position_side = 'LONG' if dual_side_position else 'BOTH' 350 | elif self.stop_market_direction.lower() == "sell": 351 | position_side = 'SHORT' if dual_side_position else 'BOTH' 352 | else: 353 | self.telegram_bot.send_message(f"Invalid direction {self.stop_market_direction}!") 354 | return 355 | 356 | stop_price = round(stop_price, self.price_precision) 357 | while True: 358 | if not self.is_active: 359 | break 360 | print(f"self.price_precision ==>{self.price_precision}") 361 | print(f"self.price_precision ==>{self.stop_market_direction}") 362 | try: 363 | print(f"self.price_precision ==>{self.price_precision}") 364 | print(f"self.price_precision ==>{self.stop_market_direction}") 365 | order = self.binance_client.futures_create_order(symbol=self.ticker, 366 | side=self.stop_market_direction, 367 | type=FUTURE_ORDER_TYPE_TAKE_PROFIT_MARKET, 368 | stopPrice=stop_price, 369 | positionSide=position_side, 370 | quantity=abs(size) 371 | ) # futures 372 | break 373 | except Exception as e: 374 | self.telegram_bot.send_message(f"Exception! Placing market order. Retrying3... {e}") 375 | time.sleep(10) 376 | break 377 | 378 | self.stop_order_id = order['orderId'] 379 | # self.telegram_bot.send_message(f"Take Profit Market {self.stop_market_direction} order of size " 380 | # f"{round(abs(size), self.quantity_precision)} {self.ticker} placed at {stop_price}\n") 381 | self.result.append(f"Take Profit Market {self.stop_market_direction} order of size " 382 | f"{round(abs(size), self.quantity_precision)} {self.ticker} placed at {stop_price}\n") 383 | # self.telegram_bot.send_message('\n\n'.join(self.result)) 384 | # for i in self.result: 385 | # self.telegram_bot.send_message(i) 386 | # self.result.clear() 387 | return self.stop_order_id 388 | 389 | def place_initial_tpsl_orders(self): 390 | if self.initial_direction.lower() == "buy": 391 | self.take_profit_price = self.first_entry_price * (1 + self.take_profit_pct) 392 | stop_loss_price = self.first_entry_price * (1 - self.stop_loss_pct) 393 | else: 394 | self.take_profit_price = self.first_entry_price * (1 - self.take_profit_pct) 395 | stop_loss_price = self.first_entry_price * (1 + self.stop_loss_pct) 396 | 397 | # self.telegram_bot.send_message(f"Placing initial Take profit order.\n") 398 | self.result.append(f"Placing initial Take profit order.\n") 399 | self.take_profit_order_id = self.place_take_profit_market_order(self.take_profit_price) 400 | # self.telegram_bot.send_message(f"Placing initial Stop loss order.\n") 401 | self.result.append(f"Placing initial Stop loss order.\n") 402 | self.stop_loss_order_id = self.place_stop_market_order(stop_loss_price) 403 | 404 | def reset(self): 405 | self.first_entry_price = 0 406 | self.filled_price = 0 407 | self.avg_entry_price = 0 408 | self.hit_tp = False 409 | self.filter_met = False 410 | self.increment_num = 0 411 | self.current_volume = self.base_order_size 412 | if self.stop_loss_order_id != 0: 413 | self.cancel_order(self.stop_loss_order_id) 414 | self.stop_loss_order_id = 0 415 | if self.take_profit_order_id != 0: 416 | self.cancel_order(self.take_profit_order_id) 417 | self.take_profit_order_id = 0 418 | self.take_profit_price = 0 419 | 420 | def buy_check_tp_sl_increment(self, current_price): 421 | increment_price = self.first_entry_price + self.first_entry_price*self.base_increment_pct 422 | take_profit_order = self.get_order_info(self.take_profit_order_id) 423 | stop_loss_order = self.get_order_info(self.stop_loss_order_id) 424 | tp_order_status = take_profit_order['status'].lower() if isinstance(take_profit_order, dict) else 'none' 425 | sl_order_status = stop_loss_order['status'].lower() if isinstance(stop_loss_order, dict) else 'none' 426 | 427 | if tp_order_status == 'filled': # hit take profit 428 | self.telegram_bot.send_message(f"$$$ TAKE PROFIT HIT! Closing open position and sleeping for 10 seconds.") 429 | self.reset() 430 | time.sleep(2) 431 | return 432 | 433 | if sl_order_status == 'filled': # hit stop loss 434 | if not self.hit_tp: 435 | self.telegram_bot.send_message(f"!!! STOP LOSS HIT! Closing open position and sleeping for 10 seconds") 436 | else: 437 | self.telegram_bot.send_message(f"### BREAK EVEN HIT! Closing open position and sleeping for 10 seconds") 438 | self.reset() 439 | time.sleep(2) 440 | return 441 | 442 | if current_price >= increment_price: # increment level exceeded 443 | self.increment_num += 1 444 | self.telegram_bot.send_message(f"Increment price reached. Scaling volume by {self.volume_scale}x. ({self.increment_num}th order)") 445 | # print(f"Increment price reached. Scaling volume by {self.volume_scale}x") 446 | scaled_volume = self.current_volume * self.volume_scale 447 | self.current_volume = scaled_volume 448 | mark_price = self.get_mark_price() 449 | self.place_market_order(round(scaled_volume / mark_price, self.quantity_precision), self.initial_direction, mark_price) 450 | self.first_entry_price = self.filled_price 451 | print("filled price : ", self.filled_price) 452 | print("average price : ", self.avg_entry_price) 453 | self.update_sl_tp_order() 454 | self.hit_tp = True 455 | 456 | def sell_check_tp_sl_increment(self, current_price): 457 | print("Here is sell_check_tp_sl_increment") 458 | increment_price = self.first_entry_price - self.first_entry_price*self.base_increment_pct 459 | take_profit_order = self.get_order_info(self.take_profit_order_id) 460 | stop_loss_order = self.get_order_info(self.stop_loss_order_id) 461 | 462 | # Handle order status checking 463 | tp_order_status = take_profit_order['status'].lower() if isinstance(take_profit_order, dict) else 'none' 464 | sl_order_status = stop_loss_order['status'].lower() if isinstance(stop_loss_order, dict) else 'none' 465 | print(f"Here is sell_check_tp_sl_increment. tp_order_status =>{tp_order_status}") 466 | if tp_order_status == 'filled': # hit take profit 467 | self.telegram_bot.send_message(f"$$$ TAKE PROFIT HIT! Closing open position and sleeping for 10 seconds.") 468 | self.reset() 469 | time.sleep(2) 470 | return 471 | 472 | if sl_order_status == 'filled': # hit stop loss 473 | if not self.hit_tp: 474 | self.telegram_bot.send_message(f"!!! STOP LOSS HIT! Closing open position and sleeping for 10 seconds") 475 | else: 476 | self.telegram_bot.send_message(f"### BREAK EVEN HIT! Closing open position and sleeping for 10 seconds") 477 | self.reset() 478 | time.sleep(2) 479 | return 480 | print(f"Here is sell_check_tp_sl_increment. current_price =>{current_price} increment_price=>{increment_price}") 481 | if current_price <= increment_price: # increment level exceeded 482 | 483 | self.increment_num += 1 484 | self.telegram_bot.send_message(f"Increment price reached. Scaling volume by {self.volume_scale}x. ({self.increment_num}th order)") 485 | scaled_volume = self.current_volume * self.volume_scale 486 | self.current_volume = scaled_volume 487 | mark_price = self.get_mark_price() 488 | order_size = round(scaled_volume / mark_price, self.quantity_precision) 489 | self.place_market_order(order_size, self.initial_direction, mark_price) 490 | self.first_entry_price = self.filled_price 491 | print("filled price : ", self.filled_price) 492 | print("average price : ", self.avg_entry_price) 493 | self.update_sl_tp_order() 494 | self.hit_tp = True 495 | 496 | def update_sl_tp_order(self): 497 | if self.increment_num > 0: 498 | if self.initial_direction.lower() == "buy": 499 | stop_loss_price = self.avg_entry_price * (1 + self.breakeven_threshold_pct) 500 | else: 501 | stop_loss_price = self.avg_entry_price * (1 + self.breakeven_threshold_pct*-1) 502 | else: 503 | if self.initial_direction.lower() == "buy": 504 | stop_loss_price = self.first_entry_price * (1 - self.stop_loss_pct) 505 | else: 506 | stop_loss_price = self.first_entry_price * (1 + self.stop_loss_pct) 507 | 508 | self.telegram_bot.send_message(f"Updating Stop Loss & Take Profit orders") 509 | 510 | if self.stop_loss_order_id != 0: 511 | self.cancel_order(self.stop_loss_order_id) 512 | self.stop_loss_order_id = self.place_stop_market_order(stop_loss_price) 513 | 514 | if self.take_profit_order_id != 0: 515 | self.cancel_order(self.take_profit_order_id) 516 | self.take_profit_order_id = self.place_take_profit_market_order(self.take_profit_price) 517 | 518 | def calculate_sma(self, prices, period, sma_tf): 519 | if period == 0 or sma_tf == '0': 520 | return 0 521 | else: 522 | sma = pd.Series(prices).rolling(window=period).mean().iloc[-1] 523 | return sma 524 | 525 | def calculate_wma(self, prices, period): 526 | weights = np.arange(1, period + 1) 527 | wma = prices.rolling(period).apply(lambda prices: np.dot(prices, weights)/weights.sum(), raw=True) 528 | return wma 529 | 530 | def calculate_hma(self, prices, period, hma_tf): 531 | if period == 0 or hma_tf == '0': 532 | return 0 533 | else: 534 | half_length = math.ceil(period / 2) 535 | sqrt_length = round(math.sqrt(period)) 536 | hma_prices_series = pd.Series(prices) 537 | wma_half = self.calculate_wma(hma_prices_series, half_length) 538 | wma_full = self.calculate_wma(hma_prices_series, period) 539 | diff_wma = 2 * wma_half - wma_full 540 | hma = self.calculate_wma(diff_wma, sqrt_length) 541 | return hma.iloc[-1] 542 | 543 | def get_historical_klines(self, interval, start_str): 544 | while True: 545 | if not self.is_active: 546 | break 547 | try: 548 | klines = self.binance_client.futures_historical_klines(symbol=self.ticker, interval=interval, start_str=start_str) 549 | return klines 550 | except Exception as e: 551 | return 'None' 552 | time.sleep(2) 553 | 554 | def isAvailable(self) : 555 | # getting historical candle data 556 | sma_klines = self.get_historical_klines(self.get_tf_val(self.sma_tf), self.get_start_str(self.sma_tf, self.sma_period)) 557 | # Parse the closing prices 558 | # getting historical candle data 559 | hma1_klines = self.get_historical_klines(self.get_tf_val(self.hma1_tf), self.get_start_str(self.hma1_tf, self.hma1_period*3)) 560 | hma2_klines = self.get_historical_klines(self.get_tf_val(self.hma2_tf), self.get_start_str(self.hma2_tf, self.hma2_period*3)) 561 | hma3_klines = self.get_historical_klines(self.get_tf_val(self.hma3_tf), self.get_start_str(self.hma3_tf, self.hma3_period*3)) 562 | 563 | if sma_klines =='None' or hma1_klines=='None' or hma2_klines=='None' or hma3_klines=='None' : 564 | print("Error getting historical klines----1-->") 565 | return False 566 | 567 | sma_prices = [float(kline[4]) for kline in sma_klines][:-1] 568 | # Parse the closing prices 569 | hma1_prices = [float(kline[4]) for kline in hma1_klines][:-1] 570 | hma2_prices = [float(kline[4]) for kline in hma2_klines][:-1] 571 | hma3_prices = [float(kline[4]) for kline in hma3_klines][:-1] 572 | 573 | # Calculate current SMA and HMA values 574 | current_sma = self.calculate_sma(sma_prices, self.sma_period, self.sma_tf) 575 | current_hma1 = self.calculate_hma(hma1_prices, self.hma1_period, self.hma1_tf) 576 | current_hma2 = self.calculate_hma(hma2_prices, self.hma2_period, self.hma2_tf) 577 | current_hma3 = self.calculate_hma(hma3_prices, self.hma3_period, self.hma3_tf) 578 | sma_last_closed_candle = 0 if self.sma_tf == "0" or self.sma_period == 0 else sma_prices[-1] 579 | hma1_last_closed_candle = 0 if self.hma1_tf == "0" or self.hma1_period == 0 else hma1_prices[-1] 580 | hma2_last_closed_candle = 0 if self.hma2_tf == "0" or self.hma2_period == 0 else hma2_prices[-1] 581 | hma3_last_closed_candle = 0 if self.hma3_tf == "0" or self.hma3_period == 0 else hma3_prices[-1] 582 | 583 | if self.avg_entry_price == 0: 584 | if self.initial_direction.lower() == "buy": 585 | if (sma_last_closed_candle > current_sma != 0 or current_sma == 0) and \ 586 | (hma1_last_closed_candle > current_hma1 != 0 or current_hma1 == 0) and \ 587 | (hma2_last_closed_candle > current_hma2 != 0 or current_hma2 == 0) and \ 588 | (hma3_last_closed_candle > current_hma3 != 0 or current_hma3 == 0): 589 | return True 590 | elif self.initial_direction.lower() == "sell": 591 | if (sma_last_closed_candle < current_sma != 0 or current_sma == 0) and \ 592 | (hma1_last_closed_candle < current_hma1 != 0 or current_hma1 == 0) and \ 593 | (hma2_last_closed_candle < current_hma2 != 0 or current_hma2 == 0) and \ 594 | (hma3_last_closed_candle < current_hma3 != 0 or current_hma3 == 0) : 595 | return True 596 | return False 597 | 598 | def run(self): 599 | # getting historical candle data 600 | sma_klines = self.get_historical_klines(self.get_tf_val(self.sma_tf), self.get_start_str(self.sma_tf, self.sma_period)) 601 | # Parse the closing prices 602 | sma_prices = [float(kline[4]) for kline in sma_klines][:-1] 603 | 604 | # getting historical candle data 605 | hma1_klines = self.get_historical_klines(self.get_tf_val(self.hma1_tf), self.get_start_str(self.hma1_tf, self.hma1_period*3)) 606 | hma2_klines = self.get_historical_klines(self.get_tf_val(self.hma2_tf), self.get_start_str(self.hma2_tf, self.hma2_period*3)) 607 | hma3_klines = self.get_historical_klines(self.get_tf_val(self.hma3_tf), self.get_start_str(self.hma3_tf, self.hma3_period*3)) 608 | 609 | # Parse the closing prices 610 | hma1_prices = [float(kline[4]) for kline in hma1_klines][:-1] 611 | hma2_prices = [float(kline[4]) for kline in hma2_klines][:-1] 612 | hma3_prices = [float(kline[4]) for kline in hma3_klines][:-1] 613 | 614 | # Calculate current SMA and HMA values 615 | current_sma = self.calculate_sma(sma_prices, self.sma_period, self.sma_tf) 616 | current_hma1 = self.calculate_hma(hma1_prices, self.hma1_period, self.hma1_tf) 617 | current_hma2 = self.calculate_hma(hma2_prices, self.hma2_period, self.hma2_tf) 618 | current_hma3 = self.calculate_hma(hma3_prices, self.hma3_period, self.hma3_tf) 619 | sma_last_closed_candle = 0 if self.sma_tf == "0" or self.sma_period == 0 else sma_prices[-1] 620 | hma1_last_closed_candle = 0 if self.hma1_tf == "0" or self.hma1_period == 0 else hma1_prices[-1] 621 | hma2_last_closed_candle = 0 if self.hma2_tf == "0" or self.hma2_period == 0 else hma2_prices[-1] 622 | hma3_last_closed_candle = 0 if self.hma3_tf == "0" or self.hma3_period == 0 else hma3_prices[-1] 623 | print(f"avg_entry_price => {self.avg_entry_price}") 624 | open_position = self.check_opened_position() 625 | if open_position == -1: # not in position 626 | mark_price = self.get_mark_price() 627 | 628 | if self.initial_direction.lower() == "buy": 629 | if (sma_last_closed_candle > current_sma or current_sma == 0) and \ 630 | (hma1_last_closed_candle > current_hma1 and current_hma1 != 0) or (current_hma1 == 0) and \ 631 | (hma2_last_closed_candle > current_hma2 and current_hma2 != 0) or (current_hma2 == 0) and \ 632 | (hma3_last_closed_candle > current_hma3 and current_hma3 != 0) or (current_hma3 == 0): 633 | 634 | self.telegram_bot.send_message(f"Conditions have been met, so Starting the bot!!! \n" 635 | f"SMA Last Candle's Price is {sma_last_closed_candle} and SMA_{self.sma_period} is {round(current_sma, 4)}. \n" 636 | f"HMA 1 Last Candle's Price is {hma1_last_closed_candle} and HMA_{self.hma1_period} is {round(current_hma1, 4)}. \n" 637 | f"HMA 2 Last Candle's Price is {hma2_last_closed_candle} and HMA_{self.hma2_period} is {round(current_hma2, 4)}. \n" 638 | f"HMA 3 Last Candle's Price is {hma3_last_closed_candle} and HMA_{self.hma3_period} is {round(current_hma3, 4)}. \n") 639 | order_size = round(self.current_volume / mark_price, self.quantity_precision) 640 | # self.telegram_bot.send_message(f"****************************************************\n" 641 | # f"First entry. Opening new position with base volume {order_size} {self.ticker}\n") 642 | self.result.append(f"****************************************************\n" 643 | f"First entry. Opening new position with base volume {order_size} {self.ticker}\n") 644 | self.place_market_order(order_size, self.initial_direction, mark_price) 645 | self.first_entry_price = self.filled_price 646 | # Place initial take profit and stop loss orders. 647 | self.place_initial_tpsl_orders() 648 | elif (sma_last_closed_candle < current_sma or hma1_last_closed_candle < current_hma1 or 649 | hma2_last_closed_candle < current_hma2 or hma3_last_closed_candle < current_hma3) and not self.filter_met: 650 | 651 | self.telegram_bot.send_message(f"Pauses the bot until the condition is met!!! \n" 652 | f"SMA Last Candle's Price is {sma_last_closed_candle} and SMA_{self.sma_period} is {round(current_sma, 4)}. \n" 653 | f"HMA 1 Last Candle's Price is {hma1_last_closed_candle} and HMA_{self.hma1_period} is {round(current_hma1, 4)}. \n" 654 | f"HMA 2 Last Candle's Price is {hma2_last_closed_candle} and HMA_{self.hma2_period} is {round(current_hma2, 4)}. \n" 655 | f"HMA 3 Last Candle's Price is {hma3_last_closed_candle} and HMA_{self.hma3_period} is {round(current_hma3, 4)}. \n") 656 | self.filter_met = True 657 | elif self.initial_direction.lower() == "sell": 658 | if (sma_last_closed_candle < current_sma or current_sma == 0) and \ 659 | (hma1_last_closed_candle < current_hma1 and current_hma1 != 0) or (current_hma1 == 0) and \ 660 | (hma2_last_closed_candle < current_hma2 and current_hma2 != 0) or (current_hma2 == 0) and \ 661 | (hma3_last_closed_candle < current_hma3 and current_hma3 != 0) or (current_hma3 == 0): 662 | 663 | self.telegram_bot.send_message(f"Conditions have been met, so Starting the bot!!! \n" 664 | f"SMA Last Candle's Price is {sma_last_closed_candle} and SMA_{self.sma_period} is {round(current_sma, 4)}. \n" 665 | f"HMA 1 Last Candle's Price is {hma1_last_closed_candle} and HMA_{self.hma1_period} is {round(current_hma1, 4)}. \n" 666 | f"HMA 2 Last Candle's Price is {hma2_last_closed_candle} and HMA_{self.hma2_period} is {round(current_hma2, 4)}. \n" 667 | f"HMA 3 Last Candle's Price is {hma3_last_closed_candle} and HMA_{self.hma3_period} is {round(current_hma3, 4)}. \n") 668 | order_size = round(self.current_volume / mark_price, self.quantity_precision) 669 | print(f"------------------------------------------------------------\n current_volume => {self.current_volume} mark_price=>{mark_price} order_size=>{order_size}\n-------------------------------------------------") 670 | # self.telegram_bot.send_message(f"****************************************************\n First entry." 671 | # f"Opening new position with base volume {order_size} {self.ticker}\n") 672 | self.result.append(f"****************************************************\n First entry." 673 | f"Opening new position with base volume {order_size} {self.ticker}\n") 674 | self.place_market_order(order_size, self.initial_direction, mark_price) 675 | self.first_entry_price = self.filled_price 676 | # Place initial take profit and stop loss orders. 677 | self.place_initial_tpsl_orders() 678 | elif (sma_last_closed_candle > current_sma or hma1_last_closed_candle > current_hma1 or 679 | hma2_last_closed_candle > current_hma2 or hma3_last_closed_candle > current_hma3) and not self.filter_met: 680 | 681 | self.telegram_bot.send_message(f"Pauses the bot until the condition is met!!! \n" 682 | f"SMA Last Candle's Price is {sma_last_closed_candle} and SMA_{self.sma_period} is {round(current_sma, 4)}. \n" 683 | f"HMA 1 Last Candle's Price is {hma1_last_closed_candle} and HMA_{self.hma1_period} is {round(current_hma1, 4)}. \n" 684 | f"HMA 2 Last Candle's Price is {hma2_last_closed_candle} and HMA_{self.hma2_period} is {round(current_hma2, 4)}. \n" 685 | f"HMA 3 Last Candle's Price is {hma3_last_closed_candle} and HMA_{self.hma3_period} is {round(current_hma3, 4)}. \n") 686 | self.filter_met = True 687 | else: 688 | self.telegram_bot.send_message("Invalid initial direction provided in the config. Exiting!") 689 | exit() 690 | else: # already in position 691 | print("Here is already in position") 692 | current_price = self.get_mark_price() 693 | if self.initial_direction.lower() == "buy": 694 | if (sma_last_closed_candle > current_sma or current_sma == 0) and \ 695 | (hma1_last_closed_candle > current_hma1 and current_hma1 != 0) or (current_hma1 == 0) and \ 696 | (hma2_last_closed_candle > current_hma2 and current_hma2 != 0) or (current_hma2 == 0) and \ 697 | (hma3_last_closed_candle > current_hma3 and current_hma3 != 0) or (current_hma3 == 0): 698 | 699 | self.buy_check_tp_sl_increment(current_price) 700 | else: 701 | self.telegram_bot.send_message(f"Signals reversed. Closing positions!!! \n" 702 | f"SMA Last Candle's Price is {sma_last_closed_candle} and SMA_{self.sma_period} is {round(current_sma, 4)}. \n" 703 | f"HMA 1 Last Candle's Price is {hma1_last_closed_candle} and HMA_{self.hma1_period} is {round(current_hma1, 4)}. \n" 704 | f"HMA 2 Last Candle's Price is {hma2_last_closed_candle} and HMA_{self.hma2_period} is {round(current_hma2, 4)}. \n" 705 | f"HMA 3 Last Candle's Price is {hma3_last_closed_candle} and HMA_{self.hma3_period} is {round(current_hma3, 4)}. \n") 706 | self.close_position() 707 | self.reset() 708 | elif self.initial_direction.lower() == "sell": 709 | if (sma_last_closed_candle < current_sma or current_sma == 0) and \ 710 | (hma1_last_closed_candle < current_hma1 and current_hma1 != 0) or (current_hma1 == 0) and \ 711 | (hma2_last_closed_candle < current_hma2 and current_hma2 != 0) or (current_hma2 == 0) and \ 712 | (hma3_last_closed_candle < current_hma3 and current_hma3 != 0) or (current_hma3 == 0): 713 | 714 | print("Here is already in position. I have met the criteria") 715 | self.sell_check_tp_sl_increment(current_price) 716 | else: 717 | print("Here is already in position. I haven't met the criteria") 718 | self.telegram_bot.send_message(f"Signals reversed. Closing positions!!! \n" 719 | f"SMA Last Candle's Price is {sma_last_closed_candle} and SMA_{self.sma_period} is {round(current_sma, 4)}. \n" 720 | f"HMA 1 Last Candle's Price is {hma1_last_closed_candle} and HMA_{self.hma1_period} is {round(current_hma1, 4)}. \n" 721 | f"HMA 2 Last Candle's Price is {hma2_last_closed_candle} and HMA_{self.hma2_period} is {round(current_hma2, 4)}. \n" 722 | f"HMA 3 Last Candle's Price is {hma3_last_closed_candle} and HMA_{self.hma3_period} is {round(current_hma3, 4)}. \n") 723 | self.close_position() 724 | self.reset() 725 | else: 726 | self.telegram_bot.send_message("Invalid initial direction provided in the config. Exiting!") 727 | exit() 728 | 729 | for i in self.result: 730 | self.telegram_bot.send_message(i) 731 | self.result.clear() 732 | --------------------------------------------------------------------------------