Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 30
CRAP
74.64% covered (warning)
74.64%
1451 / 1944
LupeCode\phpTraderNative\TALib\Core\MomentumIndicators
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 30
6308.04
74.64% covered (warning)
74.64%
1451 / 1944
 adx
0.00% covered (danger)
0.00%
0 / 1
80.79
75.18% covered (warning)
75.18%
106 / 141
 adxr
0.00% covered (danger)
0.00%
0 / 1
10.58
73.08% covered (warning)
73.08%
19 / 26
 apo
0.00% covered (danger)
0.00%
0 / 1
11.64
61.54% covered (warning)
61.54%
8 / 13
 aroon
0.00% covered (danger)
0.00%
0 / 1
16.33
89.09% covered (warning)
89.09%
49 / 55
 aroonOsc
0.00% covered (danger)
0.00%
0 / 1
16.33
89.09% covered (warning)
89.09%
49 / 55
 bop
0.00% covered (danger)
0.00%
0 / 1
4.10
81.82% covered (warning)
81.82%
9 / 11
 cci
0.00% covered (danger)
0.00%
0 / 1
18.91
81.25% covered (warning)
81.25%
39 / 48
 cmo
0.00% covered (danger)
0.00%
0 / 1
97.85
52.63% covered (warning)
52.63%
50 / 95
 dx
0.00% covered (danger)
0.00%
0 / 1
39.08
88.50% covered (warning)
88.50%
100 / 113
 macd
0.00% covered (danger)
0.00%
0 / 1
14.00
70.83% covered (warning)
70.83%
17 / 24
 macdExt
0.00% covered (danger)
0.00%
0 / 1
46.93
65.85% covered (warning)
65.85%
54 / 82
 macdFix
0.00% covered (danger)
0.00%
0 / 1
5.16
81.25% covered (warning)
81.25%
13 / 16
 mfi
0.00% covered (danger)
0.00%
0 / 1
39.39
70.10% covered (warning)
70.10%
68 / 97
 minusDI
0.00% covered (danger)
0.00%
0 / 1
78.18
67.21% covered (warning)
67.21%
82 / 122
 minusDM
0.00% covered (danger)
0.00%
0 / 1
25.57
78.21% covered (warning)
78.21%
61 / 78
 mom
0.00% covered (danger)
0.00%
0 / 1
9.73
70.00% covered (warning)
70.00%
14 / 20
 plusDI
0.00% covered (danger)
0.00%
0 / 1
78.18
67.21% covered (warning)
67.21%
82 / 122
 plusDM
0.00% covered (danger)
0.00%
0 / 1
57.53
56.41% covered (warning)
56.41%
44 / 78
 ppo
0.00% covered (danger)
0.00%
0 / 1
8.66
78.26% covered (warning)
78.26%
18 / 23
 roc
0.00% covered (danger)
0.00%
0 / 1
11.01
70.83% covered (warning)
70.83%
17 / 24
 rocP
0.00% covered (danger)
0.00%
0 / 1
11.01
70.83% covered (warning)
70.83%
17 / 24
 rocR
0.00% covered (danger)
0.00%
0 / 1
11.01
70.83% covered (warning)
70.83%
17 / 24
 rocR100
0.00% covered (danger)
0.00%
0 / 1
11.01
70.83% covered (warning)
70.83%
17 / 24
 rsi
0.00% covered (danger)
0.00%
0 / 1
95.33
53.19% covered (warning)
53.19%
50 / 94
 stoch
0.00% covered (danger)
0.00%
0 / 1
40.19
80.00% covered (warning)
80.00%
76 / 95
 stochF
0.00% covered (danger)
0.00%
0 / 1
34.61
81.18% covered (warning)
81.18%
69 / 85
 stochRsi
0.00% covered (danger)
0.00%
0 / 1
22.48
73.33% covered (warning)
73.33%
44 / 60
 trix
0.00% covered (danger)
0.00%
0 / 1
20.51
70.97% covered (warning)
70.97%
44 / 62
 ultOsc
0.00% covered (danger)
0.00%
0 / 1
48
95.43% covered (success)
95.43%
167 / 175
 willR
0.00% covered (danger)
0.00%
0 / 1
17.51
87.93% covered (warning)
87.93%
51 / 58
<?php
/**
 * This is a PHP port of the Trader extension for PHP, which is a port of the TA-LIB C code.
 *
 * This port is written in PHP and without any other requirements.
 * The goal is that this library can be used by those whom cannot install the PHP Trader extension.
 *
 * Below is the copyright information for TA-LIB found in the source code.
 */
/* TA-LIB Copyright (c) 1999-2007, Mario Fortier
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * - Neither name of author nor the names of its contributors
 *   may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
namespace LupeCode\phpTraderNative\TALib\Core;
use LupeCode\phpTraderNative\TALib\Classes\MoneyFlow;
use LupeCode\phpTraderNative\TALib\Enum\Compatibility;
use LupeCode\phpTraderNative\TALib\Enum\ReturnCode;
use LupeCode\phpTraderNative\TALib\Enum\UnstablePeriodFunctionID;
class MomentumIndicators extends Core
{
    /**
     * @param int     $startIdx
     * @param int     $endIdx
     * @param float[] $inHigh
     * @param float[] $inLow
     * @param float[] $inClose
     * @param int     $optInTimePeriod
     * @param int     $outBegIdx
     * @param int     $outNBElement
     * @param float[] $outReal
     *
     * @return int
     */
    public static function adx(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $lookbackTotal = (2 * $optInTimePeriod) + (static::$unstablePeriod[UnstablePeriodFunctionID::ADX]) - 1;
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outBegIdx   = $today = $startIdx;
        $prevMinusDM = 0.0;
        $prevPlusDM  = 0.0;
        $prevTR      = 0.0;
        $today       = $startIdx - $lookbackTotal;
        $prevHigh    = $inHigh[$today];
        $prevLow     = $inLow[$today];
        $prevClose   = $inClose[$today];
        $i           = $optInTimePeriod - 1;
        while ($i-- > 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            } elseif (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    += $tempReal;
            $prevClose = $inClose[$today];
        }
        $sumDX = 0.0;
        $i     = $optInTimePeriod;
        while ($i-- > 0) {
            $today++;
            $tempReal    = $inHigh[$today];
            $diffP       = $tempReal - $prevHigh;
            $prevHigh    = $tempReal;
            $tempReal    = $inLow[$today];
            $diffM       = $prevLow - $tempReal;
            $prevLow     = $tempReal;
            $prevMinusDM -= $prevMinusDM / $optInTimePeriod;
            $prevPlusDM  -= $prevPlusDM / $optInTimePeriod;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            } elseif (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
            if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
                $minusDI  = (100.0 * ($prevMinusDM / $prevTR));
                $plusDI   = (100.0 * ($prevPlusDM / $prevTR));
                $tempReal = $minusDI + $plusDI;
                if (!(((-0.00000001) < $tempReal) && ($tempReal < 0.00000001))) {
                    $sumDX += (100.0 * (abs($minusDI - $plusDI) / $tempReal));
                }
            }
        }
        $prevADX = ($sumDX / $optInTimePeriod);
        $i       = (static::$unstablePeriod[UnstablePeriodFunctionID::ADX]);
        while ($i-- > 0) {
            $today++;
            $tempReal    = $inHigh[$today];
            $diffP       = $tempReal - $prevHigh;
            $prevHigh    = $tempReal;
            $tempReal    = $inLow[$today];
            $diffM       = $prevLow - $tempReal;
            $prevLow     = $tempReal;
            $prevMinusDM -= $prevMinusDM / $optInTimePeriod;
            $prevPlusDM  -= $prevPlusDM / $optInTimePeriod;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            } elseif (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
            if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
                $minusDI  = (100.0 * ($prevMinusDM / $prevTR));
                $plusDI   = (100.0 * ($prevPlusDM / $prevTR));
                $tempReal = $minusDI + $plusDI;
                if (!(((-0.00000001) < $tempReal) && ($tempReal < 0.00000001))) {
                    $tempReal = (100.0 * (abs($minusDI - $plusDI) / $tempReal));
                    $prevADX  = ((($prevADX * ($optInTimePeriod - 1)) + $tempReal) / $optInTimePeriod);
                }
            }
        }
        $outReal[0] = $prevADX;
        $outIdx     = 1;
        while ($today < $endIdx) {
            $today++;
            $tempReal    = $inHigh[$today];
            $diffP       = $tempReal - $prevHigh;
            $prevHigh    = $tempReal;
            $tempReal    = $inLow[$today];
            $diffM       = $prevLow - $tempReal;
            $prevLow     = $tempReal;
            $prevMinusDM -= $prevMinusDM / $optInTimePeriod;
            $prevPlusDM  -= $prevPlusDM / $optInTimePeriod;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            } elseif (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
            if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
                $minusDI  = (100.0 * ($prevMinusDM / $prevTR));
                $plusDI   = (100.0 * ($prevPlusDM / $prevTR));
                $tempReal = $minusDI + $plusDI;
                if (!(((-0.00000001) < $tempReal) && ($tempReal < 0.00000001))) {
                    $tempReal = (100.0 * (abs($minusDI - $plusDI) / $tempReal));
                    $prevADX  = ((($prevADX * ($optInTimePeriod - 1)) + $tempReal) / $optInTimePeriod);
                }
            }
            $outReal[$outIdx++] = $prevADX;
        }
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int     $startIdx
     * @param int     $endIdx
     * @param float[] $inHigh
     * @param float[] $inLow
     * @param float[] $inClose
     * @param int     $optInTimePeriod
     * @param int     $outBegIdx
     * @param int     $outNBElement
     * @param float[] $outReal
     *
     * @return int
     */
    public static function adxr(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $adxrLookback = Lookback::adxrLookback($optInTimePeriod);
        if ($startIdx < $adxrLookback) {
            $startIdx = $adxrLookback;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $adx        = static::double($endIdx - $startIdx + $optInTimePeriod);
        $ReturnCode = self::adx($startIdx - ($optInTimePeriod - 1), $endIdx, $inHigh, $inLow, $inClose, $optInTimePeriod, $outBegIdx, $outNBElement, $adx);
        if ($ReturnCode != ReturnCode::Success) {
            return $ReturnCode;
        }
        $i         = $optInTimePeriod - 1;
        $j         = 0;
        $outIdx    = 0;
        $nbElement = $endIdx - $startIdx + 2;
        while (--$nbElement != 0) {
            $outReal[$outIdx++] = (($adx[$i++] + $adx[$j++]) / 2.0);
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int     $startIdx
     * @param int     $endIdx
     * @param float[] $inReal
     * @param int     $optInFastPeriod
     * @param int     $optInSlowPeriod
     * @param int     $optInMAType
     * @param int     $outBegIdx
     * @param int     $outNBElement
     * @param float[] $outReal
     *
     * @return int
     */
    public static function apo(int $startIdx, int $endIdx, array $inReal, int $optInFastPeriod, int $optInSlowPeriod, int $optInMAType, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInFastPeriod == (PHP_INT_MIN)) {
            $optInFastPeriod = 12;
        } elseif (((int)$optInFastPeriod < 2) || ((int)$optInFastPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSlowPeriod == (PHP_INT_MIN)) {
            $optInSlowPeriod = 26;
        } elseif (((int)$optInSlowPeriod < 2) || ((int)$optInSlowPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $tempBuffer = static::double($endIdx - $startIdx + 1);
        $ReturnCode = static::TA_INT_PO($startIdx, $endIdx, $inReal, $optInFastPeriod, $optInSlowPeriod, $optInMAType, $outBegIdx, $outNBElement, $outReal, $tempBuffer, false);
        return $ReturnCode;
    }
    /**
     * @param int     $startIdx
     * @param int     $endIdx
     * @param float[] $inHigh
     * @param float[] $inLow
     * @param int     $optInTimePeriod
     * @param int     $outBegIdx
     * @param int     $outNBElement
     * @param float[] $outAroonDown
     * @param float[] $outAroonUp
     *
     * @return int
     */
    public static function aroon(int $startIdx, int $endIdx, array $inHigh, array $inLow, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outAroonDown, array &$outAroonUp): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($startIdx < $optInTimePeriod) {
            $startIdx = $optInTimePeriod;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $today       = $startIdx;
        $trailingIdx = $startIdx - $optInTimePeriod;
        $lowestIdx   = -1;
        $highestIdx  = -1;
        $lowest      = 0.0;
        $highest     = 0.0;
        $factor      = (double)100.0 / (double)$optInTimePeriod;
        while ($today <= $endIdx) {
            $tmp = $inLow[$today];
            if ($lowestIdx < $trailingIdx) {
                $lowestIdx = $trailingIdx;
                $lowest    = $inLow[$lowestIdx];
                $i         = $lowestIdx;
                while (++$i <= $today) {
                    $tmp = $inLow[$i];
                    if ($tmp <= $lowest) {
                        $lowestIdx = $i;
                        $lowest    = $tmp;
                    }
                }
            } elseif ($tmp <= $lowest) {
                $lowestIdx = $today;
                $lowest    = $tmp;
            }
            $tmp = $inHigh[$today];
            if ($highestIdx < $trailingIdx) {
                $highestIdx = $trailingIdx;
                $highest    = $inHigh[$highestIdx];
                $i          = $highestIdx;
                while (++$i <= $today) {
                    $tmp = $inHigh[$i];
                    if ($tmp >= $highest) {
                        $highestIdx = $i;
                        $highest    = $tmp;
                    }
                }
            } elseif ($tmp >= $highest) {
                $highestIdx = $today;
                $highest    = $tmp;
            }
            $outAroonUp[$outIdx]   = $factor * ($optInTimePeriod - ($today - $highestIdx));
            $outAroonDown[$outIdx] = $factor * ($optInTimePeriod - ($today - $lowestIdx));
            $outIdx++;
            $trailingIdx++;
            $today++;
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int     $startIdx
     * @param int     $endIdx
     * @param float[] $inHigh
     * @param float[] $inLow
     * @param int     $optInTimePeriod
     * @param int     $outBegIdx
     * @param int     $outNBElement
     * @param float[] $outReal
     *
     * @return int
     */
    public static function aroonOsc(int $startIdx, int $endIdx, array $inHigh, array $inLow, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($startIdx < $optInTimePeriod) {
            $startIdx = $optInTimePeriod;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $today       = $startIdx;
        $trailingIdx = $startIdx - $optInTimePeriod;
        $lowestIdx   = -1;
        $highestIdx  = -1;
        $lowest      = 0.0;
        $highest     = 0.0;
        $factor      = (double)100.0 / (double)$optInTimePeriod;
        while ($today <= $endIdx) {
            $tmp = $inLow[$today];
            if ($lowestIdx < $trailingIdx) {
                $lowestIdx = $trailingIdx;
                $lowest    = $inLow[$lowestIdx];
                $i         = $lowestIdx;
                while (++$i <= $today) {
                    $tmp = $inLow[$i];
                    if ($tmp <= $lowest) {
                        $lowestIdx = $i;
                        $lowest    = $tmp;
                    }
                }
            } elseif ($tmp <= $lowest) {
                $lowestIdx = $today;
                $lowest    = $tmp;
            }
            $tmp = $inHigh[$today];
            if ($highestIdx < $trailingIdx) {
                $highestIdx = $trailingIdx;
                $highest    = $inHigh[$highestIdx];
                $i          = $highestIdx;
                while (++$i <= $today) {
                    $tmp = $inHigh[$i];
                    if ($tmp >= $highest) {
                        $highestIdx = $i;
                        $highest    = $tmp;
                    }
                }
            } elseif ($tmp >= $highest) {
                $highestIdx = $today;
                $highest    = $tmp;
            }
            $aroon            = $factor * ($highestIdx - $lowestIdx);
            $outReal[$outIdx] = $aroon;
            $outIdx++;
            $trailingIdx++;
            $today++;
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int     $startIdx
     * @param int     $endIdx
     * @param float[] $inOpen
     * @param float[] $inHigh
     * @param float[] $inLow
     * @param float[] $inClose
     * @param int     $outBegIdx
     * @param int     $outNBElement
     * @param float[] $outReal
     *
     * @return int
     */
    public static function bop(int $startIdx, int $endIdx, array $inOpen, array $inHigh, array $inLow, array $inClose, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        $outIdx = 0;
        for ($i = $startIdx; $i <= $endIdx; $i++) {
            $tempReal = $inHigh[$i] - $inLow[$i];
            if (($tempReal < 0.00000001)) {
                $outReal[$outIdx++] = 0.0;
            } else {
                $outReal[$outIdx++] = ($inClose[$i] - $inOpen[$i]) / $tempReal;
            }
        }
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int     $startIdx
     * @param int     $endIdx
     * @param float[] $inHigh
     * @param float[] $inLow
     * @param float[] $inClose
     * @param int     $optInTimePeriod
     * @param int     $outBegIdx
     * @param int     $outNBElement
     * @param float[] $outReal
     *
     * @return int
     */
    public static function cci(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        $circBuffer_Idx = 0;
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $lookbackTotal = ($optInTimePeriod - 1);
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        {
            if ($optInTimePeriod <= 0) {
                return ReturnCode::AllocError;
            }
            $circBuffer        = static::double($optInTimePeriod);
            $maxIdx_circBuffer = ($optInTimePeriod - 1);
        };
        $i = $startIdx - $lookbackTotal;
        if ($optInTimePeriod > 1) {
            while ($i < $startIdx) {
                $circBuffer[$circBuffer_Idx] = ($inHigh[$i] + $inLow[$i] + $inClose[$i]) / 3;
                $i++;
                {
                    $circBuffer_Idx++;
                    if ($circBuffer_Idx > $maxIdx_circBuffer) {
                        $circBuffer_Idx = 0;
                    }
                };
            }
        }
        $outIdx = 0;
        do {
            $lastValue                   = ($inHigh[$i] + $inLow[$i] + $inClose[$i]) / 3;
            $circBuffer[$circBuffer_Idx] = $lastValue;
            $theAverage                  = 0;
            for ($j = 0; $j < $optInTimePeriod; $j++) {
                $theAverage += $circBuffer[$j];
            }
            $theAverage /= $optInTimePeriod;
            $tempReal2  = 0;
            for ($j = 0; $j < $optInTimePeriod; $j++) {
                $tempReal2 += abs($circBuffer[$j] - $theAverage);
            }
            $tempReal = $lastValue - $theAverage;
            if (($tempReal != 0.0) && ($tempReal2 != 0.0)) {
                $outReal[$outIdx++] = $tempReal / (0.015 * ($tempReal2 / $optInTimePeriod));
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            {
                $circBuffer_Idx++;
                if ($circBuffer_Idx > $maxIdx_circBuffer) {
                    $circBuffer_Idx = 0;
                }
            };
            $i++;
        } while ($i <= $endIdx);
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function cmo(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $outBegIdx     = 0;
        $outNBElement  = 0;
        $lookbackTotal = Lookback::cmoLookback($optInTimePeriod);
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            return ReturnCode::Success;
        }
        $outIdx = 0;
        if ($optInTimePeriod == 1) {
            $outBegIdx    = $startIdx;
            $i            = ($endIdx - $startIdx) + 1;
            $outNBElement = $i;
            $outReal      = \array_slice($inReal, 0, $i);
            return ReturnCode::Success;
        }
        $today          = $startIdx - $lookbackTotal;
        $prevValue      = $inReal[$today];
        $unstablePeriod = (static::$unstablePeriod[UnstablePeriodFunctionID::CMO]);
        if (($unstablePeriod == 0) &&
            ((static::$compatibility) == Compatibility::Metastock)) {
            $savePrevValue = $prevValue;
            $prevGain      = 0.0;
            $prevLoss      = 0.0;
            for ($i = $optInTimePeriod; $i > 0; $i--) {
                $tempValue1 = $inReal[$today++];
                $tempValue2 = $tempValue1 - $prevValue;
                $prevValue  = $tempValue1;
                if ($tempValue2 < 0) {
                    $prevLoss -= $tempValue2;
                } else {
                    $prevGain += $tempValue2;
                }
            }
            $tempValue1 = $prevLoss / $optInTimePeriod;
            $tempValue2 = $prevGain / $optInTimePeriod;
            $tempValue3 = $tempValue2 - $tempValue1;
            $tempValue4 = $tempValue1 + $tempValue2;
            if (!(((-0.00000001) < $tempValue4) && ($tempValue4 < 0.00000001))) {
                $outReal[$outIdx++] = 100 * ($tempValue3 / $tempValue4);
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            if ($today > $endIdx) {
                $outBegIdx    = $startIdx;
                $outNBElement = $outIdx;
                return ReturnCode::Success;
            }
            $today     -= $optInTimePeriod;
            $prevValue = $savePrevValue;
        }
        $prevGain = 0.0;
        $prevLoss = 0.0;
        $today++;
        for ($i = $optInTimePeriod; $i > 0; $i--) {
            $tempValue1 = $inReal[$today++];
            $tempValue2 = $tempValue1 - $prevValue;
            $prevValue  = $tempValue1;
            if ($tempValue2 < 0) {
                $prevLoss -= $tempValue2;
            } else {
                $prevGain += $tempValue2;
            }
        }
        $prevLoss /= $optInTimePeriod;
        $prevGain /= $optInTimePeriod;
        if ($today > $startIdx) {
            $tempValue1 = $prevGain + $prevLoss;
            if (!(((-0.00000001) < $tempValue1) && ($tempValue1 < 0.00000001))) {
                $outReal[$outIdx++] = 100.0 * (($prevGain - $prevLoss) / $tempValue1);
            } else {
                $outReal[$outIdx++] = 0.0;
            }
        } else {
            while ($today < $startIdx) {
                $tempValue1 = $inReal[$today];
                $tempValue2 = $tempValue1 - $prevValue;
                $prevValue  = $tempValue1;
                $prevLoss   *= ($optInTimePeriod - 1);
                $prevGain   *= ($optInTimePeriod - 1);
                if ($tempValue2 < 0) {
                    $prevLoss -= $tempValue2;
                } else {
                    $prevGain += $tempValue2;
                }
                $prevLoss /= $optInTimePeriod;
                $prevGain /= $optInTimePeriod;
                $today++;
            }
        }
        while ($today <= $endIdx) {
            $tempValue1 = $inReal[$today++];
            $tempValue2 = $tempValue1 - $prevValue;
            $prevValue  = $tempValue1;
            $prevLoss   *= ($optInTimePeriod - 1);
            $prevGain   *= ($optInTimePeriod - 1);
            if ($tempValue2 < 0) {
                $prevLoss -= $tempValue2;
            } else {
                $prevGain += $tempValue2;
            }
            $prevLoss   /= $optInTimePeriod;
            $prevGain   /= $optInTimePeriod;
            $tempValue1 = $prevGain + $prevLoss;
            if (!(((-0.00000001) < $tempValue1) && ($tempValue1 < 0.00000001))) {
                $outReal[$outIdx++] = 100.0 * (($prevGain - $prevLoss) / $tempValue1);
            } else {
                $outReal[$outIdx++] = 0.0;
            }
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function dx(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($optInTimePeriod > 1) {
            $lookbackTotal = $optInTimePeriod + (static::$unstablePeriod[UnstablePeriodFunctionID::DX]);
        } else {
            $lookbackTotal = 2;
        }
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $outBegIdx   = $today = $startIdx;
        $prevMinusDM = 0.0;
        $prevPlusDM  = 0.0;
        $prevTR      = 0.0;
        $today       = $startIdx - $lookbackTotal;
        $prevHigh    = $inHigh[$today];
        $prevLow     = $inLow[$today];
        $prevClose   = $inClose[$today];
        $i           = $optInTimePeriod - 1;
        while ($i-- > 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            } elseif (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    += $tempReal;
            $prevClose = $inClose[$today];
        }
        $i = (static::$unstablePeriod[UnstablePeriodFunctionID::DX]) + 1;
        while ($i-- != 0) {
            $today++;
            $tempReal    = $inHigh[$today];
            $diffP       = $tempReal - $prevHigh;
            $prevHigh    = $tempReal;
            $tempReal    = $inLow[$today];
            $diffM       = $prevLow - $tempReal;
            $prevLow     = $tempReal;
            $prevMinusDM -= $prevMinusDM / $optInTimePeriod;
            $prevPlusDM  -= $prevPlusDM / $optInTimePeriod;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            } elseif (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
        }
        if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
            $minusDI  = (100.0 * ($prevMinusDM / $prevTR));
            $plusDI   = (100.0 * ($prevPlusDM / $prevTR));
            $tempReal = $minusDI + $plusDI;
            if (!(((-0.00000001) < $tempReal) && ($tempReal < 0.00000001))) {
                $outReal[0] = (100.0 * (abs($minusDI - $plusDI) / $tempReal));
            } else {
                $outReal[0] = 0.0;
            }
        } else {
            $outReal[0] = 0.0;
        }
        $outIdx = 1;
        while ($today < $endIdx) {
            $today++;
            $tempReal    = $inHigh[$today];
            $diffP       = $tempReal - $prevHigh;
            $prevHigh    = $tempReal;
            $tempReal    = $inLow[$today];
            $diffM       = $prevLow - $tempReal;
            $prevLow     = $tempReal;
            $prevMinusDM -= $prevMinusDM / $optInTimePeriod;
            $prevPlusDM  -= $prevPlusDM / $optInTimePeriod;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            } elseif (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
            if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
                $minusDI  = (100.0 * ($prevMinusDM / $prevTR));
                $plusDI   = (100.0 * ($prevPlusDM / $prevTR));
                $tempReal = $minusDI + $plusDI;
                if (!(((-0.00000001) < $tempReal) && ($tempReal < 0.00000001))) {
                    $outReal[$outIdx] = (100.0 * (abs($minusDI - $plusDI) / $tempReal));
                } else {
                    $outReal[$outIdx] = $outReal[$outIdx - 1];
                }
            } else {
                $outReal[$outIdx] = $outReal[$outIdx - 1];
            }
            $outIdx++;
        }
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInFastPeriod
     * @param int   $optInSlowPeriod
     * @param int   $optInSignalPeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outMACD
     * @param array $outMACDSignal
     * @param array $outMACDHist
     *
     * @return int
     */
    public static function macd(int $startIdx, int $endIdx, array $inReal, int $optInFastPeriod, int $optInSlowPeriod, int $optInSignalPeriod, int &$outBegIdx, int &$outNBElement, array &$outMACD, array &$outMACDSignal, array &$outMACDHist): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInFastPeriod == (PHP_INT_MIN)) {
            $optInFastPeriod = 12;
        } elseif (((int)$optInFastPeriod < 2) || ((int)$optInFastPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSlowPeriod == (PHP_INT_MIN)) {
            $optInSlowPeriod = 26;
        } elseif (((int)$optInSlowPeriod < 2) || ((int)$optInSlowPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSignalPeriod == (PHP_INT_MIN)) {
            $optInSignalPeriod = 9;
        } elseif (((int)$optInSignalPeriod < 1) || ((int)$optInSignalPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        return static::TA_INT_MACD(
            $startIdx, $endIdx, $inReal,
            $optInFastPeriod,
            $optInSlowPeriod,
            $optInSignalPeriod,
            $outBegIdx,
            $outNBElement,
            $outMACD,
            $outMACDSignal,
            $outMACDHist
        );
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInFastPeriod
     * @param int   $optInFastMAType
     * @param int   $optInSlowPeriod
     * @param int   $optInSlowMAType
     * @param int   $optInSignalPeriod
     * @param int   $optInSignalMAType
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outMACD
     * @param array $outMACDSignal
     * @param array $outMACDHist
     *
     * @return int
     */
    public static function macdExt(int $startIdx, int $endIdx, array $inReal, int $optInFastPeriod, int $optInFastMAType, int $optInSlowPeriod, int $optInSlowMAType, int $optInSignalPeriod, int $optInSignalMAType, int &$outBegIdx, int &$outNBElement, array &$outMACD, array &$outMACDSignal, array &$outMACDHist): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        $outBegIdx1    = 0;
        $outNbElement1 = 0;
        $outBegIdx2    = 0;
        $outNbElement2 = 0;
        if ((int)$optInFastPeriod == (PHP_INT_MIN)) {
            $optInFastPeriod = 12;
        } elseif (((int)$optInFastPeriod < 2) || ((int)$optInFastPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSlowPeriod == (PHP_INT_MIN)) {
            $optInSlowPeriod = 26;
        } elseif (((int)$optInSlowPeriod < 2) || ((int)$optInSlowPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSignalPeriod == (PHP_INT_MIN)) {
            $optInSignalPeriod = 9;
        } elseif (((int)$optInSignalPeriod < 1) || ((int)$optInSignalPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($optInSlowPeriod < $optInFastPeriod) {
            $tempInteger     = $optInSlowPeriod;
            $optInSlowPeriod = $optInFastPeriod;
            $optInFastPeriod = $tempInteger;
            $tempMAType      = $optInSlowMAType;
            $optInSlowMAType = $optInFastMAType;
            $optInFastMAType = $tempMAType;
        }
        $lookbackLargest = Lookback::movingAverageLookback($optInFastPeriod, $optInFastMAType);
        $tempInteger     = Lookback::movingAverageLookback($optInSlowPeriod, $optInSlowMAType);
        if ($tempInteger > $lookbackLargest) {
            $lookbackLargest = $tempInteger;
        }
        $lookbackSignal = Lookback::movingAverageLookback($optInSignalPeriod, $optInSignalMAType);
        $lookbackTotal  = $lookbackSignal + $lookbackLargest;
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $tempInteger  = ($endIdx - $startIdx) + 1 + $lookbackSignal;
        $fastMABuffer = static::double($tempInteger);
        $slowMABuffer = static::double($tempInteger);
        $tempInteger  = $startIdx - $lookbackSignal;
        $ReturnCode   = OverlapStudies::movingAverage(
            $tempInteger, $endIdx,
            $inReal, $optInSlowPeriod, $optInSlowMAType,
            $outBegIdx1, $outNbElement1,
            $slowMABuffer
        );
        if ($ReturnCode != ReturnCode::Success) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        $ReturnCode = OverlapStudies::movingAverage(
            $tempInteger, $endIdx,
            $inReal, $optInFastPeriod, $optInFastMAType,
            $outBegIdx2, $outNbElement2,
            $fastMABuffer
        );
        if ($ReturnCode != ReturnCode::Success) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        if (($outBegIdx1 != $tempInteger) ||
            ($outBegIdx2 != $tempInteger) ||
            ($outNbElement1 != $outNbElement2) ||
            ($outNbElement1 != ($endIdx - $startIdx) + 1 + $lookbackSignal)) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return (ReturnCode::InternalError);
        }
        for ($i = 0; $i < $outNbElement1; $i++) {
            $fastMABuffer[$i] = $fastMABuffer[$i] - $slowMABuffer[$i];
        }
        //System::arraycopy($fastMABuffer, $lookbackSignal, $outMACD, 0, ($endIdx - $startIdx) + 1);
        $outMACD    = \array_slice($fastMABuffer, $lookbackSignal, ($endIdx - $startIdx) + 1);
        $ReturnCode = OverlapStudies::movingAverage(
            0, $outNbElement1 - 1,
            $fastMABuffer, $optInSignalPeriod, $optInSignalMAType,
            $outBegIdx2, $outNbElement2, $outMACDSignal
        );
        if ($ReturnCode != ReturnCode::Success) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        for ($i = 0; $i < $outNbElement2; $i++) {
            $outMACDHist[$i] = $outMACD[$i] - $outMACDSignal[$i];
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outNbElement2;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInSignalPeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outMACD
     * @param array $outMACDSignal
     * @param array $outMACDHist
     *
     * @return int
     */
    public static function macdFix(int $startIdx, int $endIdx, array $inReal, int $optInSignalPeriod, int &$outBegIdx, int &$outNBElement, array &$outMACD, array &$outMACDSignal, array &$outMACDHist): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInSignalPeriod == (PHP_INT_MIN)) {
            $optInSignalPeriod = 9;
        } elseif (((int)$optInSignalPeriod < 1) || ((int)$optInSignalPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        return static::TA_INT_MACD(
            $startIdx, $endIdx, $inReal,
            0,
            0,
            $optInSignalPeriod,
            $outBegIdx,
            $outNBElement,
            $outMACD,
            $outMACDSignal,
            $outMACDHist
        );
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param array $inVolume
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function mfi(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, array &$inVolume, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        $money_flow_Idx    = 0;
        $maxIdx_money_flow = (50 - 1);
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        {
            if ($optInTimePeriod <= 0) {
                return ReturnCode::AllocError;
            }
            $money_flow = \array_pad([], $optInTimePeriod, new MoneyFlow());
            for ($_money_flow_index = 0; $_money_flow_index < $optInTimePeriod; $_money_flow_index++) {
                $money_flow[$_money_flow_index] = new MoneyFlow();
            }
            $maxIdx_money_flow = ($optInTimePeriod - 1);
        };
        $outBegIdx     = 0;
        $outNBElement  = 0;
        $lookbackTotal = $optInTimePeriod + (static::$unstablePeriod[UnstablePeriodFunctionID::MFI]);
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            return ReturnCode::Success;
        }
        $outIdx    = 0;
        $today     = $startIdx - $lookbackTotal;
        $prevValue = ($inHigh[$today] + $inLow[$today] + $inClose[$today]) / 3.0;
        $posSumMF  = 0.0;
        $negSumMF  = 0.0;
        $today++;
        for ($i = $optInTimePeriod; $i > 0; $i--) {
            $tempValue1 = ($inHigh[$today] + $inLow[$today] + $inClose[$today]) / 3.0;
            $tempValue2 = $tempValue1 - $prevValue;
            $prevValue  = $tempValue1;
            $tempValue1 *= $inVolume[$today++];
            if ($tempValue2 < 0) {
                ($money_flow[$money_flow_Idx])->negative = $tempValue1;
                $negSumMF                                += $tempValue1;
                ($money_flow[$money_flow_Idx])->positive = 0.0;
            } elseif ($tempValue2 > 0) {
                ($money_flow[$money_flow_Idx])->positive = $tempValue1;
                $posSumMF                                += $tempValue1;
                ($money_flow[$money_flow_Idx])->negative = 0.0;
            } else {
                ($money_flow[$money_flow_Idx])->positive = 0.0;
                ($money_flow[$money_flow_Idx])->negative = 0.0;
            }
            {
                $money_flow_Idx++;
                if ($money_flow_Idx > $maxIdx_money_flow) {
                    $money_flow_Idx = 0;
                }
            };
        }
        if ($today > $startIdx) {
            $tempValue1 = $posSumMF + $negSumMF;
            if ($tempValue1 < 1.0) {
                $outReal[$outIdx++] = 0.0;
            } else {
                $outReal[$outIdx++] = 100.0 * ($posSumMF / $tempValue1);
            }
        } else {
            while ($today < $startIdx) {
                $posSumMF   -= ($money_flow[$money_flow_Idx])->positive;
                $negSumMF   -= ($money_flow[$money_flow_Idx])->negative;
                $tempValue1 = ($inHigh[$today] + $inLow[$today] + $inClose[$today]) / 3.0;
                $tempValue2 = $tempValue1 - $prevValue;
                $prevValue  = $tempValue1;
                $tempValue1 *= $inVolume[$today++];
                if ($tempValue2 < 0) {
                    ($money_flow[$money_flow_Idx])->negative = $tempValue1;
                    $negSumMF                                += $tempValue1;
                    ($money_flow[$money_flow_Idx])->positive = 0.0;
                } elseif ($tempValue2 > 0) {
                    ($money_flow[$money_flow_Idx])->positive = $tempValue1;
                    $posSumMF                                += $tempValue1;
                    ($money_flow[$money_flow_Idx])->negative = 0.0;
                } else {
                    ($money_flow[$money_flow_Idx])->positive = 0.0;
                    ($money_flow[$money_flow_Idx])->negative = 0.0;
                }
                {
                    $money_flow_Idx++;
                    if ($money_flow_Idx > $maxIdx_money_flow) {
                        $money_flow_Idx = 0;
                    }
                };
            }
        }
        while ($today <= $endIdx) {
            $posSumMF   -= ($money_flow[$money_flow_Idx])->positive;
            $negSumMF   -= ($money_flow[$money_flow_Idx])->negative;
            $tempValue1 = ($inHigh[$today] + $inLow[$today] + $inClose[$today]) / 3.0;
            $tempValue2 = $tempValue1 - $prevValue;
            $prevValue  = $tempValue1;
            $tempValue1 *= $inVolume[$today++];
            if ($tempValue2 < 0) {
                ($money_flow[$money_flow_Idx])->negative = $tempValue1;
                $negSumMF                                += $tempValue1;
                ($money_flow[$money_flow_Idx])->positive = 0.0;
            } elseif ($tempValue2 > 0) {
                ($money_flow[$money_flow_Idx])->positive = $tempValue1;
                $posSumMF                                += $tempValue1;
                ($money_flow[$money_flow_Idx])->negative = 0.0;
            } else {
                ($money_flow[$money_flow_Idx])->positive = 0.0;
                ($money_flow[$money_flow_Idx])->negative = 0.0;
            }
            $tempValue1 = $posSumMF + $negSumMF;
            if ($tempValue1 < 1.0) {
                $outReal[$outIdx++] = 0.0;
            } else {
                $outReal[$outIdx++] = 100.0 * ($posSumMF / $tempValue1);
            }
            {
                $money_flow_Idx++;
                if ($money_flow_Idx > $maxIdx_money_flow) {
                    $money_flow_Idx = 0;
                }
            };
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function minusDI(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($optInTimePeriod > 1) {
            $lookbackTotal = $optInTimePeriod + (static::$unstablePeriod[UnstablePeriodFunctionID::MinusDI]);
        } else {
            $lookbackTotal = 1;
        }
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx = 0;
        if ($optInTimePeriod <= 1) {
            $outBegIdx = $startIdx;
            $today     = $startIdx - 1;
            $prevHigh  = $inHigh[$today];
            $prevLow   = $inLow[$today];
            $prevClose = $inClose[$today];
            while ($today < $endIdx) {
                $today++;
                $tempReal = $inHigh[$today];
                $diffP    = $tempReal - $prevHigh;
                $prevHigh = $tempReal;
                $tempReal = $inLow[$today];
                $diffM    = $prevLow - $tempReal;
                $prevLow  = $tempReal;
                if (($diffM > 0) && ($diffP < $diffM)) {
                    {
                        $tempReal  = $prevHigh - $prevLow;
                        $tempReal2 = abs($prevHigh - $prevClose);
                        if ($tempReal2 > $tempReal) {
                            $tempReal = $tempReal2;
                        }
                        $tempReal2 = abs($prevLow - $prevClose);
                        if ($tempReal2 > $tempReal) {
                            $tempReal = $tempReal2;
                        }
                    };
                    if ((((-0.00000001) < $tempReal) && ($tempReal < 0.00000001))) {
                        $outReal[$outIdx++] = (double)0.0;
                    } else {
                        $outReal[$outIdx++] = $diffM / $tempReal;
                    }
                } else {
                    $outReal[$outIdx++] = (double)0.0;
                }
                $prevClose = $inClose[$today];
            }
            $outNBElement = $outIdx;
            return ReturnCode::Success;
        }
        $outBegIdx   = $today = $startIdx;
        $prevMinusDM = 0.0;
        $prevTR      = 0.0;
        $today       = $startIdx - $lookbackTotal;
        $prevHigh    = $inHigh[$today];
        $prevLow     = $inLow[$today];
        $prevClose   = $inClose[$today];
        $i           = $optInTimePeriod - 1;
        while ($i-- > 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    += $tempReal;
            $prevClose = $inClose[$today];
        }
        $i = (static::$unstablePeriod[UnstablePeriodFunctionID::MinusDI]) + 1;
        while ($i-- != 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod) + $diffM;
            } else {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod);
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
        }
        if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
            $outReal[0] = (100.0 * ($prevMinusDM / $prevTR));
        } else {
            $outReal[0] = 0.0;
        }
        $outIdx = 1;
        while ($today < $endIdx) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod) + $diffM;
            } else {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod);
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
            if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
                $outReal[$outIdx++] = (100.0 * ($prevMinusDM / $prevTR));
            } else {
                $outReal[$outIdx++] = 0.0;
            }
        }
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function minusDM(int $startIdx, int $endIdx, array $inHigh, array $inLow, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($optInTimePeriod > 1) {
            $lookbackTotal = $optInTimePeriod + (static::$unstablePeriod[UnstablePeriodFunctionID::MinusDM]) - 1;
        } else {
            $lookbackTotal = 1;
        }
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx = 0;
        if ($optInTimePeriod <= 1) {
            $outBegIdx = $startIdx;
            $today     = $startIdx - 1;
            $prevHigh  = $inHigh[$today];
            $prevLow   = $inLow[$today];
            while ($today < $endIdx) {
                $today++;
                $tempReal = $inHigh[$today];
                $diffP    = $tempReal - $prevHigh;
                $prevHigh = $tempReal;
                $tempReal = $inLow[$today];
                $diffM    = $prevLow - $tempReal;
                $prevLow  = $tempReal;
                if (($diffM > 0) && ($diffP < $diffM)) {
                    $outReal[$outIdx++] = $diffM;
                } else {
                    $outReal[$outIdx++] = 0;
                }
            }
            $outNBElement = $outIdx;
            return ReturnCode::Success;
        }
        $outBegIdx   = $startIdx;
        $prevMinusDM = 0.0;
        $today       = $startIdx - $lookbackTotal;
        $prevHigh    = $inHigh[$today];
        $prevLow     = $inLow[$today];
        $i           = $optInTimePeriod - 1;
        while ($i-- > 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM += $diffM;
            }
        }
        $i = (static::$unstablePeriod[UnstablePeriodFunctionID::MinusDM]);
        while ($i-- != 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod) + $diffM;
            } else {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod);
            }
        }
        $outReal[0] = $prevMinusDM;
        $outIdx     = 1;
        while ($today < $endIdx) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffM > 0) && ($diffP < $diffM)) {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod) + $diffM;
            } else {
                $prevMinusDM = $prevMinusDM - ($prevMinusDM / $optInTimePeriod);
            }
            $outReal[$outIdx++] = $prevMinusDM;
        }
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function mom(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 10;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($startIdx < $optInTimePeriod) {
            $startIdx = $optInTimePeriod;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $inIdx       = $startIdx;
        $trailingIdx = $startIdx - $optInTimePeriod;
        while ($inIdx <= $endIdx) {
            $outReal[$outIdx++] = $inReal[$inIdx++] - $inReal[$trailingIdx++];
        }
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function plusDI(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($optInTimePeriod > 1) {
            $lookbackTotal = $optInTimePeriod + (static::$unstablePeriod[UnstablePeriodFunctionID::PlusDI]);
        } else {
            $lookbackTotal = 1;
        }
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx = 0;
        if ($optInTimePeriod <= 1) {
            $outBegIdx = $startIdx;
            $today     = $startIdx - 1;
            $prevHigh  = $inHigh[$today];
            $prevLow   = $inLow[$today];
            $prevClose = $inClose[$today];
            while ($today < $endIdx) {
                $today++;
                $tempReal = $inHigh[$today];
                $diffP    = $tempReal - $prevHigh;
                $prevHigh = $tempReal;
                $tempReal = $inLow[$today];
                $diffM    = $prevLow - $tempReal;
                $prevLow  = $tempReal;
                if (($diffP > 0) && ($diffP > $diffM)) {
                    {
                        $tempReal  = $prevHigh - $prevLow;
                        $tempReal2 = abs($prevHigh - $prevClose);
                        if ($tempReal2 > $tempReal) {
                            $tempReal = $tempReal2;
                        }
                        $tempReal2 = abs($prevLow - $prevClose);
                        if ($tempReal2 > $tempReal) {
                            $tempReal = $tempReal2;
                        }
                    };
                    if ((((-0.00000001) < $tempReal) && ($tempReal < 0.00000001))) {
                        $outReal[$outIdx++] = (double)0.0;
                    } else {
                        $outReal[$outIdx++] = $diffP / $tempReal;
                    }
                } else {
                    $outReal[$outIdx++] = (double)0.0;
                }
                $prevClose = $inClose[$today];
            }
            $outNBElement = $outIdx;
            return ReturnCode::Success;
        }
        $outBegIdx  = $today = $startIdx;
        $prevPlusDM = 0.0;
        $prevTR     = 0.0;
        $today      = $startIdx - $lookbackTotal;
        $prevHigh   = $inHigh[$today];
        $prevLow    = $inLow[$today];
        $prevClose  = $inClose[$today];
        $i          = $optInTimePeriod - 1;
        while ($i-- > 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    += $tempReal;
            $prevClose = $inClose[$today];
        }
        $i = (static::$unstablePeriod[UnstablePeriodFunctionID::PlusDI]) + 1;
        while ($i-- != 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod) + $diffP;
            } else {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod);
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
        }
        if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
            $outReal[0] = (100.0 * ($prevPlusDM / $prevTR));
        } else {
            $outReal[0] = 0.0;
        }
        $outIdx = 1;
        while ($today < $endIdx) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod) + $diffP;
            } else {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod);
            }
            {
                $tempReal  = $prevHigh - $prevLow;
                $tempReal2 = abs($prevHigh - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
                $tempReal2 = abs($prevLow - $prevClose);
                if ($tempReal2 > $tempReal) {
                    $tempReal = $tempReal2;
                }
            };
            $prevTR    = $prevTR - ($prevTR / $optInTimePeriod) + $tempReal;
            $prevClose = $inClose[$today];
            if (!(((-0.00000001) < $prevTR) && ($prevTR < 0.00000001))) {
                $outReal[$outIdx++] = (100.0 * ($prevPlusDM / $prevTR));
            } else {
                $outReal[$outIdx++] = 0.0;
            }
        }
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function plusDM(int $startIdx, int $endIdx, array $inHigh, array $inLow, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($optInTimePeriod > 1) {
            $lookbackTotal = $optInTimePeriod + (static::$unstablePeriod[UnstablePeriodFunctionID::PlusDM]) - 1;
        } else {
            $lookbackTotal = 1;
        }
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx = 0;
        if ($optInTimePeriod <= 1) {
            $outBegIdx = $startIdx;
            $today     = $startIdx - 1;
            $prevHigh  = $inHigh[$today];
            $prevLow   = $inLow[$today];
            while ($today < $endIdx) {
                $today++;
                $tempReal = $inHigh[$today];
                $diffP    = $tempReal - $prevHigh;
                $prevHigh = $tempReal;
                $tempReal = $inLow[$today];
                $diffM    = $prevLow - $tempReal;
                $prevLow  = $tempReal;
                if (($diffP > 0) && ($diffP > $diffM)) {
                    $outReal[$outIdx++] = $diffP;
                } else {
                    $outReal[$outIdx++] = 0;
                }
            }
            $outNBElement = $outIdx;
            return ReturnCode::Success;
        }
        $outBegIdx  = $startIdx;
        $prevPlusDM = 0.0;
        $today      = $startIdx - $lookbackTotal;
        $prevHigh   = $inHigh[$today];
        $prevLow    = $inLow[$today];
        $i          = $optInTimePeriod - 1;
        while ($i-- > 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM += $diffP;
            }
        }
        $i = (static::$unstablePeriod[UnstablePeriodFunctionID::PlusDM]);
        while ($i-- != 0) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod) + $diffP;
            } else {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod);
            }
        }
        $outReal[0] = $prevPlusDM;
        $outIdx     = 1;
        while ($today < $endIdx) {
            $today++;
            $tempReal = $inHigh[$today];
            $diffP    = $tempReal - $prevHigh;
            $prevHigh = $tempReal;
            $tempReal = $inLow[$today];
            $diffM    = $prevLow - $tempReal;
            $prevLow  = $tempReal;
            if (($diffP > 0) && ($diffP > $diffM)) {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod) + $diffP;
            } else {
                $prevPlusDM = $prevPlusDM - ($prevPlusDM / $optInTimePeriod);
            }
            $outReal[$outIdx++] = $prevPlusDM;
        }
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInFastPeriod
     * @param int   $optInSlowPeriod
     * @param int   $optInMAType
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function ppo(int $startIdx, int $endIdx, array $inReal, int $optInFastPeriod, int $optInSlowPeriod, int $optInMAType, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInFastPeriod == (PHP_INT_MIN)) {
            $optInFastPeriod = 12;
        } elseif (((int)$optInFastPeriod < 2) || ((int)$optInFastPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSlowPeriod == (PHP_INT_MIN)) {
            $optInSlowPeriod = 26;
        } elseif (((int)$optInSlowPeriod < 2) || ((int)$optInSlowPeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $tempBuffer = static::double($endIdx - $startIdx + 1);
        $one        = 1;
        $ReturnCode = static::TA_INT_PO(
            $startIdx, $endIdx, $inReal,
            $optInFastPeriod,
            $optInSlowPeriod,
            $optInMAType,
            $outBegIdx,
            $outNBElement,
            $outReal,
            $tempBuffer,
            $one
        );
        return $ReturnCode;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function roc(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 10;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($startIdx < $optInTimePeriod) {
            $startIdx = $optInTimePeriod;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $inIdx       = $startIdx;
        $trailingIdx = $startIdx - $optInTimePeriod;
        while ($inIdx <= $endIdx) {
            $tempReal = $inReal[$trailingIdx++];
            if ($tempReal != 0.0) {
                $outReal[$outIdx++] = (($inReal[$inIdx] / $tempReal) - 1.0) * 100.0;
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            $inIdx++;
        }
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function rocP(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 10;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($startIdx < $optInTimePeriod) {
            $startIdx = $optInTimePeriod;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $inIdx       = $startIdx;
        $trailingIdx = $startIdx - $optInTimePeriod;
        while ($inIdx <= $endIdx) {
            $tempReal = $inReal[$trailingIdx++];
            if ($tempReal != 0.0) {
                $outReal[$outIdx++] = ($inReal[$inIdx] - $tempReal) / $tempReal;
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            $inIdx++;
        }
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function rocR(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 10;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($startIdx < $optInTimePeriod) {
            $startIdx = $optInTimePeriod;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $inIdx       = $startIdx;
        $trailingIdx = $startIdx - $optInTimePeriod;
        while ($inIdx <= $endIdx) {
            $tempReal = $inReal[$trailingIdx++];
            if ($tempReal != 0.0) {
                $outReal[$outIdx++] = ($inReal[$inIdx] / $tempReal);
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            $inIdx++;
        }
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function rocR100(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 10;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ($startIdx < $optInTimePeriod) {
            $startIdx = $optInTimePeriod;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $inIdx       = $startIdx;
        $trailingIdx = $startIdx - $optInTimePeriod;
        while ($inIdx <= $endIdx) {
            $tempReal = $inReal[$trailingIdx++];
            if ($tempReal != 0.0) {
                $outReal[$outIdx++] = ($inReal[$inIdx] / $tempReal) * 100.0;
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            $inIdx++;
        }
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function rsi(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $outBegIdx     = 0;
        $outNBElement  = 0;
        $lookbackTotal = Lookback::rsiLookback($optInTimePeriod);
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            return ReturnCode::Success;
        }
        $outIdx = 0;
        if ($optInTimePeriod == 1) {
            $outBegIdx    = $startIdx;
            $i            = ($endIdx - $startIdx) + 1;
            $outNBElement = $i;
            //System::arraycopy($inReal, $startIdx, $outReal, 0, $i);
            $outReal = \array_slice($inReal, $startIdx, $i);
            return ReturnCode::Success;
        }
        $today          = $startIdx - $lookbackTotal;
        $prevValue      = $inReal[$today];
        $unstablePeriod = (static::$unstablePeriod[UnstablePeriodFunctionID::RSI]);
        if (($unstablePeriod == 0) &&
            ((static::$compatibility) == Compatibility::Metastock)) {
            $savePrevValue = $prevValue;
            $prevGain      = 0.0;
            $prevLoss      = 0.0;
            for ($i = $optInTimePeriod; $i > 0; $i--) {
                $tempValue1 = $inReal[$today++];
                $tempValue2 = $tempValue1 - $prevValue;
                $prevValue  = $tempValue1;
                if ($tempValue2 < 0) {
                    $prevLoss -= $tempValue2;
                } else {
                    $prevGain += $tempValue2;
                }
            }
            $tempValue1 = $prevLoss / $optInTimePeriod;
            $tempValue2 = $prevGain / $optInTimePeriod;
            $tempValue1 = $tempValue2 + $tempValue1;
            if (!(((-0.00000001) < $tempValue1) && ($tempValue1 < 0.00000001))) {
                $outReal[$outIdx++] = 100 * ($tempValue2 / $tempValue1);
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            if ($today > $endIdx) {
                $outBegIdx    = $startIdx;
                $outNBElement = $outIdx;
                return ReturnCode::Success;
            }
            $today     -= $optInTimePeriod;
            $prevValue = $savePrevValue;
        }
        $prevGain = 0.0;
        $prevLoss = 0.0;
        $today++;
        for ($i = $optInTimePeriod; $i > 0; $i--) {
            $tempValue1 = $inReal[$today++];
            $tempValue2 = $tempValue1 - $prevValue;
            $prevValue  = $tempValue1;
            if ($tempValue2 < 0) {
                $prevLoss -= $tempValue2;
            } else {
                $prevGain += $tempValue2;
            }
        }
        $prevLoss /= $optInTimePeriod;
        $prevGain /= $optInTimePeriod;
        if ($today > $startIdx) {
            $tempValue1 = $prevGain + $prevLoss;
            if (!(((-0.00000001) < $tempValue1) && ($tempValue1 < 0.00000001))) {
                $outReal[$outIdx++] = 100.0 * ($prevGain / $tempValue1);
            } else {
                $outReal[$outIdx++] = 0.0;
            }
        } else {
            while ($today < $startIdx) {
                $tempValue1 = $inReal[$today];
                $tempValue2 = $tempValue1 - $prevValue;
                $prevValue  = $tempValue1;
                $prevLoss   *= ($optInTimePeriod - 1);
                $prevGain   *= ($optInTimePeriod - 1);
                if ($tempValue2 < 0) {
                    $prevLoss -= $tempValue2;
                } else {
                    $prevGain += $tempValue2;
                }
                $prevLoss /= $optInTimePeriod;
                $prevGain /= $optInTimePeriod;
                $today++;
            }
        }
        while ($today <= $endIdx) {
            $tempValue1 = $inReal[$today++];
            $tempValue2 = $tempValue1 - $prevValue;
            $prevValue  = $tempValue1;
            $prevLoss   *= ($optInTimePeriod - 1);
            $prevGain   *= ($optInTimePeriod - 1);
            if ($tempValue2 < 0) {
                $prevLoss -= $tempValue2;
            } else {
                $prevGain += $tempValue2;
            }
            $prevLoss   /= $optInTimePeriod;
            $prevGain   /= $optInTimePeriod;
            $tempValue1 = $prevGain + $prevLoss;
            if (!(((-0.00000001) < $tempValue1) && ($tempValue1 < 0.00000001))) {
                $outReal[$outIdx++] = 100.0 * ($prevGain / $tempValue1);
            } else {
                $outReal[$outIdx++] = 0.0;
            }
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param int   $optInFastK_Period
     * @param int   $optInSlowK_Period
     * @param int   $optInSlowK_MAType
     * @param int   $optInSlowD_Period
     * @param int   $optInSlowD_MAType
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outSlowK
     * @param array $outSlowD
     *
     * @return int
     */
    public static function stoch(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInFastK_Period, int $optInSlowK_Period, int $optInSlowK_MAType, int $optInSlowD_Period, int $optInSlowD_MAType, int &$outBegIdx, int &$outNBElement, array &$outSlowK, array &$outSlowD): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInFastK_Period == (PHP_INT_MIN)) {
            $optInFastK_Period = 5;
        } elseif (((int)$optInFastK_Period < 1) || ((int)$optInFastK_Period > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSlowK_Period == (PHP_INT_MIN)) {
            $optInSlowK_Period = 3;
        } elseif (((int)$optInSlowK_Period < 1) || ((int)$optInSlowK_Period > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInSlowD_Period == (PHP_INT_MIN)) {
            $optInSlowD_Period = 3;
        } elseif (((int)$optInSlowD_Period < 1) || ((int)$optInSlowD_Period > 100000)) {
            return ReturnCode::BadParam;
        }
        $lookbackK     = $optInFastK_Period - 1;
        $lookbackKSlow = Lookback::movingAverageLookback($optInSlowK_Period, $optInSlowK_MAType);
        $lookbackDSlow = Lookback::movingAverageLookback($optInSlowD_Period, $optInSlowD_MAType);
        $lookbackTotal = $lookbackK + $lookbackDSlow + $lookbackKSlow;
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $trailingIdx = $startIdx - $lookbackTotal;
        $today       = $trailingIdx + $lookbackK;
        $lowestIdx   = $highestIdx = -1;
        $diff        = $highest = $lowest = 0.0;
        if (($outSlowK == $inHigh) ||
            ($outSlowK == $inLow) ||
            ($outSlowK == $inClose)) {
            $tempBuffer = $outSlowK;
        } elseif (($outSlowD == $inHigh) ||
                  ($outSlowD == $inLow) ||
                  ($outSlowD == $inClose)) {
            $tempBuffer = $outSlowD;
        } else {
            $tempBuffer = static::double($endIdx - $today + 1);
        }
        while ($today <= $endIdx) {
            $tmp = $inLow[$today];
            if ($lowestIdx < $trailingIdx) {
                $lowestIdx = $trailingIdx;
                $lowest    = $inLow[$lowestIdx];
                $i         = $lowestIdx;
                while (++$i <= $today) {
                    $tmp = $inLow[$i];
                    if ($tmp < $lowest) {
                        $lowestIdx = $i;
                        $lowest    = $tmp;
                    }
                }
                $diff = ($highest - $lowest) / 100.0;
            } elseif ($tmp <= $lowest) {
                $lowestIdx = $today;
                $lowest    = $tmp;
                $diff      = ($highest - $lowest) / 100.0;
            }
            $tmp = $inHigh[$today];
            if ($highestIdx < $trailingIdx) {
                $highestIdx = $trailingIdx;
                $highest    = $inHigh[$highestIdx];
                $i          = $highestIdx;
                while (++$i <= $today) {
                    $tmp = $inHigh[$i];
                    if ($tmp > $highest) {
                        $highestIdx = $i;
                        $highest    = $tmp;
                    }
                }
                $diff = ($highest - $lowest) / 100.0;
            } elseif ($tmp >= $highest) {
                $highestIdx = $today;
                $highest    = $tmp;
                $diff       = ($highest - $lowest) / 100.0;
            }
            if ($diff != 0.0) {
                $tempBuffer[$outIdx++] = ($inClose[$today] - $lowest) / $diff;
            } else {
                $tempBuffer[$outIdx++] = 0.0;
            }
            $trailingIdx++;
            $today++;
        }
        $ReturnCode = OverlapStudies::movingAverage(
            0, $outIdx - 1,
            $tempBuffer, $optInSlowK_Period,
            $optInSlowK_MAType,
            $outBegIdx, $outNBElement, $tempBuffer
        );
        if (($ReturnCode != ReturnCode::Success) || ((int)$outNBElement == 0)) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        $ReturnCode = OverlapStudies::movingAverage(
            0, (int)$outNBElement - 1,
            $tempBuffer, $optInSlowD_Period,
            $optInSlowD_MAType,
            $outBegIdx, $outNBElement, $outSlowD
        );
        //System::arraycopy($tempBuffer, $lookbackDSlow, $outSlowK, 0, (int)$outNBElement);
        $outSlowK = \array_slice($tempBuffer, $lookbackDSlow, (int)$outNBElement);
        if ($ReturnCode != ReturnCode::Success) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        $outBegIdx = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param int   $optInFastK_Period
     * @param int   $optInFastD_Period
     * @param int   $optInFastD_MAType
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outFastK
     * @param array $outFastD
     *
     * @return int
     */
    public static function stochF(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInFastK_Period, int $optInFastD_Period, int $optInFastD_MAType, int &$outBegIdx, int &$outNBElement, array &$outFastK, array &$outFastD): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInFastK_Period == (PHP_INT_MIN)) {
            $optInFastK_Period = 5;
        } elseif (((int)$optInFastK_Period < 1) || ((int)$optInFastK_Period > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInFastD_Period == (PHP_INT_MIN)) {
            $optInFastD_Period = 3;
        } elseif (((int)$optInFastD_Period < 1) || ((int)$optInFastD_Period > 100000)) {
            return ReturnCode::BadParam;
        }
        $lookbackK     = $optInFastK_Period - 1;
        $lookbackFastD = Lookback::movingAverageLookback($optInFastD_Period, $optInFastD_MAType);
        $lookbackTotal = $lookbackK + $lookbackFastD;
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outIdx      = 0;
        $trailingIdx = $startIdx - $lookbackTotal;
        $today       = $trailingIdx + $lookbackK;
        $lowestIdx   = $highestIdx = -1;
        $diff        = $highest = $lowest = 0.0;
        if (($outFastK == $inHigh) ||
            ($outFastK == $inLow) ||
            ($outFastK == $inClose)) {
            $tempBuffer = $outFastK;
        } elseif (($outFastD == $inHigh) ||
                  ($outFastD == $inLow) ||
                  ($outFastD == $inClose)) {
            $tempBuffer = $outFastD;
        } else {
            $tempBuffer = static::double($endIdx - $today + 1);
        }
        while ($today <= $endIdx) {
            $tmp = $inLow[$today];
            if ($lowestIdx < $trailingIdx) {
                $lowestIdx = $trailingIdx;
                $lowest    = $inLow[$lowestIdx];
                $i         = $lowestIdx;
                while (++$i <= $today) {
                    $tmp = $inLow[$i];
                    if ($tmp < $lowest) {
                        $lowestIdx = $i;
                        $lowest    = $tmp;
                    }
                }
                $diff = ($highest - $lowest) / 100.0;
            } elseif ($tmp <= $lowest) {
                $lowestIdx = $today;
                $lowest    = $tmp;
                $diff      = ($highest - $lowest) / 100.0;
            }
            $tmp = $inHigh[$today];
            if ($highestIdx < $trailingIdx) {
                $highestIdx = $trailingIdx;
                $highest    = $inHigh[$highestIdx];
                $i          = $highestIdx;
                while (++$i <= $today) {
                    $tmp = $inHigh[$i];
                    if ($tmp > $highest) {
                        $highestIdx = $i;
                        $highest    = $tmp;
                    }
                }
                $diff = ($highest - $lowest) / 100.0;
            } elseif ($tmp >= $highest) {
                $highestIdx = $today;
                $highest    = $tmp;
                $diff       = ($highest - $lowest) / 100.0;
            }
            if ($diff != 0.0) {
                $tempBuffer[$outIdx++] = ($inClose[$today] - $lowest) / $diff;
            } else {
                $tempBuffer[$outIdx++] = 0.0;
            }
            $trailingIdx++;
            $today++;
        }
        $ReturnCode = OverlapStudies::movingAverage(
            0, $outIdx - 1,
            $tempBuffer, $optInFastD_Period,
            $optInFastD_MAType,
            $outBegIdx, $outNBElement, $outFastD
        );
        if (($ReturnCode != ReturnCode::Success) || ((int)$outNBElement) == 0) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        //System::arraycopy($tempBuffer, $lookbackFastD, $outFastK, 0, (int)$outNBElement);
        $outFastK = \array_slice($tempBuffer, $lookbackFastD, (int)$outNBElement);
        if ($ReturnCode != ReturnCode::Success) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        $outBegIdx = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $optInFastK_Period
     * @param int   $optInFastD_Period
     * @param int   $optInFastD_MAType
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outFastK
     * @param array $outFastD
     *
     * @return int
     */
    public static function stochRsi(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int $optInFastK_Period, int $optInFastD_Period, int $optInFastD_MAType, int &$outBegIdx, int &$outNBElement, array &$outFastK, array &$outFastD): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        $outBegIdx1    = 0;
        $outBegIdx2    = 0;
        $outNbElement1 = 0;
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInFastK_Period == (PHP_INT_MIN)) {
            $optInFastK_Period = 5;
        } elseif (((int)$optInFastK_Period < 1) || ((int)$optInFastK_Period > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInFastD_Period == (PHP_INT_MIN)) {
            $optInFastD_Period = 3;
        } elseif (((int)$optInFastD_Period < 1) || ((int)$optInFastD_Period > 100000)) {
            return ReturnCode::BadParam;
        }
        $outBegIdx      = 0;
        $outNBElement   = 0;
        $lookbackSTOCHF = Lookback::stochFLookback($optInFastK_Period, $optInFastD_Period, $optInFastD_MAType);
        $lookbackTotal  = Lookback::rsiLookback($optInTimePeriod) + $lookbackSTOCHF;
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $outBegIdx     = $startIdx;
        $tempArraySize = ($endIdx - $startIdx) + 1 + $lookbackSTOCHF;
        $tempRSIBuffer = static::double($tempArraySize);
        $ReturnCode    = self::rsi(
            $startIdx - $lookbackSTOCHF,
            $endIdx,
            $inReal,
            $optInTimePeriod,
            $outBegIdx1,
            $outNbElement1,
            $tempRSIBuffer
        );
        if ($ReturnCode != ReturnCode::Success || $outNbElement1 == 0) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        $ReturnCode = self::stochF(
            0,
            $tempArraySize - 1,
            $tempRSIBuffer,
            $tempRSIBuffer,
            $tempRSIBuffer,
            $optInFastK_Period,
            $optInFastD_Period,
            $optInFastD_MAType,
            $outBegIdx2,
            $outNBElement,
            $outFastK,
            $outFastD
        );
        if ($ReturnCode != ReturnCode::Success || ((int)$outNBElement) == 0) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return $ReturnCode;
        }
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inReal
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function trix(int $startIdx, int $endIdx, array $inReal, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        $nbElement = 0;
        $begIdx    = 0;
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 30;
        } elseif (((int)$optInTimePeriod < 1) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $emaLookback   = Lookback::emaLookback($optInTimePeriod);
        $rocLookback   = Lookback::rocRLookback(1);
        $totalLookback = ($emaLookback * 3) + $rocLookback;
        if ($startIdx < $totalLookback) {
            $startIdx = $totalLookback;
        }
        if ($startIdx > $endIdx) {
            $outNBElement = 0;
            $outBegIdx    = 0;
            return ReturnCode::Success;
        }
        $outBegIdx         = $startIdx;
        $nbElementToOutput = ($endIdx - $startIdx) + 1 + $totalLookback;
        $tempBuffer        = static::double($nbElementToOutput);
        $k                 = ((double)2.0 / ((double)($optInTimePeriod + 1)));
        $ReturnCode        = static::TA_INT_EMA(
            ($startIdx - $totalLookback), $endIdx, $inReal,
                                          $optInTimePeriod, $k,
                                          $begIdx, $nbElement,
                                          $tempBuffer
        );
        if (($ReturnCode != ReturnCode::Success) || ($nbElement == 0)) {
            $outNBElement = 0;
            $outBegIdx    = 0;
            return $ReturnCode;
        }
        $nbElementToOutput--;
        $nbElementToOutput -= $emaLookback;
        $ReturnCode        = static::TA_INT_EMA(
            0, $nbElementToOutput, $tempBuffer,
            $optInTimePeriod, $k,
            $begIdx, $nbElement,
            $tempBuffer
        );
        if (($ReturnCode != ReturnCode::Success) || ($nbElement == 0)) {
            $outNBElement = 0;
            $outBegIdx    = 0;
            return $ReturnCode;
        }
        $nbElementToOutput -= $emaLookback;
        $ReturnCode        = static::TA_INT_EMA(
            0, $nbElementToOutput, $tempBuffer,
            $optInTimePeriod, $k,
            $begIdx, $nbElement,
            $tempBuffer
        );
        if (($ReturnCode != ReturnCode::Success) || ($nbElement == 0)) {
            $outNBElement = 0;
            $outBegIdx    = 0;
            return $ReturnCode;
        }
        $nbElementToOutput -= $emaLookback;
        $ReturnCode        = self::roc(
            0, $nbElementToOutput,
            $tempBuffer,
            1, $begIdx, $outNBElement,
            $outReal
        );
        if (($ReturnCode != ReturnCode::Success) || ((int)$outNBElement == 0)) {
            $outNBElement = 0;
            $outBegIdx    = 0;
            return $ReturnCode;
        }
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param int   $optInTimePeriod1
     * @param int   $optInTimePeriod2
     * @param int   $optInTimePeriod3
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function ultOsc(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod1, int $optInTimePeriod2, int $optInTimePeriod3, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        $usedFlag      = \array_pad([], 3, 0);
        $periods       = \array_pad([], 3, 0);
        $sortedPeriods = \array_pad([], 3, 0);
        if ((int)$optInTimePeriod1 == (PHP_INT_MIN)) {
            $optInTimePeriod1 = 7;
        } elseif (((int)$optInTimePeriod1 < 1) || ((int)$optInTimePeriod1 > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInTimePeriod2 == (PHP_INT_MIN)) {
            $optInTimePeriod2 = 14;
        } elseif (((int)$optInTimePeriod2 < 1) || ((int)$optInTimePeriod2 > 100000)) {
            return ReturnCode::BadParam;
        }
        if ((int)$optInTimePeriod3 == (PHP_INT_MIN)) {
            $optInTimePeriod3 = 28;
        } elseif (((int)$optInTimePeriod3 < 1) || ((int)$optInTimePeriod3 > 100000)) {
            return ReturnCode::BadParam;
        }
        $outBegIdx    = 0;
        $outNBElement = 0;
        $periods[0]   = $optInTimePeriod1;
        $periods[1]   = $optInTimePeriod2;
        $periods[2]   = $optInTimePeriod3;
        $usedFlag[0]  = 0;
        $usedFlag[1]  = 0;
        $usedFlag[2]  = 0;
        for ($i = 0; $i < 3; ++$i) {
            $longestPeriod = 0;
            $longestIndex  = 0;
            for ($j = 0; $j < 3; ++$j) {
                if (($usedFlag[$j] == 0) && ($periods[$j] > $longestPeriod)) {
                    $longestPeriod = $periods[$j];
                    $longestIndex  = $j;
                }
            }
            $usedFlag[$longestIndex] = 1;
            $sortedPeriods[$i]       = $longestPeriod;
        }
        $optInTimePeriod1 = $sortedPeriods[2];
        $optInTimePeriod2 = $sortedPeriods[1];
        $optInTimePeriod3 = $sortedPeriods[0];
        $lookbackTotal    = Lookback::ultOscLookback($optInTimePeriod1, $optInTimePeriod2, $optInTimePeriod3);
        if ($startIdx < $lookbackTotal) {
            $startIdx = $lookbackTotal;
        }
        if ($startIdx > $endIdx) {
            return ReturnCode::Success;
        }
        {
            $a1Total = 0;
            $b1Total = 0;
            for ($i = $startIdx - $optInTimePeriod1 + 1; $i < $startIdx; ++$i) {
                {
                    $tempLT            = $inLow[$i];
                    $tempHT            = $inHigh[$i];
                    $tempCY            = $inClose[$i - 1];
                    $trueLow           = ((($tempLT) < ($tempCY)) ? ($tempLT) : ($tempCY));
                    $closeMinusTrueLow = $inClose[$i] - $trueLow;
                    $trueRange         = $tempHT - $tempLT;
                    $tempDouble        = abs($tempCY - $tempHT);
                    if ($tempDouble > $trueRange) {
                        $trueRange = $tempDouble;
                    }
                    $tempDouble = abs($tempCY - $tempLT);
                    if ($tempDouble > $trueRange) {
                        $trueRange = $tempDouble;
                    }
                };
                $a1Total += $closeMinusTrueLow;
                $b1Total += $trueRange;
            }
        };
        {
            $a2Total = 0;
            $b2Total = 0;
            for ($i = $startIdx - $optInTimePeriod2 + 1; $i < $startIdx; ++$i) {
                {
                    $tempLT            = $inLow[$i];
                    $tempHT            = $inHigh[$i];
                    $tempCY            = $inClose[$i - 1];
                    $trueLow           = ((($tempLT) < ($tempCY)) ? ($tempLT) : ($tempCY));
                    $closeMinusTrueLow = $inClose[$i] - $trueLow;
                    $trueRange         = $tempHT - $tempLT;
                    $tempDouble        = abs($tempCY - $tempHT);
                    if ($tempDouble > $trueRange) {
                        $trueRange = $tempDouble;
                    }
                    $tempDouble = abs($tempCY - $tempLT);
                    if ($tempDouble > $trueRange) {
                        $trueRange = $tempDouble;
                    }
                };
                $a2Total += $closeMinusTrueLow;
                $b2Total += $trueRange;
            }
        };
        {
            $a3Total = 0;
            $b3Total = 0;
            for ($i = $startIdx - $optInTimePeriod3 + 1; $i < $startIdx; ++$i) {
                {
                    $tempLT            = $inLow[$i];
                    $tempHT            = $inHigh[$i];
                    $tempCY            = $inClose[$i - 1];
                    $trueLow           = ((($tempLT) < ($tempCY)) ? ($tempLT) : ($tempCY));
                    $closeMinusTrueLow = $inClose[$i] - $trueLow;
                    $trueRange         = $tempHT - $tempLT;
                    $tempDouble        = abs($tempCY - $tempHT);
                    if ($tempDouble > $trueRange) {
                        $trueRange = $tempDouble;
                    }
                    $tempDouble = abs($tempCY - $tempLT);
                    if ($tempDouble > $trueRange) {
                        $trueRange = $tempDouble;
                    }
                };
                $a3Total += $closeMinusTrueLow;
                $b3Total += $trueRange;
            }
        };
        $today        = $startIdx;
        $outIdx       = 0;
        $trailingIdx1 = $today - $optInTimePeriod1 + 1;
        $trailingIdx2 = $today - $optInTimePeriod2 + 1;
        $trailingIdx3 = $today - $optInTimePeriod3 + 1;
        while ($today <= $endIdx) {
            {
                $tempLT            = $inLow[$today];
                $tempHT            = $inHigh[$today];
                $tempCY            = $inClose[$today - 1];
                $trueLow           = ((($tempLT) < ($tempCY)) ? ($tempLT) : ($tempCY));
                $closeMinusTrueLow = $inClose[$today] - $trueLow;
                $trueRange         = $tempHT - $tempLT;
                $tempDouble        = abs($tempCY - $tempHT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
                $tempDouble = abs($tempCY - $tempLT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
            };
            $a1Total += $closeMinusTrueLow;
            $a2Total += $closeMinusTrueLow;
            $a3Total += $closeMinusTrueLow;
            $b1Total += $trueRange;
            $b2Total += $trueRange;
            $b3Total += $trueRange;
            $output  = 0.0;
            if (!(((-0.00000001) < $b1Total) && ($b1Total < 0.00000001))) {
                $output += 4.0 * ($a1Total / $b1Total);
            }
            if (!(((-0.00000001) < $b2Total) && ($b2Total < 0.00000001))) {
                $output += 2.0 * ($a2Total / $b2Total);
            }
            if (!(((-0.00000001) < $b3Total) && ($b3Total < 0.00000001))) {
                $output += $a3Total / $b3Total;
            }
            {
                $tempLT            = $inLow[$trailingIdx1];
                $tempHT            = $inHigh[$trailingIdx1];
                $tempCY            = $inClose[$trailingIdx1 - 1];
                $trueLow           = ((($tempLT) < ($tempCY)) ? ($tempLT) : ($tempCY));
                $closeMinusTrueLow = $inClose[$trailingIdx1] - $trueLow;
                $trueRange         = $tempHT - $tempLT;
                $tempDouble        = abs($tempCY - $tempHT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
                $tempDouble = abs($tempCY - $tempLT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
            };
            $a1Total -= $closeMinusTrueLow;
            $b1Total -= $trueRange;
            {
                $tempLT            = $inLow[$trailingIdx2];
                $tempHT            = $inHigh[$trailingIdx2];
                $tempCY            = $inClose[$trailingIdx2 - 1];
                $trueLow           = ((($tempLT) < ($tempCY)) ? ($tempLT) : ($tempCY));
                $closeMinusTrueLow = $inClose[$trailingIdx2] - $trueLow;
                $trueRange         = $tempHT - $tempLT;
                $tempDouble        = abs($tempCY - $tempHT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
                $tempDouble = abs($tempCY - $tempLT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
            };
            $a2Total -= $closeMinusTrueLow;
            $b2Total -= $trueRange;
            {
                $tempLT            = $inLow[$trailingIdx3];
                $tempHT            = $inHigh[$trailingIdx3];
                $tempCY            = $inClose[$trailingIdx3 - 1];
                $trueLow           = ((($tempLT) < ($tempCY)) ? ($tempLT) : ($tempCY));
                $closeMinusTrueLow = $inClose[$trailingIdx3] - $trueLow;
                $trueRange         = $tempHT - $tempLT;
                $tempDouble        = abs($tempCY - $tempHT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
                $tempDouble = abs($tempCY - $tempLT);
                if ($tempDouble > $trueRange) {
                    $trueRange = $tempDouble;
                }
            };
            $a3Total          -= $closeMinusTrueLow;
            $b3Total          -= $trueRange;
            $outReal[$outIdx] = 100.0 * ($output / 7.0);
            $outIdx++;
            $today++;
            $trailingIdx1++;
            $trailingIdx2++;
            $trailingIdx3++;
        }
        $outNBElement = $outIdx;
        $outBegIdx    = $startIdx;
        return ReturnCode::Success;
    }
    /**
     * @param int   $startIdx
     * @param int   $endIdx
     * @param array $inHigh
     * @param array $inLow
     * @param array $inClose
     * @param int   $optInTimePeriod
     * @param int   $outBegIdx
     * @param int   $outNBElement
     * @param array $outReal
     *
     * @return int
     */
    public static function willR(int $startIdx, int $endIdx, array $inHigh, array $inLow, array $inClose, int $optInTimePeriod, int &$outBegIdx, int &$outNBElement, array &$outReal): int
    {
        if ($RetCode = static::validateStartEndIndexes($startIdx, $endIdx)) {
            return $RetCode;
        }
        if ((int)$optInTimePeriod == (PHP_INT_MIN)) {
            $optInTimePeriod = 14;
        } elseif (((int)$optInTimePeriod < 2) || ((int)$optInTimePeriod > 100000)) {
            return ReturnCode::BadParam;
        }
        $nbInitialElementNeeded = ($optInTimePeriod - 1);
        if ($startIdx < $nbInitialElementNeeded) {
            $startIdx = $nbInitialElementNeeded;
        }
        if ($startIdx > $endIdx) {
            $outBegIdx    = 0;
            $outNBElement = 0;
            return ReturnCode::Success;
        }
        $diff        = 0.0;
        $outIdx      = 0;
        $today       = $startIdx;
        $trailingIdx = $startIdx - $nbInitialElementNeeded;
        $lowestIdx   = $highestIdx = -1;
        $diff        = $highest = $lowest = 0.0;
        while ($today <= $endIdx) {
            $tmp = $inLow[$today];
            if ($lowestIdx < $trailingIdx) {
                $lowestIdx = $trailingIdx;
                $lowest    = $inLow[$lowestIdx];
                $i         = $lowestIdx;
                while (++$i <= $today) {
                    $tmp = $inLow[$i];
                    if ($tmp < $lowest) {
                        $lowestIdx = $i;
                        $lowest    = $tmp;
                    }
                }
                $diff = ($highest - $lowest) / (-100.0);
            } elseif ($tmp <= $lowest) {
                $lowestIdx = $today;
                $lowest    = $tmp;
                $diff      = ($highest - $lowest) / (-100.0);
            }
            $tmp = $inHigh[$today];
            if ($highestIdx < $trailingIdx) {
                $highestIdx = $trailingIdx;
                $highest    = $inHigh[$highestIdx];
                $i          = $highestIdx;
                while (++$i <= $today) {
                    $tmp = $inHigh[$i];
                    if ($tmp > $highest) {
                        $highestIdx = $i;
                        $highest    = $tmp;
                    }
                }
                $diff = ($highest - $lowest) / (-100.0);
            } elseif ($tmp >= $highest) {
                $highestIdx = $today;
                $highest    = $tmp;
                $diff       = ($highest - $lowest) / (-100.0);
            }
            if ($diff != 0.0) {
                $outReal[$outIdx++] = ($highest - $inClose[$today]) / $diff;
            } else {
                $outReal[$outIdx++] = 0.0;
            }
            $trailingIdx++;
            $today++;
        }
        $outBegIdx    = $startIdx;
        $outNBElement = $outIdx;
        return ReturnCode::Success;
    }
}