mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-16 01:28:05 +00:00
125 lines
5.3 KiB
Plaintext
125 lines
5.3 KiB
Plaintext
// Licensed under the Apache License, Version 2.0
|
|||
|
|
// © mihakralj
|
||
|
|
//@version=6
|
||
|
|
indicator("Parabolic SAR Extended (SAREXT)", "SAREXT", overlay=true)
|
||
|
|
|
||
|
|
//@function Parabolic SAR Extended — asymmetric PSAR with separate long/short AF parameters,
|
||
|
|
// auto-detect or forced initial direction, and an offset applied on reversal.
|
||
|
|
// Output is sign-encoded: positive SAR = long mode, negative SAR = short mode.
|
||
|
|
//@param start_value Initial direction: >0 force long, <0 force short, 0 auto-detect from DM
|
||
|
|
//@param offset_on_reverse Gap added to SAR on reversal (default 0)
|
||
|
|
//@param af_init_long Initial AF for long positions (default 0.02)
|
||
|
|
//@param af_long AF increment per new EP in long (default 0.02)
|
||
|
|
//@param af_max_long Maximum AF for long (default 0.20)
|
||
|
|
//@param af_init_short Initial AF for short positions (default 0.02)
|
||
|
|
//@param af_short AF increment per new EP in short (default 0.02)
|
||
|
|
//@param af_max_short Maximum AF for short (default 0.20)
|
||
|
|
//@returns Sign-encoded SAR value (positive = long, negative = short)
|
||
|
|
//@reference J. Welles Wilder Jr., "New Concepts in Technical Trading Systems" (1978)
|
||
|
|
//@optimized O(1) per bar, state-machine with asymmetric acceleration factors
|
||
|
|
sarext(simple float start_value = 0.0, simple float offset_on_reverse = 0.0,
|
||
|
|
simple float af_init_long = 0.02, simple float af_long = 0.02, simple float af_max_long = 0.20,
|
||
|
|
simple float af_init_short = 0.02, simple float af_short = 0.02, simple float af_max_short = 0.20) =>
|
||
|
|
if af_init_long <= 0 or af_long <= 0 or af_max_long <= af_init_long
|
||
|
|
runtime.error("Long AF parameters invalid")
|
||
|
|
if af_init_short <= 0 or af_short <= 0 or af_max_short <= af_init_short
|
||
|
|
runtime.error("Short AF parameters invalid")
|
||
|
|
if offset_on_reverse < 0
|
||
|
|
runtime.error("Offset must be >= 0")
|
||
|
|
|
||
|
|
var bool is_long = true
|
||
|
|
var float sar = low
|
||
|
|
var float ep = high
|
||
|
|
var float af = af_init_long
|
||
|
|
|
||
|
|
if bar_index == 0
|
||
|
|
// Bar 0: collect first bar data
|
||
|
|
is_long := true
|
||
|
|
sar := high
|
||
|
|
ep := low
|
||
|
|
af := af_init_short
|
||
|
|
float(na)
|
||
|
|
else if bar_index == 1
|
||
|
|
// Bar 1: determine initial direction
|
||
|
|
if start_value > 0
|
||
|
|
is_long := true
|
||
|
|
sar := math.min(low[1], low)
|
||
|
|
ep := math.max(high[1], high)
|
||
|
|
af := af_init_long
|
||
|
|
else if start_value < 0
|
||
|
|
is_long := false
|
||
|
|
sar := math.max(high[1], high)
|
||
|
|
ep := math.min(low[1], low)
|
||
|
|
af := af_init_short
|
||
|
|
else
|
||
|
|
// Auto-detect from DM
|
||
|
|
float plus_dm = high - high[1]
|
||
|
|
float minus_dm = low[1] - low
|
||
|
|
if plus_dm > minus_dm and plus_dm > 0
|
||
|
|
is_long := true
|
||
|
|
sar := math.min(low[1], low)
|
||
|
|
ep := math.max(high[1], high)
|
||
|
|
af := af_init_long
|
||
|
|
else
|
||
|
|
is_long := false
|
||
|
|
sar := math.max(high[1], high)
|
||
|
|
ep := math.min(low[1], low)
|
||
|
|
af := af_init_short
|
||
|
|
is_long ? sar : -sar
|
||
|
|
else
|
||
|
|
// Bar 2+: standard SAR state machine with asymmetric AF
|
||
|
|
float new_sar = sar + af * (ep - sar)
|
||
|
|
if is_long
|
||
|
|
new_sar := math.min(new_sar, low[1])
|
||
|
|
if bar_index > 1
|
||
|
|
new_sar := math.min(new_sar, low[2])
|
||
|
|
if low <= new_sar
|
||
|
|
// Reverse to short
|
||
|
|
is_long := false
|
||
|
|
new_sar := ep + offset_on_reverse
|
||
|
|
ep := low
|
||
|
|
af := af_init_short
|
||
|
|
else
|
||
|
|
if high > ep
|
||
|
|
ep := high
|
||
|
|
af := math.min(af + af_long, af_max_long)
|
||
|
|
else
|
||
|
|
new_sar := math.max(new_sar, high[1])
|
||
|
|
if bar_index > 1
|
||
|
|
new_sar := math.max(new_sar, high[2])
|
||
|
|
if high >= new_sar
|
||
|
|
// Reverse to long
|
||
|
|
is_long := true
|
||
|
|
new_sar := ep - offset_on_reverse
|
||
|
|
ep := high
|
||
|
|
af := af_init_long
|
||
|
|
else
|
||
|
|
if low < ep
|
||
|
|
ep := low
|
||
|
|
af := math.min(af + af_short, af_max_short)
|
||
|
|
sar := new_sar
|
||
|
|
is_long ? sar : -sar
|
||
|
|
|
||
|
|
// ── Inputs ──
|
||
|
|
float i_start = input.float(0.0, "Start Value (0=auto)")
|
||
|
|
float i_offset = input.float(0.0, "Offset on Reverse", minval=0)
|
||
|
|
float i_af_init_long = input.float(0.02, "AF Init Long", minval=0.001, step=0.001)
|
||
|
|
float i_af_long = input.float(0.02, "AF Long", minval=0.001, step=0.001)
|
||
|
|
float i_af_max_long = input.float(0.20, "AF Max Long", minval=0.01, step=0.01)
|
||
|
|
float i_af_init_short = input.float(0.02, "AF Init Short", minval=0.001, step=0.001)
|
||
|
|
float i_af_short = input.float(0.02, "AF Short", minval=0.001, step=0.001)
|
||
|
|
float i_af_max_short = input.float(0.20, "AF Max Short", minval=0.01, step=0.01)
|
||
|
|
|
||
|
|
// ── Calculation ──
|
||
|
|
float result = sarext(i_start, i_offset, i_af_init_long, i_af_long, i_af_max_long,
|
||
|
|
i_af_init_short, i_af_short, i_af_max_short)
|
||
|
|
|
||
|
|
float sar_val = math.abs(nz(result))
|
||
|
|
bool is_long = nz(result) > 0
|
||
|
|
float sar_above = not is_long ? sar_val : na
|
||
|
|
float sar_below = is_long ? sar_val : na
|
||
|
|
|
||
|
|
// ── Plot ──
|
||
|
|
plot(sar_above, "SAREXT Above", color=color.red, style=plot.style_linebr, linewidth=2)
|
||
|
|
plot(sar_below, "SAREXT Below", color=color.green, style=plot.style_linebr, linewidth=2)
|