Swing trading strategy in Python

Swing trading captures multi-day moves, here is a trend-filtered swing strategy in Python, backtested with real costs on the Rust core.

What is swing trading?

Swing trading sits between day trading and long-term investing: positions are held from a few days to a few weeks to capture a single 'swing' in price. It needs less screen time than intraday trading and trades far more than buy-and-hold.

A robust swing approach combines a trend filter with a momentum gate: only trade in the direction of the larger trend, and avoid buying into exhausted moves. That keeps you in confirmed up-swings and out of choppy, range-bound conditions.

The logic

We define the trend with a 50-bar EMA on daily bars and only consider longs while price trades above it. This filters out the directionless markets that punish swing entries.

The rule is stateless and long-only: hold a full position while price is above the 50-EMA and the 14-period RSI sits below 60, and step aside in downtrends or once momentum runs hot. A stop-loss caps the downside on a failed swing.

swing trading in Python

Here is the full strategy in the Manifold-BT expression DSL. It imports manifoldbt, builds the signal, configures realistic execution, and runs the backtest against Rust.

swing_trading.py
import manifoldbt as mbt
from manifoldbt.indicators import close, ema, rsi
from manifoldbt.helpers import time_range, Slippage, Interval

# Daily trend filter (50-EMA) + momentum gate (14-RSI)
trend = ema(close, 50)
r = rsi(close, 14)
uptrend = close > trend

# Long while the trend is up and momentum is not yet overheated
strategy = (
    mbt.Strategy.create("swing")
    .signal("trend", trend)
    .signal("rsi", r)
    .size(mbt.when(uptrend & (r < 60.0), 1.0, 0.0))
    .stop_loss(pct=6.0)
)

start, end = time_range("2020-01-01", "2026-01-01")
config = mbt.BacktestConfig(
    universe={"binance": ["BTCUSDT"]},
    time_range_start=start,
    time_range_end=end,
    bar_interval=Interval.days(1),
    initial_capital=10_000,
    fees=mbt.FeeConfig.binance_perps(),
    slippage=Slippage.fixed_bps(2),
    warmup_bars=50,
)
store = mbt.ingest(provider="binance", symbol="BTCUSDT", symbol_id=1,
                   interval="1d", start="2020-01-01T00:00:00Z",
                   end="2026-01-01T00:00:00Z")
result = mbt.run(strategy, config, store)
print(result.summary())
mbt.plot.tearsheet(result, show=True)

Backtest it with Manifold-BT

On daily bars the strategy trades only a handful of times a year, so fees are negligible and the result is driven by how well the trend filter keeps you on the right side of the major moves.

Sweep the EMA length and the RSI gate together. Swing systems are few-trade, so judge robustness across a wide grid rather than chasing the single best pair, which tells you little.

Backtest configuration
Universe{"binance": ["BTCUSDT"]}
Bar interval1d
FeesFeeConfig.binance_perps()
Slippagefixed 2 bps
Warmup50 bars
Sample tearsheet (illustrative, not a forecast)
Total return+74.6%
Sharpe1.18
Max drawdown-22.1%
Win rate52%
Trades57

Frequently asked questions

Is swing trading profitable?

It can be, when a trend filter keeps you on the right side of the market and costs stay small relative to the multi-day moves you capture. A realistic backtest is the only way to confirm the edge survives fees and slippage.

Keep reading

Run your first backtest

Install Manifold-BT and reproduce the backtest above in seconds. The Rust core runs years of bars sub-second so you can sweep parameters instead of waiting.

$pip install manifoldbt