TWAP Order Slicing with RSI-Based Entries and Exits
Summary
The strategy demonstrates time-weighted average price execution by splitting entries and exits into equal-sized portions spaced at fixed time intervals. It configures the number of slices and spacing, and uses an RSI indicator on a 15-minute timeframe to generate example signals: long entries below 45 and short entries above 55, provided volume is present. The entry and exit signal logic is presented as an example rather than a complete trading system.
Position adjustments wait for open orders to clear, then submit another entry slice until the target count is reached. Partial exits begin when RSI crosses the opposite threshold, and continue in timed slices until the position is closed. The code sets a stop loss and a minimal return target but provides no backtest or execution evidence. It is an illustrative implementation with important limits: it does not establish that the RSI rules are profitable, and actual fill sizes, timing, fees, liquidity, and exchange behavior can affect how closely execution follows the intended schedule.
Key ideas
- The strategy divides entries and exits into a configured number of equal time-based slices.
- RSI thresholds provide example long and short entry signals and conditions for starting partial exits.
- Position adjustments pause while an order remains open and observe a fixed interval between fills.
- The document provides implementation logic but no performance evidence for its signals or execution approach.
Tags
Full text
# TWAPStrategy.py
```py
from datetime import datetime, timedelta
import pandas as pd
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy
import talib.abstract as ta
class TWAPStrategy(IStrategy):
"""
TWAP (Time-Weighted Average Price) execution on both entry and exit.
this strategy breaks orders into equal time-based slices to minimize market impact
The entry and exit signals are examples and should be adapted to your strategy.
- twap_num_slices: desired number of execution slices.
- twap_interval_minutes: time between execution slices.
"""
timeframe = "15m"
stoploss = -0.10
minimal_roi = {"0": 0.02}
process_only_new_candles = True
startup_candle_count = 30
can_short = True
position_adjustment_enable = True
twap_num_slices = 10 # number of slices
twap_interval_minutes = 1 # interval between slices
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe["rsi"] = ta.RSI(dataframe)
return dataframe
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
dataframe.loc[
(dataframe["rsi"] < 45) & (dataframe["volume"] > 0),
"enter_long",
] = 1
# Short entry
dataframe.loc[
(dataframe["rsi"] > 55) & (dataframe["volume"] > 0),
"enter_short",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
return dataframe
def should_exit_partially(self, trade: Trade, current_time: datetime) -> bool:
"""
Determine whether the trade should be partially exited with slices.
This method is only intended to be called from strategy callbacks.
"""
dataframe, _ = self.dp.get_analyzed_dataframe(
trade.pair, self.timeframe
)
if dataframe.empty:
return False
last_candle = dataframe.iloc[-1]
rsi = last_candle["rsi"]
if trade.is_short:
return rsi < 45
return rsi > 55
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float | None, max_stake: float,
leverage: float, entry_tag: str | None, side: str,
**kwargs) -> float:
return proposed_stake / self.twap_num_slices
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: float | None, max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs
) -> float | None | tuple[float | None, str | None]:
if trade.has_open_orders:
return None
filled_entries = trade.select_filled_orders(trade.entry_side)
entry_slices_done = len(filled_entries)
filled_exits = trade.select_filled_orders(trade.exit_side)
exit_slices_done = len(filled_exits)
already_exiting = exit_slices_done > 0
if already_exiting or self.should_exit_partially(trade, current_time):
return self._next_exit_slice(trade, current_time, filled_exits, exit_slices_done)
if entry_slices_done < self.twap_num_slices:
return self._next_entry_slice(trade, current_time, filled_entries, entry_slices_done)
return None
def _next_entry_slice(self, trade: Trade, current_time: datetime,
filled_entries: list, slices_done: int
) -> float | None | tuple[float | None, str | None]:
last_fill_time = filled_entries[-1].order_filled_utc if filled_entries else trade.open_date_utc
next_slice_due_at = last_fill_time + timedelta(minutes=self.twap_interval_minutes)
if current_time < next_slice_due_at:
return None
stake_already_filled = sum(o.stake_amount_filled for o in filled_entries)
twap_total_stake = filled_entries[0].stake_amount_filled * self.twap_num_slices
remaining_stake = twap_total_stake - stake_already_filled
remaining_slices = self.twap_num_slices - slices_done
next_slice_stake = remaining_stake if remaining_slices <= 1 else remaining_stake / remaining_slices
if next_slice_stake < 0:
return None
return next_slice_stake
def _next_exit_slice(self, trade: Trade, current_time: datetime,
filled_exits: list, slices_done: int
) -> float | None | tuple[float | None, str | None]:
if slices_done >= self.twap_num_slices:
return None
last_fill_time = filled_exits[-1].order_filled_utc if filled_exits else current_time
next_slice_due_at = last_fill_time + timedelta(minutes=self.twap_interval_minutes)
if slices_done > 0 and current_time < next_slice_due_at:
return None
remaining_slices = self.twap_num_slices - slices_done
if remaining_slices <= 1:
return -trade.stake_amount
slice_stake = trade.stake_amount / remaining_slices
return -slice_stake
```Shown in full with attribution under the source's licence. Licence: GPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.