Lean  $LEAN_TAG$
ParabolicStopAndReverse.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
18 
20 {
21  /// <summary>
22  /// Parabolic SAR Indicator
23  /// Based on TA-Lib implementation
24  /// </summary>
26  {
27  private bool _isLong;
28  private IBaseDataBar _previousBar;
29  private decimal _sar;
30  private decimal _ep;
31  private decimal _outputSar;
32  private decimal _af;
33  private readonly decimal _afInit;
34  private readonly decimal _afMax;
35  private readonly decimal _afIncrement;
36 
37  /// <summary>
38  /// Create new Parabolic SAR
39  /// </summary>
40  /// <param name="name">The name of this indicator</param>
41  /// <param name="afStart">Acceleration factor start value</param>
42  /// <param name="afIncrement">Acceleration factor increment value</param>
43  /// <param name="afMax">Acceleration factor max value</param>
44  public ParabolicStopAndReverse(string name, decimal afStart = 0.02m, decimal afIncrement = 0.02m, decimal afMax = 0.2m)
45  : base(name)
46  {
47  _afInit = afStart;
48  _af = afStart;
49  _afIncrement = afIncrement;
50  _afMax = afMax;
51  }
52 
53  /// <summary>
54  /// Create new Parabolic SAR
55  /// </summary>
56  /// <param name="afStart">Acceleration factor start value</param>
57  /// <param name="afIncrement">Acceleration factor increment value</param>
58  /// <param name="afMax">Acceleration factor max value</param>
59  public ParabolicStopAndReverse(decimal afStart = 0.02m, decimal afIncrement = 0.02m, decimal afMax = 0.2m)
60  : this($"PSAR({afStart},{afIncrement},{afMax})", afStart, afIncrement, afMax)
61  {
62  }
63 
64  /// <summary>
65  /// Gets a flag indicating when this indicator is ready and fully initialized
66  /// </summary>
67  public override bool IsReady => Samples >= 2;
68 
69  /// <summary>
70  /// Required period, in data points, for the indicator to be ready and fully initialized.
71  /// </summary>
72  public int WarmUpPeriod => 2;
73 
74  /// <summary>
75  /// Resets this indicator to its initial state
76  /// </summary>
77  public override void Reset()
78  {
79  _af = _afInit;
80  base.Reset();
81  }
82 
83  /// <summary>
84  /// Computes the next value of this indicator from the given state
85  /// </summary>
86  /// <param name="input">The trade bar input given to the indicator</param>
87  /// <returns>A new value for this indicator</returns>
88  protected override decimal ComputeNextValue(IBaseDataBar input)
89  {
90  // On first iteration we can’t produce an SAR value so we save the current bar and return zero
91  if (Samples == 1)
92  {
93  _previousBar = input;
94 
95  // return a value that's close to where we will be, returning 0 doesn't make sense
96  return input.Close;
97  }
98 
99  // On second iteration we initiate the position the extreme point and the SAR
100  if (Samples == 2)
101  {
102  Init(input);
103  _previousBar = input;
104  return _sar;
105  }
106 
107  if (_isLong)
108  {
109  HandleLongPosition(input);
110  }
111  else
112  {
113  HandleShortPosition(input);
114  }
115 
116  _previousBar = input;
117 
118  return _outputSar;
119  }
120 
121  /// <summary>
122  /// Initialize the indicator values
123  /// </summary>
124  private void Init(IBaseDataBar currentBar)
125  {
126  // init position
127  _isLong = currentBar.Close >= _previousBar.Close;
128 
129 
130  // init sar and Extreme price
131  if (_isLong)
132  {
133  _ep = Math.Min(currentBar.High, _previousBar.High);
134  _sar = _previousBar.Low;
135  }
136  else
137  {
138  _ep = Math.Min(currentBar.Low, _previousBar.Low);
139  _sar = _previousBar.High;
140  }
141  }
142 
143  /// <summary>
144  /// Calculate indicator value when the position is long
145  /// </summary>
146  private void HandleLongPosition(IBaseDataBar currentBar)
147  {
148  // Switch to short if the low penetrates the SAR value.
149  if (currentBar.Low <= _sar)
150  {
151  // Switch and Overide the SAR with the ep
152  _isLong = false;
153  _sar = _ep;
154 
155  // Make sure the overide SAR is within yesterday's and today's range.
156  if (_sar < _previousBar.High)
157  _sar = _previousBar.High;
158  if (_sar < currentBar.High)
159  _sar = currentBar.High;
160 
161  // Output the overide SAR
162  _outputSar = _sar;
163 
164  // Adjust af and ep
165  _af = _afInit;
166  _ep = currentBar.Low;
167 
168  // Calculate the new SAR
169  _sar = _sar + _af * (_ep - _sar);
170 
171  // Make sure the new SAR is within yesterday's and today's range.
172  if (_sar < _previousBar.High)
173  _sar = _previousBar.High;
174  if (_sar < currentBar.High)
175  _sar = currentBar.High;
176 
177  }
178 
179  // No switch
180  else
181  {
182  // Output the SAR (was calculated in the previous iteration)
183  _outputSar = _sar;
184 
185  // Adjust af and ep.
186  if (currentBar.High > _ep)
187  {
188  _ep = currentBar.High;
189  _af += _afIncrement;
190  if (_af > _afMax)
191  _af = _afMax;
192  }
193 
194  // Calculate the new SAR
195  _sar = _sar + _af * (_ep - _sar);
196 
197  // Make sure the new SAR is within yesterday's and today's range.
198  if (_sar > _previousBar.Low)
199  _sar = _previousBar.Low;
200  if (_sar > currentBar.Low)
201  _sar = currentBar.Low;
202  }
203  }
204 
205  /// <summary>
206  /// Calculate indicator value when the position is short
207  /// </summary>
208  private void HandleShortPosition(IBaseDataBar currentBar)
209  {
210  // Switch to long if the high penetrates the SAR value.
211  if (currentBar.High >= _sar)
212  {
213  // Switch and Overide the SAR with the ep
214  _isLong = true;
215  _sar = _ep;
216 
217  // Make sure the overide SAR is within yesterday's and today's range.
218  if (_sar > _previousBar.Low)
219  _sar = _previousBar.Low;
220  if (_sar > currentBar.Low)
221  _sar = currentBar.Low;
222 
223  // Output the overide SAR
224  _outputSar = _sar;
225 
226  // Adjust af and ep
227  _af = _afInit;
228  _ep = currentBar.High;
229 
230  // Calculate the new SAR
231  _sar = _sar + _af * (_ep - _sar);
232 
233  // Make sure the new SAR is within yesterday's and today's range.
234  if (_sar > _previousBar.Low)
235  _sar = _previousBar.Low;
236  if (_sar > currentBar.Low)
237  _sar = currentBar.Low;
238  }
239 
240  //No switch
241  else
242  {
243  // Output the SAR (was calculated in the previous iteration)
244  _outputSar = _sar;
245 
246  // Adjust af and ep.
247  if (currentBar.Low < _ep)
248  {
249  _ep = currentBar.Low;
250  _af += _afIncrement;
251  if (_af > _afMax)
252  _af = _afMax;
253  }
254 
255  // Calculate the new SAR
256  _sar = _sar + _af * (_ep - _sar);
257 
258  // Make sure the new SAR is within yesterday's and today's range.
259  if (_sar < _previousBar.High)
260  _sar = _previousBar.High;
261  if (_sar < currentBar.High)
262  _sar = currentBar.High;
263  }
264  }
265  }
266 }