Lean  $LEAN_TAG$
MatHold.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 
17 using System;
19 
21 {
22  /// <summary>
23  /// Mat Hold candlestick pattern
24  /// </summary>
25  /// <remarks>
26  /// Must have:
27  /// - first candle: long white candle
28  /// - upside gap between the first and the second bodies
29  /// - second candle: small black candle
30  /// - third and fourth candles: falling small real body candlesticks(commonly black) that hold within the long
31  /// white candle's body and are higher than the reaction days of the rising three methods
32  /// - fifth candle: white candle that opens above the previous small candle's close and closes higher than the
33  /// high of the highest reaction day
34  /// The meaning of "short" and "long" is specified with SetCandleSettings;
35  /// "hold within" means "a part of the real body must be within";
36  /// penetration is the maximum percentage of the first white body the reaction days can penetrate(it is
37  /// to specify how much the reaction days should be "higher than the reaction days of the rising three methods")
38  /// The returned value is positive(+1): mat hold is always bullish
39  /// </remarks>
40  public class MatHold : CandlestickPattern
41  {
42  private readonly decimal _penetration;
43 
44  private readonly int _bodyShortAveragePeriod;
45  private readonly int _bodyLongAveragePeriod;
46 
47  private decimal[] _bodyPeriodTotal = new decimal[5];
48 
49  /// <summary>
50  /// Initializes a new instance of the <see cref="MatHold"/> class using the specified name.
51  /// </summary>
52  /// <param name="name">The name of this indicator</param>
53  /// <param name="penetration">Percentage of penetration of a candle within another candle</param>
54  public MatHold(string name, decimal penetration = 0.5m)
55  : base(name, Math.Max(CandleSettings.Get(CandleSettingType.BodyShort).AveragePeriod, CandleSettings.Get(CandleSettingType.BodyLong).AveragePeriod) + 4 + 1)
56  {
57  _penetration = penetration;
58 
59  _bodyShortAveragePeriod = CandleSettings.Get(CandleSettingType.BodyShort).AveragePeriod;
60  _bodyLongAveragePeriod = CandleSettings.Get(CandleSettingType.BodyLong).AveragePeriod;
61  }
62 
63  /// <summary>
64  /// Initializes a new instance of the <see cref="MatHold"/> class.
65  /// </summary>
66  /// <param name="penetration">Percentage of penetration of a candle within another candle</param>
67  public MatHold(decimal penetration)
68  : this("MATHOLD", penetration)
69  {
70  }
71 
72  /// <summary>
73  /// Initializes a new instance of the <see cref="MatHold"/> class.
74  /// </summary>
75  public MatHold()
76  : this("MATHOLD")
77  {
78  }
79 
80  /// <summary>
81  /// Gets a flag indicating when this indicator is ready and fully initialized
82  /// </summary>
83  public override bool IsReady
84  {
85  get { return Samples > Period; }
86  }
87 
88  /// <summary>
89  /// Computes the next value of this indicator from the given state
90  /// </summary>
91  /// <param name="window">The window of data held in this indicator</param>
92  /// <param name="input">The input given to the indicator</param>
93  /// <returns>A new value for this indicator</returns>
94  protected override decimal ComputeNextValue(IReadOnlyWindow<IBaseDataBar> window, IBaseDataBar input)
95  {
96  if (!IsReady)
97  {
98  if (Samples > Period - _bodyShortAveragePeriod)
99  {
100  _bodyPeriodTotal[3] += GetCandleRange(CandleSettingType.BodyShort, window[3]);
101  _bodyPeriodTotal[2] += GetCandleRange(CandleSettingType.BodyShort, window[2]);
102  _bodyPeriodTotal[1] += GetCandleRange(CandleSettingType.BodyShort, window[1]);
103  }
104 
105  if (Samples > Period - _bodyLongAveragePeriod)
106  {
107  _bodyPeriodTotal[4] += GetCandleRange(CandleSettingType.BodyLong, window[4]);
108  }
109 
110  return 0m;
111  }
112 
113  decimal value;
114  if (
115  // 1st long, then 3 small
116  GetRealBody(window[4]) > GetCandleAverage(CandleSettingType.BodyLong, _bodyPeriodTotal[4], window[4]) &&
117  GetRealBody(window[3]) < GetCandleAverage(CandleSettingType.BodyShort, _bodyPeriodTotal[3], window[3]) &&
118  GetRealBody(window[2]) < GetCandleAverage(CandleSettingType.BodyShort, _bodyPeriodTotal[2], window[2]) &&
119  GetRealBody(window[1]) < GetCandleAverage(CandleSettingType.BodyShort, _bodyPeriodTotal[1], window[1]) &&
120  // white, black, 2 black or white, white
121  GetCandleColor(window[4]) == CandleColor.White &&
122  GetCandleColor(window[3]) == CandleColor.Black &&
123  GetCandleColor(input) == CandleColor.White &&
124  // upside gap 1st to 2nd
125  GetRealBodyGapUp(window[3], window[4]) &&
126  // 3rd to 4th hold within 1st: a part of the real body must be within 1st real body
127  Math.Min(window[2].Open, window[2].Close) < window[4].Close &&
128  Math.Min(window[1].Open, window[1].Close) < window[4].Close &&
129  // reaction days penetrate first body less than optInPenetration percent
130  Math.Min(window[2].Open, window[2].Close) > window[4].Close - GetRealBody(window[4]) * _penetration &&
131  Math.Min(window[1].Open, window[1].Close) > window[4].Close - GetRealBody(window[4]) * _penetration &&
132  // 2nd to 4th are falling
133  Math.Max(window[2].Close, window[2].Open) < window[3].Open &&
134  Math.Max(window[1].Close, window[1].Open) < Math.Max(window[2].Close, window[2].Open) &&
135  // 5th opens above the prior close
136  input.Open > window[1].Close &&
137  // 5th closes above the highest high of the reaction days
138  input.Close > Math.Max(Math.Max(window[3].High, window[2].High), window[1].High)
139  )
140  value = 1m;
141  else
142  value = 0m;
143 
144  // add the current range and subtract the first range: this is done after the pattern recognition
145  // when avgPeriod is not 0, that means "compare with the previous candles" (it excludes the current candle)
146 
147  _bodyPeriodTotal[4] += GetCandleRange(CandleSettingType.BodyLong, window[4]) -
148  GetCandleRange(CandleSettingType.BodyLong, window[_bodyLongAveragePeriod + 4]);
149 
150  for (var i = 3; i >= 1; i--)
151  {
152  _bodyPeriodTotal[i] += GetCandleRange(CandleSettingType.BodyShort, window[i]) -
153  GetCandleRange(CandleSettingType.BodyShort, window[i + _bodyShortAveragePeriod]);
154  }
155 
156  return value;
157  }
158 
159  /// <summary>
160  /// Resets this indicator to its initial state
161  /// </summary>
162  public override void Reset()
163  {
164  _bodyPeriodTotal = new decimal[5];
165  base.Reset();
166  }
167  }
168 }