/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Indicators
{
///
/// Parabolic SAR Extended Indicator
/// Based on TA-Lib implementation
///
public class ParabolicStopAndReverseExtended : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private bool _isLong;
private IBaseDataBar _previousBar;
private decimal _sar;
private decimal _extremepoint;
private decimal _outputSar;
private decimal _afShort;
private decimal _afLong;
private readonly decimal _sarInit;
private readonly decimal _offsetOnReverse;
private readonly decimal _afInitShort;
private readonly decimal _afIncrementShort;
private readonly decimal _afMaxShort;
private readonly decimal _afInitLong;
private readonly decimal _afIncrementLong;
private readonly decimal _afMaxLong;
///
/// Create a new Parabolic SAR Extended
///
/// The name of the Parabolic Stop and Reverse Extended indicator
/// The starting value for the Parabolic Stop and Reverse indicator
/// The offset value to be applied on reverse
/// The starting acceleration factor for short positions
/// The increment value for the acceleration factor for short positions
/// The maximum value for the acceleration factor for short positions
/// The starting acceleration factor for long positions
/// The increment value for the acceleration factor for long positions
/// The maximum value for the acceleration factor for long positions
public ParabolicStopAndReverseExtended(string name, decimal sarStart = 0.0m, decimal offsetOnReverse = 0.0m,
decimal afStartShort = 0.02m, decimal afIncrementShort = 0.02m, decimal afMaxShort = 0.2m,
decimal afStartLong = 0.02m, decimal afIncrementLong = 0.02m, decimal afMaxLong = 0.2m) : base(name)
{
_sarInit = sarStart;
_offsetOnReverse = offsetOnReverse;
_afShort = _afInitShort = afStartShort;
_afIncrementShort = afIncrementShort;
_afMaxShort = afMaxShort;
_afLong = _afInitLong = afStartLong;
_afIncrementLong = afIncrementLong;
_afMaxLong = afMaxLong;
}
///
/// Create a new Parabolic SAR Extended
///
/// The starting value for the Parabolic Stop and Reverse indicator
/// The offset value to be applied on reverse
/// The starting acceleration factor for short positions
/// The increment value for the acceleration factor for short positions
/// The maximum value for the acceleration factor for short positions
/// The starting acceleration factor for long positions
/// The increment value for the acceleration factor for long positions
/// The maximum value for the acceleration factor for long positions
public ParabolicStopAndReverseExtended(decimal sarStart = 0.0m, decimal offsetOnReverse = 0.0m,
decimal afStartShort = 0.02m, decimal afIncrementShort = 0.02m, decimal afMaxShort = 0.2m,
decimal afStartLong = 0.02m, decimal afIncrementLong = 0.02m, decimal afMaxLong = 0.2m)
: this($"SAREXT({sarStart},{offsetOnReverse},{afStartShort},{afIncrementShort},{afMaxShort},{afStartLong},{afIncrementLong},{afMaxLong})",
sarStart, offsetOnReverse, afStartShort, afIncrementShort, afMaxShort, afStartLong, afIncrementLong, afMaxLong)
{
}
///
/// Gets a flag indicating when this indicator is ready and fully initialized
///
public override bool IsReady => Samples >= 2;
///
/// Required period, in data points, for the indicator to be ready and fully initialized.
///
public int WarmUpPeriod => 2;
///
/// Resets this indicator to its initial state
///
public override void Reset()
{
_afShort = _afInitShort;
_afLong = _afInitLong;
base.Reset();
}
///
/// Computes the next value of this indicator from the given state
///
/// The trade bar input given to the indicator
/// A new value for this indicator
protected override decimal ComputeNextValue(IBaseDataBar input)
{
// On the first iteration, we can't compute a valid SAR value yet,
// so we save the current bar and return the initial SAR if provided,
// or fall back to a realistic price (input.Close) to maintain continuity
if (Samples == 1)
{
_previousBar = input;
// Makes sense to return _sarInit when its non-zero
if (_sarInit != 0)
return _sarInit;
// Otherwise, return default
return input.Close;
}
// On second iteration we initiate the position of extreme point SAR
if (Samples == 2)
{
Init(input);
_previousBar = input;
}
if (_isLong)
HandleLongPosition(input);
else
HandleShortPosition(input);
_previousBar = input;
return _outputSar;
}
///
/// Initialize the indicator values
///
private void Init(IBaseDataBar currentBar)
{
// initialize starting sar value
if (_sarInit > 0)
{
_isLong = true;
_sar = _sarInit;
}
else if (_sarInit < 0)
{
_isLong = false;
_sar = Math.Abs(_sarInit);
}
// same set up as standard PSAR when _sarInit = 0
else
{
_isLong = !HasNegativeDM(currentBar);
_sar = _isLong ? _previousBar.Low : _previousBar.High;
}
// initialize extreme point
_extremepoint = _isLong ? currentBar.High : currentBar.Low;
}
///
/// Returns true if Directional Movement (DM) > 0 between today and yesterday's tradebar (false otherwise)
///
private bool HasNegativeDM(IBaseDataBar currentBar)
{
if (currentBar.Low >= _previousBar.Low)
return false;
var highDiff = currentBar.High - _previousBar.High;
var lowDiff = _previousBar.Low - currentBar.Low;
return highDiff < lowDiff;
}
///
/// Calculate indicator value when the position is long
///
private void HandleLongPosition(IBaseDataBar currentBar)
{
// Switch to short if the low penetrates the SAR value.
if (currentBar.Low <= _sar)
{
// Switch and Overide the SAR with the ep
_isLong = false;
_sar = _extremepoint;
// Make sure the overide SAR is within yesterday's and today's range.
_sar = AdjustSARForHighs(_sar, _previousBar.High, currentBar.High);
// Output the overide SAR
if (_offsetOnReverse != 0.0m)
_sar += _sar * _offsetOnReverse;
_outputSar = -_sar;
// Adjust af and ep
_afShort = _afInitShort;
_extremepoint = currentBar.Low;
// Calculate the new SAR
_sar = _sar + _afShort * (_extremepoint - _sar);
// Make sure the new SAR is within yesterday's and today's range.
_sar = AdjustSARForHighs(_sar, _previousBar.High, currentBar.High);
}
// No switch
else
{
// Output the SAR (was calculated in the previous iteration)
_outputSar = _sar;
// Adjust af and ep.
if (currentBar.High > _extremepoint)
{
_extremepoint = currentBar.High;
_afLong += _afIncrementLong;
if (_afLong > _afMaxLong)
_afLong = _afMaxLong;
}
// Calculate the new SAR
_sar = _sar + _afLong * (_extremepoint - _sar);
// Make sure the new SAR is within yesterday's and today's range.
_sar = AdjustSARForLows(_sar, _previousBar.Low, currentBar.Low);
}
}
///
/// Calculate indicator value when the position is short
///
private void HandleShortPosition(IBaseDataBar currentBar)
{
// Switch to long if the high penetrates the SAR value.
if (currentBar.High >= _sar)
{
// Switch and overide the SAR with the ep
_isLong = true;
_sar = _extremepoint;
// Make sure the overide SAR is within yesterday's and today's range.
_sar = AdjustSARForLows(_sar, _previousBar.Low, currentBar.Low);
// Output the overide SAR
if (_offsetOnReverse != 0.0m)
_sar -= _sar * _offsetOnReverse;
_outputSar = _sar;
// Adjust af and ep
_afLong = _afInitLong;
_extremepoint = currentBar.High;
// Calculate the new SAR
_sar = _sar + _afLong * (_extremepoint - _sar);
// Make sure the new SAR is within yesterday's and today's range.
_sar = AdjustSARForLows(_sar, _previousBar.Low, currentBar.Low);
}
//No switch
else
{
// Output the SAR (was calculated in the previous iteration)
_outputSar = -_sar;
// Adjust af and ep.
if (currentBar.Low < _extremepoint)
{
_extremepoint = currentBar.Low;
_afShort += _afIncrementShort;
if (_afShort > _afMaxShort)
_afShort = _afMaxShort;
}
// Calculate the new SAR
_sar = _sar + _afShort * (_extremepoint - _sar);
// Make sure the new SAR is within yesterday's and today's range.
_sar = AdjustSARForHighs(_sar, _previousBar.High, currentBar.High);
}
}
private static decimal AdjustSARForHighs(decimal sar, decimal previousBar, decimal currentBar)
{
sar = Math.Max(sar, previousBar);
sar = Math.Max(sar, currentBar);
return sar;
}
private static decimal AdjustSARForLows(decimal sar, decimal previousBar, decimal currentBar)
{
sar = Math.Min(sar, previousBar);
sar = Math.Min(sar, currentBar);
return sar;
}
}
}