This strategy is an advanced Dollar Cost Averaging (DCA) simulator designed to replicate the logic of 3Commas algorithmic trading bots directly within TradingView. This streamlined version showcases the power of Pine Script in developing high-efficiency backtests. By deep-diving into the data before going live, users can stress-test their setups and avoid costly mistakes.
The following 3Commas configuration is assumed for this template:
Direction: Long | Order Type: Limit | Exchange: Binance (BTC/USDT
Initial Order Size: Set to 1000 USDT.
Entry Logic: Our custom TradingView signal triggers on an RSI bearish cross of the 35 level, initiating a Long DCA sequence on oversold conditions.
The Averaging orders logic for this template is configured as follows:
Deviation to open first averaging order: 1%
Averaging order size: 100 USDT
Deviation step multiplier: 1.5
Order size multiplier: 1.5
Averaging orders per trade: 11
Limit averaging orders placed on exchange: 11
Critical Note: Max amount for bot usage & Backtesting Accuracy
When configuring 3Commas, always prioritize the Max amount for bot usage parameter. This is essential to ensure your backtesting data remains realistic and avoids "illusory" results.
As shown in this setup, the Max amount for bot usage is approximately 8,500 USDT. This represents the maximum amount of funds the bot can trade. To maintain high-fidelity backtesting on TradingView, we have set the Initial Capital to 10,000 USDT.
By utilizing ~85% of the available equity (8,500 out of 10,000 USDT), the simulation closely mirrors real-world trading conditions.
If your Max amount for bot usage exceeds your account balance, you must adjust your configuration. Always align your bot settings with your specific trading goals and financial capacity to avoid liquidation or failed order execution.
🧠 Workflow Description
This strategy automates 3Commas-style Dollar Cost Averaging (DCA), operating exclusively on the Long side. The first order is triggered when the 7-period RSI crosses down the oversold threshold (default: 35), signaling a potential local bottom. Upon entry, the system simultaneously calculates and places 10 averaging orders via limit orders at progressively lower price levels to manage the position. The spacing between these orders is dynamic; it increases exponentially through a deviation multiplier, allowing the strategy to cover deep drawdowns effectively. Simultaneously, the volume of each subsequent purchase grows according to an amount multiplier, aggressively pulling the average entry price downward. The trade is closed either at a take profit target triggered once the total position equity reaches the set value or via a stop loss calculated from the initial entry price.
Backtesting Considerations & Performance Analysis
Despite the positive net profit shown in the strategy report, this specific configuration underperforms when compared to a simple Buy & Hold approach. In this scenario, a Buy & Hold investor who simply hold 10,000 USDT worth of the asset would have achieved a significantly higher return than the trader executing this DCA strategy. This indicates that while the bot is "profitable" in absolute terms, it is not capital-efficient under these specific market conditions.
❌ To keep this simulator streamlined and focused on core DCA logic, the current version does not include the following features:
Base Template Only: This is a foundational framework designed for educational and initial testing purposes.
No Leverage Backtesting: All calculations assume a 1x spot-trading margin (no liquidation or margin cost simulation).
No Short Selling: This version is strictly long-only.
Simplified DCA Settings: Advanced 3Commas parameters (such as Minimum Deviation Step or Non-linear Volume Scaling) are not included.
Fixed Order Count: The strategy is hardcoded to 11 total orders (1 Base Order + 10 Averaging Orders).
Standard Profit Logic: Take Profit % is calculated based on the Average Price, while Stop Loss % is anchored to the Initial Base Order price.
No Reinvestment (Compounding): The strategy uses a fixed position size and does not automatically reinvest profits into subsequent deals.
Feel free to swap the trigger logic or optimize the averaging settings to discover a configuration that outperforms a simple Buy & Hold strategy.
![]()
//@version=6
strategy(
"3Commas DCA Strategy Backtesting [The Quant Science]",
overlay = false,
default_qty_type = strategy.cash,
default_qty_value = 1000,
pyramiding = 100,
currency = currency.USDT,
initial_capital = 10000,
commission_type = strategy.commission.percent,
commission_value = 0.07,
slippage = 5,
process_orders_on_close = true,
close_entries_rule = "ANY"
)
// DATE RANGE FEATURE
start_date = input.int(title="D: ", defval=1, minval=1, maxval=31, inline = 'Start', group = "DATE PERIOD BACKTESTING")
start_month = input.int(title="M: ", defval=1, minval=1, maxval=12, inline = 'Start', group = "DATE PERIOD BACKTESTING")
start_year = input.int(title="Y: ", defval=2021, minval=1800, maxval=2100, inline = 'Start', group = "DATE PERIOD BACKTESTING")
end_date = input.int(title="D: ", defval=31, minval=1, maxval=31, inline = 'End', group = "DATE PERIOD BACKTESTING")
end_month = input.int(title="M: ", defval=12, minval=1, maxval=12, inline = 'End', group = "DATE PERIOD BACKTESTING")
end_year = input.int(title="Y: ", defval=2026, minval=1800, maxval=2100, inline = 'End', group = "DATE PERIOD BACKTESTING")
in_date_range = (time >= timestamp(syminfo.timezone, start_year, start_month, start_date, 0, 0)) and (time < timestamp(syminfo.timezone, end_year, end_month, end_date, 0, 0))
// BASE ORDER SIZE CONFIGURATION
qty_base = input.int(defval = 1000, minval = 0, step = 1, title = "Base order (USDT):", group = "STRATEGY", tooltip = "The Base Order is the first order the bot will create when starting a new trade.", inline = "base-order-size")
// RSI INDICATOR
lenght_period = input.int(title="Lenght", defval=7, minval=0, group = "START DEAL CONDITION", inline = "rsi")
trigger_rsi = input.int(title="Trigger RSI", defval=35, minval=0, group = "START DEAL CONDITION", inline = "rsi")
rsi = ta.rsi(close, lenght_period)
rsi_color_green = rsi <= trigger_rsi
rsi_color_red = rsi >= trigger_rsi
rsi_color = rsi_color_red ? #ff0000 : rsi_color_green ? #00ff00 : #00ff00
plot(rsi, title = "RSI", color = rsi_color, linewidth = 1)
hline(0, title = "RSI - Bottom line", color = color.rgb(0, 255, 8))
hline(100, title = "RSI - Top line", color = color.rgb(255, 39, 39))
hline(trigger_rsi, title = "RSI - Trigger value", color = color.rgb(72, 73, 73), linewidth = 2, linestyle = hline.style_dotted)
rsi_entry_condition = ta.crossunder(rsi, trigger_rsi)
// AVERAGING ORDERS
qty_dca = input.int(defval = 100, minval = 0, step = 1, title = "Averaging order size (USDT: ", group = "AVERAGING ORDERS", inline = "Header", tooltip = "Enter the amount of funds your averaging orders.", confirm = true)
DROP = input.float(defval = 1, minval = 0, title = "Price Deviation (% from initial order): ", step = 0.10, group = "AVERAGING ORDERS", inline = "Drop", tooltip = "Deviation to open first averaging order", confirm = true)
SAFETY_ORDER_VOLUME_SCALE = input.float(defval = 1.7, minval = 0, step = 0.1, maxval = 100, title = "Order size multiplier: ", group = "AVERAGING ORDERS", confirm = true)
STEP_SCALE_VALUE = input.float(defval = 4, minval = 0, maxval = 100, title = "Deviation step multiplier: ", step = 0.10, group = "AVERAGING ORDERS", confirm = true)
// TAKE PROFIT FEATURE
PROFIT = input.float(defval = 2.4, minval = 0, title = "Target profit (%): ", step = 0.10, group = "TAKE PROFIT", inline = "pov", tooltip = "Configure the percentage 'Take Profit' target the strategy will use to close successful trades, the strategy will automatically account for exchange fees.", confirm = true)
// STOP LOSS FEATURE
STOPLOSS = input.bool(defval = false, title = "", group = "STOP LOSS", inline = "SL")
STOPLOSSVALUE = input.float(defval = 99, minval = 0, title = "Stop loss (%): ", step = 0.10, group = "STOP LOSS", inline = "SL", tooltip = "This is the percentage that price needs to move in the opposite direction to your take profit target, at which point the strategy will execute a 'Market Order' on the exchange account to close the deal for a smaller loss than keeping the deal open. Please note, the 'Stop Loss' is calculated from the price the initial 'Base Order' was filled at on the exchange account and not the Dollar Cost Average price.")
// INITIALIZE VARIABLES
var float equity = 0
var float base_order_price = 0
var float dca1 = 0
var float dca2 = 0
var float dca3 = 0
var float dca4 = 0
var float dca5 = 0
var float dca6 = 0
var float dca7 = 0
var float dca8 = 0
var float dca9 = 0
var float dca10 = 0
var float exit_condition_long = 0
var float exit_condition_stoploss = 0
// STARTING DCA CONDITIONS
if (rsi_entry_condition and in_date_range and strategy.opentrades==0)
equity := strategy.equity
base_order_price := close
// DEFINE PRICE LEVELS
dca1 := (base_order_price - (base_order_price * (DROP * (STEP_SCALE_VALUE * 1))/ 100))
dca2 := (dca1 - (dca1 * (DROP * (STEP_SCALE_VALUE * 2)) / 100))
dca3 := (dca2 - (dca2 * (DROP * (STEP_SCALE_VALUE * 3)) / 100))
dca4 := (dca3 - (dca3 * (DROP * (STEP_SCALE_VALUE * 4)) / 100))
dca5 := (dca4 - (dca4 * (DROP * (STEP_SCALE_VALUE * 5)) / 100))
dca6 := (dca5 - (dca5 * (DROP * (STEP_SCALE_VALUE * 6)) / 100))
dca7 := (dca6 - (dca6 * (DROP * (STEP_SCALE_VALUE * 7)) / 100))
dca8 := (dca7 - (dca7 * (DROP * (STEP_SCALE_VALUE * 8)) / 100))
dca9 := (dca8 - (dca8 * (DROP * (STEP_SCALE_VALUE * 9)) / 100))
dca10 := (dca9 - (dca9 * (DROP * (STEP_SCALE_VALUE * 10)) / 100))
// DEFINE TAKE PROFIT EXIT
exit_condition_long := equity + (equity * PROFIT / 100)
// DEFINE STOP LOSS EXIT
if STOPLOSS==true
exit_condition_stoploss := base_order_price - (base_order_price * STOPLOSSVALUE / 100)
// PLACE ORDERS
strategy.entry(id = "Buy", direction = strategy.long, qty = qty_base/base_order_price, limit = base_order_price)
strategy.entry(id = "SO1", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 1) /dca1, limit = dca1)
strategy.entry(id = "SO2", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 2) /dca2, limit = dca2)
strategy.entry(id = "SO3", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 3) /dca3, limit = dca3)
strategy.entry(id = "SO4", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 4) /dca4, limit = dca4)
strategy.entry(id = "SO5", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 5) /dca5, limit = dca5)
strategy.entry(id = "SO6", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 6) /dca6, limit = dca6)
strategy.entry(id = "SO7", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 7) /dca7, limit = dca7)
strategy.entry(id = "SO8", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 8) /dca8, limit = dca8)
strategy.entry(id = "SO9", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 9) /dca9, limit = dca9)
strategy.entry(id = "SO10", direction = strategy.long, qty = qty_dca * (SAFETY_ORDER_VOLUME_SCALE * 10) /dca10, limit = dca10)
// EXIT CONDITIONS
stop_loss_target = ta.crossunder(close, exit_condition_stoploss)
take_profit_gain = ta.crossover(strategy.equity, exit_condition_long)
if (take_profit_gain and in_date_range)
strategy.close_all("Take Profit")
strategy.cancel("SO1")
strategy.cancel("SO2")
strategy.cancel("SO3")
strategy.cancel("SO4")
strategy.cancel("SO5")
strategy.cancel("SO6")
strategy.cancel("SO7")
strategy.cancel("SO8")
strategy.cancel("SO9")
strategy.cancel("SO10")
if (stop_loss_target and in_date_range)
strategy.close_all("Stop Loss")
strategy.cancel("SO1")
strategy.cancel("SO2")
strategy.cancel("SO3")
strategy.cancel("SO4")
strategy.cancel("SO5")
strategy.cancel("SO6")
strategy.cancel("SO7")
strategy.cancel("SO8")
strategy.cancel("SO9")
strategy.cancel("SO10")
// BANKRUPTCY CHECK
if strategy.equity < 0
runtime.error("Bankruptcy! The equity line of this backtesting is negative. Try the backtesting again using different parameters, or reduce or increase the test period.")