The following article describes how we can leverage Python & AWS Lambda & 3Commas API to automatically adjust the Base and Safety order size of a DCA crypto trading multi-pair bot based on the total balance available in the account.
Prerequisites
- AWS Account
- 3Commas Account
- 3Commas API read/write keys
- Trading Account supported by 3Commas (Coinbase, FTX, etc.)
Python Script + Dependencies
Dependencies:
- cctx
- certifi
- charset_normalizer
- idna
- py3cw
- requests
Packaged Python lambda function + all dependencies can be downloaded from GitHub: https://github.com/JozefJarosciak/3commas-python-selfadjusting-dca-bot
Problem Definition
Until now, most of my playing with the crypto market was limited to writing my own Python trading bots and I always leveraged Splunk and Splunk API to create my own charts and prediction models, which I plugged back into my bots and in such way adjusted my trading strategies using machine learning. However, recently I came across the 3Commas DCA bots and I kind of like what they were doing with the product and started using it with good success on their paper trading accounts.
Now, to the issue at hand. My biggest pet peeve with 3Commas is that they do not support automatic adjusting of Base and Safety order sizes based on the total balance of the account in multi-pair bots. The % sizing option is only available for single pair bots:
3Commas, at the moment, does not offer any logic to automatically adjust these values for multi-pair DCA bots, and that means, that as time goes and your trading account balance grows larger, you will need to periodically visit and adjust your multi-pair DCA bots to make sure you’re leveraging the power of compounding. For example, if your trading account closes 50 deals a day, you’ll essentially need to login to the 3commas website 50 times a day and adjust these two values, otherwise, you’ll be losing money. Why? Well, compounding is essentially money multiplying itself, meaning, more money you have for your DCA bot to trade with, the more money you can assign to each trade and that resolves in larger profits from your trades. So, it would be a huge mistake not to earn more profit just because 3Commas does not allow base and safety orders to be based on the percentage of the total balance of your account.
These are the two settings that I am referring to:
To automatically adjust these two values, we need to create a program, that is smart enough to log in to 3commas, look up the total value of the USDT balance, and then use it to recalculate the base and safety order size and adjust the DCA bot to change the values in your strategy. In order to do so, it needs to calculate these values in such a way, that it gets as close to the total as possible, meaning it has to do the calculation for each step in the trade and bring it as close as possible to the total amount.
So, let’s say we have the following DCA bot:
- An account with the balance of 10,000 USDT
- We want to trade 10 USDT pairs (ADA, 1INCH, AAVE …) using a long strategy
- Taking profit in Quote (USDT) currency
- Max safety trades count: 10
- Max active safety trades count: 5
- Price deviation to open safety orders (% from initial order): 2%
- Safety order volume scale: 1
- Safety order step scale: 1
How would you calculate the Base and Safety order sizes?
Well, one way is to manually keep trying, until you get close to the total of 10,000 USDT that is available for trading. You’d probably spent several minutes doing this and you’d arrive at one of the possible solutions, that the best option is to use:
- Base order size: USDT 59
- Safety order size: USDT 94
The calculation for all 10 safety trades would look like this:
Order № | Deviation, % | Order | Price | Average price | Required price | Required change, % | Total | ||
---|---|---|---|---|---|---|---|---|---|
Size (ADA) | Volume (USDT) | Size (ADA) | Volume (USDT) | ||||||
BO | 0 | 30.2 | 59.192 | 1.96 | 1.96 | 1.993 | 1.68367346 | 30.2 | 59.192 |
1 | 2 | 49 | 94.08000001 | 1.92 | 1.93525252 | 1.968 | 2.5 | 79.2 | 153.27200001 |
2 | 4 | 50 | 94.05000001 | 1.881 | 1.91425696 | 1.946 | 3.45560871 | 129.2 | 247.32200002 |
3 | 6 | 51.1 | 94.1262 | 1.842 | 1.89377814 | 1.925 | 4.50597176 | 180.3 | 341.44820002 |
4 | 8 | 52.2 | 94.1166 | 1.803 | 1.87339698 | 1.905 | 5.65723793 | 232.5 | 435.56480002 |
5 | 10 | 53.3 | 94.0212 | 1.764 | 1.8529951 | 1.884 | 6.80272108 | 285.8 | 529.58600002 |
6 | 12 | 54.6 | 94.1304 | 1.724 | 1.83230434 | 1.863 | 8.06264501 | 340.4 | 623.71640002 |
7 | 14 | 55.8 | 94.02300001 | 1.685 | 1.8115583 | 1.842 | 9.31750741 | 396.2 | 717.73940003 |
8 | 16 | 57.2 | 94.1512 | 1.646 | 1.79067181 | 1.821 | 10.63183475 | 453.4 | 811.89060003 |
9 | 18 | 58.5 | 94.00950001 | 1.607 | 1.76968177 | 1.799 | 11.94772868 | 511.9 | 905.90010004 |
10 | 20 | 60 | 94.08000001 | 1.568 | 1.74852264 | 1.778 | 13.39285714 | 571.9 | 999.98010005 |
This setting would use 99.99% of your available account or 9998 USDT.
Now, this is great, but what if you’ve just closed several trades and generated an additional 110 USDT towards the base balance. Now you have 10,110 USDT in your account, but your DCA bot is still using only 10,000 USDT.
To fix it, and leverage an additional 11% USDT available in your account, you’d have to log in to 3Commas and manually adjust the base and safety order size. After a bit of calculation you’d arrive at the following change:
- Base order size: USDT 60 (increased from 59)
- Safety order size: USDT 95 (increased from 94)
The safety order calculation table would change now to look like this. As you can see, each safety trade would now leverage the additional 110 USDT available in the account, meaning you’d earn more money from your trades:
Order № | Deviation, % | Order | Price | Average price | Required price | Required change, % | Total | ||
---|---|---|---|---|---|---|---|---|---|
Size (ADA) | Volume (USDT) | Size (ADA) | Volume (USDT) | ||||||
BO | 0 | 30.7 | 60.17200001 | 1.96 | 1.96 | 1.993 | 1.68367346 | 30.7 | 60.17200001 |
1 | 2 | 49.5 | 95.04 | 1.92 | 1.93531172 | 1.968 | 2.5 | 80.2 | 155.21200001 |
2 | 4 | 50.6 | 95.1786 | 1.881 | 1.91430122 | 1.946 | 3.45560871 | 130.8 | 250.39060001 |
3 | 6 | 51.6 | 95.0472 | 1.842 | 1.89384758 | 1.926 | 4.56026058 | 182.4 | 345.43780001 |
4 | 8 | 52.7 | 95.01810001 | 1.803 | 1.87348319 | 1.905 | 5.65723793 | 235.1 | 440.45590002 |
5 | 10 | 53.9 | 95.0796 | 1.764 | 1.85306401 | 1.884 | 6.80272108 | 289 | 535.53550002 |
6 | 12 | 55.2 | 95.16480001 | 1.724 | 1.83236577 | 1.863 | 8.06264501 | 344.2 | 630.70030003 |
7 | 14 | 56.4 | 95.034 | 1.685 | 1.81161832 | 1.842 | 9.31750741 | 400.6 | 725.73430003 |
8 | 16 | 57.8 | 95.13880001 | 1.646 | 1.79073538 | 1.821 | 10.63183475 | 458.4 | 820.87310004 |
9 | 18 | 59.2 | 95.1344 | 1.607 | 1.76972082 | 1.799 | 11.94772868 | 517.6 | 916.00750004 |
10 | 20 | 60.6 | 95.0208 | 1.568 | 1.74857886 | 1.778 | 13.39285714 | 578.2 | 1011.02830004 |
Solution Architecture
Envisioned architecture:
Python Source Code
I will not go down to the details of how the below code works, because that would increase the size of this article beyond the scope of my free time, but I’ve created the below Python script, which allows you to configure the bot, and it finds the best value for the base and safety order size and automatically login to 3Commas and adjust these values in your existing DCA bot.
Packaged Python lambda function + all dependencies can be downloaded from GitHub: https://github.com/JozefJarosciak/3commas-python-selfadjusting-dca-bot
You can see the code of the lambda function here: https://github.com/JozefJarosciak/3commas-python-selfadjusting-dca-bot/blob/main/lambda_function.py
This is the raw format:
import os from py3cw.request import Py3CW def lambda_handler(event, context): #################### # 3c configuration # #################### threecommas_key = os.environ['key'] threecommas_secret = os.environ['secret'] ##################### # test run only? # ##################### test_run = 'no' ##################### # bot configuration # ##################### account_name = 'Paper Account' # your account name bot_name = 'Paper_DCA_Bot' # your bot name bot_id = 'xxxxxxx' # 3commas ID of the bot you want to auto calculate the Base and Safety orders - get it from the bot url (https://3commas.io/bots/xxxxxxx/edit) bot_take_profit = '1.7' bot_trailing_enabled = True bot_trailing_deviation = '0.5' bot_max_safety_orders_count = 25 bot_max_active_deals = 6 bot_active_safety_orders_count = 1 bot_pairs = ['USDT_AAVE', 'USDT_AKRO', ...] # list your pairs here bot_safety_order_size = int(2) bot_strategy_list = [{'options': {'time': '1m', 'type': 'strong_buy'}, 'strategy': 'trading_view'}, {'options': {'time': '5m', 'type': 'strong_buy'}, 'strategy': 'trading_view'}, {'options': {'time': '15m', 'type': 'strong_buy'}, 'strategy': 'trading_view'}, {'options': {'time': '1h', 'type': 'strong_buy'}, 'strategy': 'trading_view'}, {'options': {'time': '4h', 'type': 'buy_or_strong_buy'}, 'strategy': 'trading_view'}, {'options': {'time': '1d', 'type': 'buy_or_strong_buy'}, 'strategy': 'trading_view'}] # list your strategy here bot_martingale_volume_coefficient = '1.05' bot_martingale_step_coefficient = '1.0' bot_safety_order_step_percentage = '2' bot_take_profit_type = 'total' ###################### # risk configuration # ###################### # How much of your bankroll do you want to use risk_percentage = 325 ################################### # init python wrapper for 3commas # ################################### p3cw = Py3CW( key=threecommas_key, secret=threecommas_secret, request_options={ 'request_timeout': 10, 'nr_of_retries': 1, 'retry_status_codes': [502] } ) ###################### # auto configuration # ###################### error, data_accounts = p3cw.request( entity='accounts', action='' ) for datapoint_account in data_accounts: if datapoint_account['name'] == account_name: account_balance = round(float(datapoint_account['usd_amount']),2) account_id = str(datapoint_account['id']) # Account ID of your Exchange in 3commas # print("Account Balance ($):", account_balance) if account_balance > 0: pass else: exit("Bad account information, balance = 0") p3cw.request error, data_bots = p3cw.request( entity='bots', action='' ) ###################### # auto calculation # ###################### print("----------START-----------") base_order_size = int(bot_safety_order_size / bot_safety_order_size) adj_safety_order_size = bot_safety_order_size total_final_required_balance_array = [] total_final_required_balance = 0 # Calculate Safety Order Maximum Volume for adj_safety_order_size in range(bot_safety_order_size, bot_safety_order_size + 100000): step_value = float(0) total_value = float(0) step_value = step_value + adj_safety_order_size total_value = round(float(base_order_size), 2) x = 0 for x in range(1, bot_max_safety_orders_count + 1): base_order_size = int(adj_safety_order_size / 2) if x == 1: step_value = round(step_value, 2) else: step_value = round((step_value * 1.05), 2) total_value = round(total_value + step_value, 2) # print("#", x, " - ", float(step_value), " - ", float(total_value)) total_final_required_balance = round(total_value * int(bot_max_active_deals), 2) if total_final_required_balance > (account_balance*(float(risk_percentage)/100)): print("Recommended Safety Order ($):", adj_safety_order_size - 1) break else: total_final_required_balance_array.append(total_final_required_balance) total_final_required_balance = 0 total_final_required_balance = 0 adj_base_order_size = base_order_size final_safety_order = adj_safety_order_size - 1 # Calculate Base Order Maximum Volume for adj_base_order_size in range(base_order_size, base_order_size + 1000000): step_value = float(0) total_value = float(0) step_value = step_value + final_safety_order total_value = round(float(adj_base_order_size), 2) x = 0 for x in range(1, bot_max_safety_orders_count + 1): if x == 1: step_value = round(step_value, 2) else: step_value = round((step_value * 1.05), 2) total_value = round(total_value + step_value, 2) total_final_required_balance = round(total_value * int(bot_max_active_deals), 2) if (total_final_required_balance > (account_balance*(float(risk_percentage)/100))) or ((adj_base_order_size+1)>adj_safety_order_size/2): print("Recommended Base Order ($):", adj_base_order_size - 1) print("This configuration will use: $", total_final_required_balance_array[-1], ", which is $", round((account_balance*(risk_percentage/100))-total_final_required_balance_array[-1], 2), "below allowed maximum: $", round(account_balance + ((account_balance*(risk_percentage/100))-account_balance),2), "(account balance of $", account_balance, "+", risk_percentage, "% risk: $", round((account_balance*(risk_percentage/100))-account_balance,2), ")") break else: total_final_required_balance_array.append(total_final_required_balance) total_final_required_balance = 0 ###################### # update 3commas bot # ###################### if 'yes' in test_run: print("Test Run Completed!") else: error, update_bot = p3cw.request( entity='bots', action='update', action_id=bot_id, payload={ 'account_id': account_id, 'bot_id': bot_id, 'name': bot_name, 'pairs': bot_pairs, 'base_order_volume': adj_base_order_size - 1, # this is auto calculated value that we're changing 'safety_order_volume': adj_safety_order_size - 1, # this is auto calculated value that we're changing 'take_profit': bot_take_profit, 'martingale_volume_coefficient': bot_martingale_volume_coefficient, 'martingale_step_coefficient': bot_martingale_step_coefficient, 'max_safety_orders': bot_max_safety_orders_count, 'active_safety_orders_count': bot_active_safety_orders_count, 'safety_order_step_percentage': bot_safety_order_step_percentage, 'take_profit_type': bot_take_profit_type, 'strategy_list': bot_strategy_list, 'max_active_deals': str(bot_max_active_deals), 'trailing_enabled': bot_trailing_enabled, 'trailing_deviation': bot_trailing_deviation } ) if error == {}: print("Bot update completed!") else: print(error) print("Bot update NOT completed!")
So, let’s test it just by running the code as a Python script.
First, we need to create a DCA bot in our paper trading account. Let’s name it Paper_DCA_Bot
Configure it like this:
As you can see, this configuration uses
- An account with the balance of 440,821.4283 USDT
- We want to trade 10 USDT pairs: [‘USDT_1INCH’, ‘USDT_AAVE’, ‘USDT_ADA’,’USDT_AGLD’, ‘USDT_ALGO’, ‘USDT_ATOM’,’USDT_BTC’, ‘USDT_DOGE’, ‘USDT_LTC’,’USDT_XRP’]
- Taking profit in Quote (USDT) currency
- Max safety trades count: 10
- Max active safety trades count: 1
- Price deviation to open safety orders (% from initial order): 1%
- Safety order volume scale: 1
- Safety order step scale: 1
3Commas is telling us, that this setting would use a maximum of 2,647 USDT of our total balance of 440821.4283 USDT (or 0.6%).
Now, let’s run our script to calculate the base and safety order sizes for us to actually leverage the size of our entire USDT account.
First, we need to configure the script to replicate the above settings in code:
And here is the result:
Now, let’s log into the 3commas bot. As we can see, the values were all automatically adjusted for us.
Voila!, it worked:
The base order and safety order calculations look correct:
Order № | Deviation, % | Order | Price | Average price | Required price | Required change, % | Total | ||
---|---|---|---|---|---|---|---|---|---|
Size (1INCH) | Volume (USDT) | Size (1INCH) | Volume (USDT) | ||||||
BO | 0 | 355.5 | 1684.359 | 4.738 | 4.738 | 4.818 | 1.68847615 | 355.5 | 1684.359 |
1 | 1 | 718.6 | 3370.234 | 4.69 | 4.70588678 | 4.785 | 2.02558635 | 1074.1 | 5054.593 |
2 | 2 | 762.2 | 3538.8946 | 4.643 | 4.67978413 | 4.759 | 2.49838466 | 1836.3 | 8593.4876 |
3 | 3 | 808.6 | 3715.517 | 4.595 | 4.65386388 | 4.732 | 2.98150163 | 2644.9 | 12309.0046 |
4 | 4 | 857.8 | 3901.2744 | 4.548 | 4.62793816 | 4.706 | 3.47405452 | 3502.7 | 16210.279 |
5 | 5 | 910.1 | 4096.3601 | 4.501 | 4.60175831 | 4.679 | 3.95467673 | 4412.8 | 20306.6391 |
6 | 6 | 965.9 | 4301.1527 | 4.453 | 4.57504449 | 4.652 | 4.46889737 | 5378.7 | 24607.7918 |
7 | 7 | 1025 | 4516.15 | 4.406 | 4.5479866 | 4.625 | 4.97049477 | 6403.7 | 29123.9418 |
8 | 8 | 1088.1 | 4741.93980001 | 4.358 | 4.52039317 | 4.597 | 5.48416704 | 7491.8 | 33865.88160001 |
9 | 9 | 1155 | 4979.20500001 | 4.311 | 4.49242339 | 4.568 | 5.96149385 | 8646.8 | 38845.08660002 |
10 | 10 | 1226.1 | 5228.0904 | 4.264 | 4.46405584 | 4.539 | 6.44934333 | 9872.9 | 44073.17700002 |
Increasing Risk
How about if we wanted to run at 150% risk?
I’ve coded the script to accommodate this. Just change the script to run at:
risk_percentage = 150
Run it again, and the newly adjusted values come through, telling us that instead of using 440k USDT, we will risk across all trading pairs 150% of our total: 661k USDT:
The base order and safety order calculations look correct:
Order № | Deviation, % | Order | Price | Average price | Required price | Required change, % | Total | ||
---|---|---|---|---|---|---|---|---|---|
Size (1INCH) | Volume (USDT) | Size (1INCH) | Volume (USDT) | ||||||
BO | 0 | 536.9 | 2527.1883 | 4.707 | 4.707 | 4.787 | 1.69959634 | 536.9 | 2527.1883 |
1 | 1 | 1085.3 | 5056.41270001 | 4.659 | 4.67488657 | 4.754 | 2.03906417 | 1622.2 | 7583.60100001 |
2 | 2 | 1151.1 | 5308.87320001 | 4.612 | 4.64878455 | 4.727 | 2.49349522 | 2773.3 | 12892.47420002 |
3 | 3 | 1221.1 | 5574.3215 | 4.565 | 4.62317136 | 4.701 | 2.97918948 | 3994.4 | 18466.79570002 |
4 | 4 | 1295.5 | 5853.069 | 4.518 | 4.59741482 | 4.675 | 3.47498893 | 5289.9 | 24319.86470002 |
5 | 5 | 1374.6 | 6145.83660001 | 4.471 | 4.57134088 | 4.649 | 3.98121225 | 6664.5 | 30465.70130003 |
6 | 6 | 1458.7 | 6453.2888 | 4.424 | 4.54488257 | 4.622 | 4.4755877 | 8123.2 | 36918.99010003 |
7 | 7 | 1548 | 6775.596 | 4.377 | 4.5180108 | 4.594 | 4.9577336 | 9671.2 | 43694.58610003 |
8 | 8 | 1643.1 | 7114.623 | 4.33 | 4.49070725 | 4.567 | 5.4734411 | 11314.3 | 50809.20910003 |
9 | 9 | 1744.2 | 7470.40860001 | 4.283 | 4.46296417 | 4.538 | 5.95377072 | 13058.5 | 58279.61770004 |
10 | 10 | 1851.7 | 7843.80120001 | 4.236 | 4.43477746 | 4.51 | 6.46836638 | 14910.2 | 66123.41890005 |
AWS Lambda
Now, all that is needed for the Python script to work automatically, is to schedule it on a Linux machine as a cronjob or Windows machine as a scheduled task and let it run. But considering the script is using 3commas read/write credentials, it’s probably not the best idea to store these credentials on your PC and also, whenever your workstation goes down, the script would also stop running. So, we can do better.
Let’s leverage Amazon Cloud and their serverless, event-driven compute service called AWS Lambda, which lets us run the Python code as a backend service without provisioning or managing servers and allows us to trigger Lambda as a cron job through EventBridge.
Login into our AWS console and first wire things for lambda to run.
Create the following:
- VPC, unless you already have one
- Private Lambda subnet
- Public Lambda subnet
- NAT Gateway that Lambda will use
You can follow this article to do the above: https://aws.amazon.com/premiumsupport/knowledge-center/internet-access-lambda-function/
Now, let’s create a Lambda script, that will adjust our board automatically:
- Login to Lambda in AWS
- Create a new function, call it: 3Commas_Auto_Compounding
- Configure the VPC info:
- Security groups need to be configured in such a way that only outbound access is allowed on port 443, meaning your script can get out, but nothing can get to it.
- Configure the code config the way you want, also don’t forget to upload all dependencies as additional folders, for the Python script to run:
- As you can see above, I do not expose credentials in the code, instead, I leverage the environment variables and store them there as key/value pairs:
- Now we can create an AWS EventBridge cron trigger to run the script every 5 minutes:
And Voila! We’re done, the 3Commas base and safety order are recalculated and updated for us automatically every 5 minutes.
A simple cloud solution to a simple problem:
Enjoy :)