0bps

Strategy vault / Breakout

Dual directional Trading Strategy Based on Quantitative Indicators Bullish and Bearish Signals

This strategy is based on the bullish and bearish signals generated by the Donchain price channel indicator and the OBV oscillator quantitative indicator for dual-directional trading. It uses the price channel…

Author
ChaoZhang
Complexity
Intermediate · 202 lines
Parameters
17
Backtest bar
1d · swing
Tested on
BTC/USDT · futures

IMG

Overview

This strategy is based on the bullish and bearish signals generated by the Donchain price channel indicator and the OBV oscillator quantitative indicator for dual-directional trading. It uses the price channel indicator to judge price breakouts and pullbacks, combined with quantitative indicators to determine bullish and bearish strength, to generate trading signals.

Strategy Principles

  1. Use the Donchain price channel indicator to determine the upper and lower price channels. The upper channel is calculated from the highest price and the lower channel from the lowest price.

  2. Construct an OBV oscillator using the OBV quantitative indicator and EMA indicator to determine bullish and bearish strength. An oscillator value greater than 0 indicates bullish strength exceeds bearish strength, and vice versa if less than 0.

  3. A long signal is generated when the price breaks through the upper channel and the oscillator is greater than 0; A short signal is generated when the price breaks through the lower channel and the oscillator is less than 0.

  4. Close long positions when prices pull back to the lower channel; Close short positions when prices pullback to the upper channel.

Advantages of the Strategy

  1. Using price channels to determine trends avoids being misled by fluctuating markets.

  2. Incorporating quantitative indicators to judge bullish and bearish strength ensures trade direction is consistent with market strength.

  3. Adopting dual-directional trading allows profits whether the market rises or falls.

  4. Setting stop loss strategies effectively manages risks.

Risks of the Strategy

  1. Improper parameter settings of price channels may lead to overly loose or narrow channels, missing trading opportunities or generating wrong signals.

  2. Improper indicator parameter settings may also lead to delayed or premature signal generation.

  3. Sudden abnormal volatile moves from events may trigger stop losses leading to losses.

  4. Dual-directional trading requires simultaneously managing both long and short positions making it more difficult to operate.

Directions for Strategy Optimization

  1. Optimize price channel parameters to find optimal combinations.

  2. Test and optimize OBV oscillator parameters to ensure timely and accurate judgments of bullish/bearish strength.

  3. Consider incorporating other indicators like MACD, KD etc. to improve signal accuracy.

  4. Test different stop loss methods e.g. tracking stops, percentage stops etc.

  5. Test different products to find which best suits the strategy.

Summary

Overall this is a clear, easy-to-understand dual-directional trading strategy incorporating price action and quantitative indicators to determine market trends and strength. There is also ample room for optimization through parameter tuning and indicator combinations for further improvements to stability and profitability.

Parameters

NameDefaultWhat it controls
v_input_1_close0Source: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4
v_input_2trueUse Volume Filter
v_input_320Volume Filter - Length
v_input_41.2Volume Filter - Multiplier
v_input_5trueUse Oscillator
v_input_640Oscillator - Signal Length
v_input_765Channel - Slow Length
v_input_870Channel - Fast Length Percent
v_input_90Trade - Direction: Both | trade_long | trade_short
v_input_102Trade - Leverage
v_input_117.5Trade - Stop Loss %
v_input_125Trade - Trail Stop Threshold %
v_input_135Trade - Trail Stop Minimum %
v_input_14100Trade - Risk %
v_input_152019Test - Year
v_input_16trueTest - Month
v_input_17trueTest - Day

Source Pine Script

/*backtest
start: 2022-12-06 00:00:00
end: 2023-12-12 00:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ahancock

//@version=4
strategy(
     title = "Hancock - Filtered Volume OBV OSC [Strategy]",
     initial_capital = 1000,
     overlay = false,
     commission_type = strategy.commission.percent,
     commission_value= 0.075)

// Inputs
source = input(close, title = "Source", type = input.source)

use_volume_filter     = input(true, title = "Use Volume Filter", type = input.bool)
vol_filter_length     = input(20, title = "Volume Filter - Length", type = input.integer, minval = 1)
vol_filter_multiplier = input(1.2, title = "Volume Filter - Multiplier", type = input.float, minval = 0.1, step = 0.1)

use_osc    = input(true, title = "Use Oscillator", type = input.bool)
osc_length = input(40, title = "Oscillator - Signal Length", type = input.integer, minval = 1)

channel_length = input(65, title = "Channel - Slow Length", minval = 5, maxval = 200, step = 5)
channel_percent = input(70, title = "Channel - Fast Length Percent", minval = 5, maxval = 100, step = 5)

trade_both = "Both", trade_long = "Long", trade_short = "Short"
trade_direction       = input("Both", title = "Trade - Direction", options = [trade_both, trade_long, trade_short])
trade_leverage        = input(2, title = "Trade - Leverage", type = input.integer, minval = 1, maxval = 100)
trade_stop            = input(7.5, title = "Trade - Stop Loss %", type = input.float, minval = 0.5, step = 0.5, maxval = 100)
trade_trail_threshold = input(5, title = "Trade - Trail Stop Threshold %", type = input.float, minval = 0.5, step = 0.5, maxval = 100)
trade_trail           = input(5, title = "Trade - Trail Stop Minimum %", type = input.float, minval = 0.5, step = 0.5, maxval = 100)
trade_risk            = input(100, title = "Trade - Risk %", type = input.integer, step = 1, minval = 1, maxval = 100)

test_year   = input(2019, "Test - Year", type = input.integer, minval = 1970, maxval = 2222) 
test_month  = input(01, "Test - Month", type = input.integer, minval = 1, maxval = 12)
test_day    = input(01, "Test - Day", type = input.integer,  minval = 1, maxval = 31)

// Functions
get_round(value, precision) => round(value * (pow(10, precision))) / pow(10, precision)

get_obv(values, filter_length, filter_multiplier, use_filter, osc_length, use_osc) => 
    threshold = abs(avg(volume, filter_length) - (stdev(volume, filter_length) * filter_multiplier))

    obv = 0.0
    if (use_filter and volume < threshold)
        obv := nz(obv[1])
    else
        obv :=  nz(obv[1]) + sign(change(values)) * volume
        
    use_osc ? (obv - ema(obv, osc_length)) : obv

get_dc(high_values, low_values, length) =>

    top = highest(high_values, length)
    bot = lowest(low_values, length)
    mid = bot + ((top - bot) / 2)
    
    [top, mid, bot]

get_dcs(high_values, low_values, length, length_percent) => 
    
    slow_length = length
    fast_length = slow_length * length_percent / 100

    [slow_top, slow_mid, slow_bot] = 
         get_dc(high_values, low_values, slow_length)
         
    [fast_top, fast_mid, fast_bot] = 
         get_dc(high_values, low_values, fast_length)
    
    [slow_top, slow_mid, slow_bot, fast_top, fast_mid, fast_bot]

// Strategy
obv = get_obv(
         source, 
         vol_filter_length, 
         vol_filter_multiplier, 
         use_volume_filter,
         osc_length,
         use_osc)

[slow_top_price, _, slow_bot_price, fast_top_price, _, fast_bot_price] = 
     get_dcs(high, low, channel_length, channel_percent)

[slow_top_obv, _, slow_bot_obv, fast_top_obv, _, fast_bot_obv] = 
     get_dcs(obv, obv, channel_length, channel_percent)

enter_long_price  = high > slow_top_price[1]
exit_long_price   = low  < fast_bot_price[1]
enter_short_price = low  < slow_bot_price[1]
exit_short_price  = high > fast_top_price[1]

enter_long_obv  = obv > slow_top_obv[1] and (use_osc ? obv > 0 : true)
enter_short_obv = obv < fast_bot_obv[1] and (use_osc ? obv < 0 : true)
exit_long_obv   = obv < slow_bot_obv[1]
exit_short_obv  = obv > fast_top_obv[1]

// Trade Conditions
can_trade = true

enter_long_condition = enter_long_obv and enter_long_price
exit_long_condition  = exit_long_obv  and exit_long_price

enter_short_condition = enter_short_obv and enter_short_price 
exit_short_condition  = exit_short_obv  and exit_short_price

position_signal = 0
position_signal := 
     enter_long_condition ? 1 :
     enter_short_condition ? -1 :
     exit_long_condition or exit_short_condition ? 0 :
     position_signal[1]

// Positions
test_time   = timestamp(test_year, test_month, test_day, 0, 0)

if (time >= test_time and strategy.opentrades == 0)
    contracts = get_round((strategy.equity * trade_leverage / close) * (trade_risk / 100), 4)
    
    if (trade_direction == trade_both or trade_direction == trade_long)
        strategy.entry(
             "LONG", 
             strategy.long, 
             qty = contracts,
             when = enter_long_condition)
             
    if (trade_direction == trade_both or trade_direction == trade_short)
        strategy.entry(
             "SHORT", 
             strategy.short, 
             qty = contracts,
             when = enter_short_condition)

in_long  = strategy.position_size > 0
in_short  = strategy.position_size < 0

float long_high = na 
float short_low = na

long_high := in_long ? high >= nz(long_high[1], low) ? high : long_high[1] : na
short_low := in_short ? low <= nz(short_low[1], high) ? low : short_low[1] : na

long_change  = abs(((long_high - strategy.position_avg_price) / strategy.position_avg_price) * 100)
short_change = abs(((short_low - strategy.position_avg_price) / strategy.position_avg_price) * 100)

threshold_difference = (strategy.position_avg_price / trade_leverage) * (trade_trail_threshold / 100)

long_trail_threshold  = in_long ? strategy.position_avg_price + threshold_difference : na
short_trail_threshold = in_short ? strategy.position_avg_price - threshold_difference : na

long_trail = in_long and long_high > long_trail_threshold ? 
     long_high - (long_high / trade_leverage) * (trade_trail / 100) : na
short_trail = in_short and short_low < short_trail_threshold ? 
     short_low + (short_low / trade_leverage) * (trade_trail / 100) : na

stop_difference = (strategy.position_avg_price / trade_leverage) * (trade_stop / 100)

long_stop  = in_long ? long_high > long_trail_threshold ? long_trail : strategy.position_avg_price - stop_difference : na
short_stop = in_short ? short_low < short_trail_threshold ? short_trail : strategy.position_avg_price + stop_difference : na

strategy.exit("S/L", "LONG",
     stop = long_stop,
     qty = abs(get_round(strategy.position_size, 4)))
         
strategy.exit("S/L", "SHORT", 
     stop = short_stop, 
     qty = abs(get_round(strategy.position_size, 4)))

strategy.close_all(when = abs(change(position_signal)) > 0)

// Plots
plotshape(enter_long_condition, "Enter Long", shape.diamond, location.top, color.green)
plotshape(exit_long_condition, "Exit Long", shape.diamond, location.top, color.red)

plotshape(enter_short_condition, "Enter Short", shape.diamond, location.bottom, color.green)
plotshape(exit_short_condition, "Exit Short", shape.diamond, location.bottom, color.red)

color_green = #63b987
color_red   = #eb3d5c

hline(use_osc ? 0 : na)
plot(use_osc ? obv : na, color = color.silver, style = plot.style_area, transp = 90)
plot(obv, color = color.white, style = plot.style_line, linewidth = 2, transp = 0)

plot_slow_top = plot(slow_top_obv, color = color_green, linewidth = 2, transp = 60)
plot_slow_bot = plot(slow_bot_obv, color = color_green, linewidth = 2, transp = 60)
fill(plot_slow_top, plot_slow_bot, color = color_green, transp = 90)

plot_fast_top = plot(fast_top_obv, color = color_red, linewidth = 2, transp = 60)
plot_fast_bot = plot(fast_bot_obv, color = color_red, linewidth = 2, transp = 60)
fill(plot_fast_top, plot_fast_bot, color = color_red, transp = 90)



Published by ChaoZhang on the FMZ strategy square · last modified 2023-12-13. Collected in the Quant Trading Vault. Unvalidated research: re-test with your own data and costs before trusting it.

Build this with AgenKit

A spec is not a trading system. Paste this into your coding agent and AgenKit runs a specialist team — architecture, test-first build, risk gates, review — with paper trading on by default.

in Claude Code, Cursor, Codex & 4 more
/agenkit build a production-grade trading bot with a backtest engine, risk controls and paper trading by default from strategies/Dual-directional-Trading-Strategy-Based-on-Quantitative-Indicators-Bullish-and-Bearish-Signals.md
  1. npx agenkit install engineering-kit in your project
  2. Paste the command into your agent and approve each phase
  3. Get tests, backtest, risk controls and a paper-trading switch

Understand it first

Before you run this, read the lesson behind the idea and how to backtest it honestly. Specs like this one are the survivors of an unknown number of trials; deflate their results accordingly.

Related specs

esc

Try , , or .

↑↓ move↵ open