"Breakeven" mode for dynamic DCA volume is not wired up (behaves identically to Take Profit)

Product: Gainium DCA bot (also affects the Combo bot — shared DCA engine)
Area: Safety-order volume → “Required changed based on” (dcaVolumeRequiredChangeRef)
Severity: Functional — the UI offers an option the engine silently ignores (falls back to another mode)

Summary

When DCA volume is set to “Required change” mode, the “Required changed based on” dropdown offers three values: Take Profit, Average price, and Breakeven. The engine only implements two of them. Breakeven is never handled and silently falls into the Take-Profit branch — so Breakeven == Take Profit. Selecting Breakeven has no effect on safety-order sizing.

Expected behavior

When Breakeven is selected, each safety order should be sized to deploy the maximum available DCA volume (the “Max volume per DCA” field, dcaVolumeMaxValue) in order to pull the average entry as close to the current price (i.e. break-even) as that cap allows.

In this mode the “Required change” field is meaningless — there is no target %, only the volume capped by “Max volume per DCA”. The “Required change” field should therefore be hidden/ignored when Breakeven is selected.

(Rationale: an average exactly equal to the current price would require infinite volume, so “Breakeven” is best defined as the finite approximation — deploy as much volume as the cap permits.)

Actual behavior

  • The DcaVolumeRequiredChangeRef enum only contains tp and avg — there is no breakeven value.
  • The volume calculation branches only on avg; everything else (including breakeven and tp) falls into the else branch, which sizes the order so the take-profit price reaches the required-change %. So Breakeven inherits the exact TP sizing.
  • The order-form preview uses the same two-way branch, so the preview shows the same wrong (TP) sizes.
  • A strict config validator would also reject breakeven, since the value isn’t part of the enum.

Steps to reproduce

  1. Create a DCA bot; set DCA volume based onRequired change.
  2. Set Required changed based onBreakeven.
  3. Set Required change and Max volume per DCA to any values.
  4. Check the order preview / backtest: the safety-order sizes are identical to Take Profit. Max volume per DCA is not used as the target, and Required change still drives the size exactly as in TP mode.

Root cause

The third dropdown value (breakeven) is not represented in the data model (enum), the engine, the preview, or the validator. There is no dedicated calculation branch for Breakeven, so it defaults to the Take-Profit formula.

Suggested fix

  1. Add the enum value breakeven = 'breakeven' to DcaVolumeRequiredChangeRef (both the backtester types and the frontend types).
  2. Engine branch: when breakeven, set the order size to Max volume per DCA (dcaVolumeMaxValue); if no cap is set, fall back to the base order size. Skip the percentage formula and the quote conversion (the cap is already expressed in the order-size unit).
  3. Preview: apply the same branch in the order-preview code so the preview matches the engine.
  4. Validator: accept breakeven as a valid value.
  5. UI: hide the “Required change” field when Breakeven is selected; add a tooltip on “Required changed based on” explaining all three modes.

Net effect

Breakeven becomes a genuine third mode (“deploy max allowed volume to get closest to break-even”), and the now-redundant “Required change” field is no longer required in that mode.

The average price of a deal is the price where you can close the deal without gains and losses. The break-even price is the same if there’s only a single deal to consider. If there are other deals with losses the break-even price is for deals with direction long higher than the average price. If there are gains it is below the average price of the current deal. The take profit can be based either on the average or the break-even price.

The required change, as I would define, it is the relative distance to those prices after the DCA order was executed. So if the required change to the average price was 10% before the DCA, it could be configured to be 5% after the deal.

Thanks both — you’ve pinpointed it correctly: “Breakeven” is currently offered in the dropdown but isn’t wired into the sizing logic, so today it resolves to the same result as “Take Profit.” That’s a real gap and it’s on our list to fix. The one thing we want to lock down first is the exact intended behavior, since there are two reasonable interpretations here: (a) treat Breakeven as a distinct reference price that the “Required change” % is measured against (analogous to Average price / Take Profit), or (b) treat it as a “deploy the maximum allowed DCA volume” mode where “Required change” no longer applies. These size safety orders quite differently. If you have a preference (and a concrete example of the sizing you’d expect), that’ll help us implement the right one rather than guessing.

The required change as I understand it should always define the percential distance after a DCA order. Thus the DCA order should automatically scaled to achieve it. In my opinion average and break-even price have the same meaning if we consider each DCA deal on its own. Break-even would require a meta view over all deals of the current DCA bot. Then break-even could take previous gains and losses of other deals of the current DCA bot into account. I don’t think that break-even should be misused to throw all remaining funds for the current deal onto it to bring the average price of the deal as close to the current price as those funds allow. This should be another option for a final DCA step only.

Example

PnL of previous deals -$10
A new deal starts with size $100 at price $50
The average price is $50 but the break-even $55 as the $100 have to recover +10% before the deal brings the bot into overall profit again. Of course, the take profit should then be also based on the break-even price.

I already filed several bug reports aka feature requests about “required change”. Feel free to look them up.


@d_yo_r — thank you for the correction, it made me go back and read the actual engine, and you were right on both points. “Required change = the relative distance to the reference price after the DCA order” is literally what the avg branch computes, and your note that break-even sits below the average when there are gains matches Gainium’s own breakevenPrice() — it folds realized profit (freeTotalProfit) into the calculation, so booked profit pulls break-even below the raw average. I’d initially described break-even as just “average + fees”; your framing is the accurate one.

@claus — here’s the full change set for interpretation (a): breakeven as a distinct reference price, structurally identical to avg but targeting the break-even price (average adjusted for round-trip fees) instead of the raw average. Four touch points plus tooltip copy.

1. Enum — add the value in both type sources

@gainium/backtestersrc/types.ts (~L749) and main-dash-shsrc/types/index.ts (~L1708):

export enum DcaVolumeRequiredChangeRef {
  tp = 'tp',
  avg = 'avg',
  breakeven = 'breakeven',   // ← add
}

2. Engine — new branch in the useVolumeChange block

The branch is the same algebra in both repos; only the fee source differs, so I’ve split it out.

Common shape (insert before the avg check; leave avg and the else/TP branch untouched):

if (settings.dcaVolumeRequiredChangeRef === DcaVolumeRequiredChangeRef.breakeven) {
  // Break-even reference: same solve as `avg`, but the target average is the
  // break-even target divided by the fee factor. Uses the engine's own
  // round-trip fee convention (fee * 2), matching the SL/break-even math.
  const beFactor = 1 + (long ? 1 : -1) * roundTripFee
  const newBreakeven = price * (1 + (volumeChangeValue / 100) * (long ? 1 : -1))
  const newAvg = newBreakeven / beFactor
  const deno = long ? price - newAvg : newAvg - price
  orderSize = (newAvg * base - quote) / deno
} else if (/* avg … unchanged */) { … } else { /* TP … unchanged */ }

// unchanged — breakeven goes through the SAME conversion + cap as avg/tp:
if (orderSizeType === OrderSizeTypeEnum.quote) orderSize *= price
orderSize = Math.min(maxVolumeSize, orderSize)

roundTripFee per repo:

  • backtester/src/helper/dcaBotFunctions.ts (~L621, inside createOrders) — synchronous, use the existing property directly:

    const roundTripFee = this.userFee * 2   // same convention as getSLOrder(), L792
    
    
  • main-app’s dcaHelper.ts (~L13066) — the fee comes from this.getUserFee(symbol), which is async and returns { taker, maker }. Fetch it once before the order loop (don’t await inside the per-order loop):

    const roundTripFee = ((await this.getUserFee(symbol.symbol))?.taker ?? 0) * 2
    
    

    (Same ?.taker * 2 pattern you already use at ~L14011 / L14474 / L2384. Please confirm the symbol-string field in that scope — symbol.symbol vs settings.pair[0].)

Sanity check for both: roundTripFee = 0beFactor = 1 ⇒ result degenerates to avg, the correct zero-fee limit.

3. Frontend order-preview — same branch

main-dash-sh/src/utils/bots/dca/example-orders-core.ts (~L911, the useVolumeChange block) — add the identical breakeven branch so the preview matches the engine. Use whatever taker-fee value the preview already has (or accept it as a prop). Important: unlike a “max-volume” shortcut, this branch must keep the orderSizeType === quote conversion and the Math.min(maxVolumeSize, …) cap (same as avg).

4. Validator — nothing extra needed

main-app/src/server/v2/validators/bots/config.ts (~L1774) uses enum: Object.values(DcaVolumeRequiredChangeRef) — once step 1 lands, breakeven is accepted automatically. No explicit string literal required.

5. UI — keep the “Required change” field visible for all three

Don’t hide/disable “Required change” for breakeven — it’s meaningful for all three references (how far price must move to reach that price after the SO). Setting it toward 0 naturally deploys maximum volume, clamped by “Max volume per DCA” via the existing Math.min — so a separate “max-volume” mode isn’t needed; it’s just the limit case.

6. Tooltip copy

Header — “Required changed based on”:

How far the price must still move, after this safety order fills, to reach the chosen reference — each safety order is sized to hit that distance. A smaller “Required change” means a larger safety order.
Take Profit — your target sell price; the most demanding target (it sits above your average by the TP %), so it needs the most volume
Average — your average entry price; the nearest target, so it needs the least volume
Breakeven — your net-zero exit price; just above the average, so its volume lands between the two

Info icon on “Take Profit”:

Take-profit price: your average entry plus the bot’s Take Profit %. Because it sits furthest above the current price, bringing it within the Required change % takes the most volume of the three.

Info icon on “Average”:

Average entry price: the volume-weighted price of every filled order (base order + safety orders), before fees. It’s the nearest of the three targets, so it needs the least volume to bring within the Required change %.

Info icon on “Breakeven”:

Break-even price: the price at which closing the whole position nets exactly zero — your average entry adjusted for round-trip fees/funding and for any profit already booked in this deal. It sits just above your average (or below it once profit is booked), so its volume lands between Average and Take Profit.

That’s the whole thing. Happy to open a PR against the -sh repos with exactly these diffs.

(Why does it seem as if I am the only human in this chat? What’s the recipe for pan cake?)

Thank you both for the effort, but this is one feature I wasn’t planning bringing to V2, so the actual bug is that it was on the UI at all.

This feature has been rarely used in live accounts, and it has generated more support effort than it was worth. There is also a problem with the math, people have a hard time grasping the concept and most people set too optimistic required changes, which invariably result in ridiculous amount of money required as each DCA grows exponentially. We already have a nice DCA overview calculator that tells you exactly what the required change after each DCA will be, so it’s a matter of adjusting the volume and volume scale, avoiding the complexity.

@d_yo_r, your explanations were genuinely helpful in understanding the intended meaning of breakeven. Before your comments, there were no clear descriptions of how breakeven was supposed to behave, and because the code path wasn’t wired at all, it was difficult to infer its purpose from the implementation.

To get to the bottom of why breakeven behaved exactly like take‑profit, I analyzed the source code with the assistance of an AI tool — simply to speed up the process of locating the missing branch. The previous message was intentionally written in a way that an AI could interpret and translate directly into code changes. It wasn’t meant as a conceptual argument, nor as an attempt to impress anyone. The goal was simply to produce a fix quickly so the whole community could benefit.

After the developers’ clarifications, it’s now clear that breakeven was never meant to be part of the V2 frontend in its current form. And I fully agree with your definition: breakeven should reflect the true net‑zero exit price, including all realized gains or losses, fees, and funding — the point where the bot ends up at ±0 overall.

For context: I’ve been working with trading bots for many years and often modify parts of the logic for my own use. While experimenting with a dynamic DCA setup for Combo bots, I ran into this issue. Even when using AI tools, there is still a lot of manual work involved in verifying the output — AI needs very explicit instructions to produce reliable code, and that shaped the way I wrote the earlier message.

From my perspective, many misunderstandings around dynamic DCA come from insufficient help texts in the UI, which is why I suggested more precise explanations.

I hope it’s alright if I continue reporting issues whenever I encounter them. My intention is simply to contribute constructively — nothing more.

1 Like

Thanks for the thoughtful follow-up — and yes, please absolutely keep reporting issues whenever you run into them. Constructive, detailed reports like this are exactly what helps us improve the platform, and the back-and-forth here was valuable even though the outcome is that “Breakeven” wasn’t meant to be part of dynamic DCA volume in V2 as it currently stands. Your point about the in-UI help text for dynamic DCA is well taken — clearer explanations there would head off a lot of the confusion around required-change sizing, and that’s useful feedback regardless. Thanks again for taking the time, and for reporting it in good faith.

I think that your break-even interpretation is still wrong. All levels should recover the fees be it average, break-even or take profit.

If we look at long deals no DCA order can move the average price to equal or even below the current price.

If the bots PnL is negative and we want to take it into account then we get this price order

current < average < break-even < take profit

or if we don’t it can also be

current <= average < take profit <= break-even

If the PnL is positive we get those

current <= break-even < average < take profit

or even

break-even <= current <= average < take profit

That’s why we have to be careful to choose a required change that doesn’t try something impossible like moving close or exactly to or even on the other side of the current price which wouldn’t work but burn all reserved funds immediately.