TickAtlas
Tutorial 10 min read · March 28, 2026

Using ATR for Dynamic Stop-Loss Placement

Learn how to use Average True Range (ATR) to set volatility-adjusted stop losses that adapt to market conditions, with full code examples.

CG
By the TickAtlas team

The Problem with Fixed Stop Losses

A 50-pip stop loss on EURUSD might work perfectly in a quiet Asian session but get triggered by normal noise during a London/New York overlap. Fixed stops ignore the current volatility regime, leading to either premature exits or unnecessarily wide risk exposure.

ATR solves this by measuring the average range of price movement over a period. When volatility expands, your stop widens. When it contracts, your stop tightens. The result is a stop loss that breathes with the market.

Adaptive

Adjusts to current volatility

Fewer

Premature stop-outs

Better

Risk-reward ratios

How ATR Works

True Range is the greatest of: (1) current high minus current low, (2) absolute value of current high minus previous close, or (3) absolute value of current low minus previous close. ATR is simply the moving average of True Range over N periods (typically 14).

True Range = max(
    High - Low,
    abs(High - Previous Close),
    abs(Low - Previous Close)
)

ATR(14) = SMA(True Range, 14)

Fetching ATR from the API

curl -H "X-API-Key: YOUR_API_KEY" \
  "https://tickatlas.com/v1/indicator?symbol=EURUSD&indicator=ATR_14&timeframe=H4"

Response:

json
{
  "success": true,
  "data": {
    "symbol": "EURUSD",
    "timeframe": "H4",
    "indicator": "ATR_14",
    "values": {
      "atr": 0.00387
    },
    "ohlcv": {
      "open": 1.0845,
      "high": 1.0871,
      "low": 1.0832,
      "close": 1.0858,
      "volume": 12340
    },
    "timestamp": "2026-03-28T12:00:00Z"
  }
}

An ATR of 0.00387 for EURUSD on H4 means the average bar moves about 38.7 pips. This is your building block for stop-loss calculation.

Python: ATR-Based Stop-Loss Calculator

python
import requests
from dataclasses import dataclass

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://tickatlas.com/v1"

@dataclass
class StopLossResult:
    symbol: str
    direction: str
    entry_price: float
    stop_loss: float
    take_profit: float
    atr_value: float
    risk_pips: float
    reward_pips: float

def calculate_atr_stop(
    symbol: str,
    timeframe: str,
    direction: str = "long",
    atr_multiplier: float = 2.0,
    rr_ratio: float = 1.5,
) -> StopLossResult:
    """Calculate ATR-based stop loss and take profit."""
    resp = requests.get(
        f"{BASE_URL}/indicator",
        headers={"X-API-Key": API_KEY},
        params={
            "symbol": symbol,
            "indicator": "ATR_14",
            "timeframe": timeframe,
        },
    )
    resp.raise_for_status()
    data = resp.json()["data"]

    atr = data["values"]["atr"]
    close = data["ohlcv"]["close"]
    stop_distance = atr * atr_multiplier

    if direction == "long":
        stop_loss = close - stop_distance
        take_profit = close + (stop_distance * rr_ratio)
    else:
        stop_loss = close + stop_distance
        take_profit = close - (stop_distance * rr_ratio)

    # Convert to pips (works for most forex pairs)
    pip_factor = 10000 if "JPY" not in symbol else 100
    risk_pips = stop_distance * pip_factor
    reward_pips = stop_distance * rr_ratio * pip_factor

    return StopLossResult(
        symbol=symbol,
        direction=direction,
        entry_price=close,
        stop_loss=round(stop_loss, 5),
        take_profit=round(take_profit, 5),
        atr_value=atr,
        risk_pips=round(risk_pips, 1),
        reward_pips=round(reward_pips, 1),
    )

# Example usage
result = calculate_atr_stop("EURUSD", "H4", "long", 2.0, 1.5)
print(f"Entry: {result.entry_price}")
print(f"Stop Loss: {result.stop_loss} ({result.risk_pips} pips)")
print(f"Take Profit: {result.take_profit} ({result.reward_pips} pips)")

Choosing the ATR Multiplier

The multiplier you choose depends on your trading style and the market conditions:

1.0x ATR -- Tight stops (scalping)

More stop-outs, but smaller losses per trade. Best for high-probability setups on lower timeframes.

1.5x -- 2.0x ATR -- Standard (day/swing trading)

The most common range. Gives enough room for normal price fluctuation while limiting risk.

2.5x -- 3.0x ATR -- Wide stops (position trading)

Allows trades to survive significant pullbacks. Requires smaller position sizes to manage risk.

Position Sizing with ATR

ATR-based stops naturally lead to ATR-based position sizing. If your stop is wider because volatility is high, you trade a smaller position to keep dollar risk constant:

python
def position_size(
    account_balance: float,
    risk_percent: float,
    atr_value: float,
    atr_multiplier: float,
    pip_value: float = 10.0,  # per standard lot
) -> float:
    """Calculate position size in lots."""
    risk_amount = account_balance * (risk_percent / 100)
    stop_pips = atr_value * atr_multiplier * 10000  # for non-JPY pairs
    lots = risk_amount / (stop_pips * pip_value)
    return round(lots, 2)

# Risk 1% of a $10,000 account
lots = position_size(10000, 1.0, 0.00387, 2.0)
print(f"Position size: {lots} lots")  # ~0.13 lots

Trailing Stop with ATR

You can also use ATR as a trailing stop. As the trade moves in your favor, trail the stop at a fixed ATR distance from the most favorable price:

class ATRTrailingStop:
    def __init__(self, atr_multiplier: float = 2.0):
        self.multiplier = atr_multiplier
        self.highest_price = None
        self.trailing_stop = None

    def update(self, current_price: float, atr: float) -> float:
        """Update trailing stop for a long position."""
        if self.highest_price is None:
            self.highest_price = current_price

        self.highest_price = max(self.highest_price, current_price)
        new_stop = self.highest_price - (atr * self.multiplier)

        if self.trailing_stop is None:
            self.trailing_stop = new_stop
        else:
            # Stop can only move up, never down
            self.trailing_stop = max(self.trailing_stop, new_stop)

        return self.trailing_stop

Related Reading

Try this with live data

Every account gets $2.50 in free PAYG credits. No card required — paste your API key and run the code above against live broker data.