├── .gitignore ├── README.md ├── composer.json └── src ├── ArrayIndicator.php ├── Exceptions ├── CantCompareLessTwoElementsException.php ├── IndicatorNotFoundException.php ├── PeriodCantBeLessNumberException.php └── PeriodCantBeNegativeException.php ├── Indicator.php ├── Indicators.php ├── Indicators ├── ATR.php ├── BBands.php ├── EMA.php ├── MACD.php ├── RMA.php ├── RSI.php ├── SMA.php └── StochRSI.php ├── NumberIndicator.php └── OHLVC.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .idea 4 | /.vscode 5 | /nbproject 6 | /.vagrant 7 | .phpunit.result.cache 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Indicators 2 | 3 | Trading indicators library. It's need for 4 | simplify working with `trader` extension. 5 | 6 | ## Requires 7 | * `php 7.3+` 8 | * `ext-trader` 9 | 10 | ## Install 11 | ```bash 12 | composer install doxadoxa/phpindicators 13 | ``` 14 | 15 | ## Tests 16 | Test not exists yet. 17 | 18 | ## Example 19 | ```php 20 | 21 | $klines = ExternalApi::getKlines('btcusdt', '1m'); 22 | 23 | $ohlvc = new OHLVC( $klines ); 24 | 25 | $sma14 = $ohlvc->close()->sma(14); 26 | $sma3 = $ohlvc->close()->sma(3); 27 | $atr = $ohlvc->atr(3); 28 | 29 | if ( $sma3->crossBelow( $sma14 ) ) { 30 | Trader::setStopLossOn( $sma3[0] - $atr[0] * 10 ); 31 | } 32 | 33 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doxadoxa/php-indicators", 3 | "description": "Indicator library for PHP.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Taras Dovgal", 9 | "email": "td@hrom.fund" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "require": { 14 | "php" : "^7.1", 15 | "ext-trader" : "*" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Doxadoxa\\PhpIndicators\\": "src/" 20 | } 21 | }, 22 | "version": "0.2" 23 | } 24 | -------------------------------------------------------------------------------- /src/ArrayIndicator.php: -------------------------------------------------------------------------------- 1 | indicator = $indicator; 24 | $this->defaultValue = $defaultValue; 25 | 26 | $this->indicators = new Indicators(); 27 | } 28 | 29 | public function value() 30 | { 31 | return $this->last(); 32 | } 33 | 34 | public function last(int $n = 0) 35 | { 36 | if (count($this->indicator) - 1 - $n < 0) { 37 | return $this->defaultValue; 38 | } 39 | 40 | return $this->indicator[count($this->indicator) - 1 - $n]; 41 | } 42 | 43 | public function isExists( int $n = 0) 44 | { 45 | return isset( $this->indicator[count($this->indicator) - 1 - $n] ); 46 | } 47 | 48 | public function first(int $n = 0) 49 | { 50 | return $this->indicator[$n]; 51 | } 52 | 53 | public function crossOver(Indicator $indicator):bool 54 | { 55 | return ($this->value() > $indicator->value() && $this->last(1) < $indicator->last(1)); 56 | } 57 | 58 | public function crossBelow(Indicator $indicator): bool 59 | { 60 | return ($this->value() < $indicator->value() && $this->last(1) > $indicator->last(1)); 61 | } 62 | 63 | public function over(Indicator $indicator):bool 64 | { 65 | return ($this->value() > $indicator->value()); 66 | } 67 | 68 | public function overSeries( ArrayIndicator $indicator ): ArrayIndicator 69 | { 70 | $i = 0; 71 | 72 | $result = []; 73 | 74 | while( isset( $this[ $i ] ) && isset( $indicator[ $i ] ) ) { 75 | array_unshift( $result, $this[ $i ] > $indicator[ $i ] ); 76 | ++$i; 77 | } 78 | 79 | return new ArrayIndicator( $result ); 80 | } 81 | 82 | public function below(Indicator $indicator):bool 83 | { 84 | return ($this->last() < $indicator->last()); 85 | } 86 | 87 | public function belowSeries( ArrayIndicator $indicator ): ArrayIndicator 88 | { 89 | $i = 0; 90 | 91 | $result = []; 92 | 93 | while( isset( $this[ $i ] ) && isset( $indicator[ $i ] ) ) { 94 | array_unshift( $result, $this[ $i ] < $indicator[ $i ] ); 95 | ++$i; 96 | } 97 | 98 | return new ArrayIndicator( $result ); 99 | } 100 | 101 | public function isGrowth(int $position = 0) 102 | { 103 | return $this->last($position) > $this->last($position + 1); 104 | } 105 | 106 | public function isGrowthOrFlat(int $position = 0) 107 | { 108 | return $this->last($position) >= $this->last($position + 1); 109 | } 110 | 111 | public function isFall(int $position = 0) 112 | { 113 | return $this->last($position) < $this->last($position + 1); 114 | } 115 | 116 | public function isFallOrFlat(int $position = 0) 117 | { 118 | return $this->last($position) <= $this->last($position + 1); 119 | } 120 | 121 | public function turnDown() 122 | { 123 | return $this->last(2) < $this->last(1) && $this->last() < $this->last(1); 124 | } 125 | 126 | public function turnUp() 127 | { 128 | return $this->last(2) > $this->last(1) && $this->last() > $this->last(1); 129 | } 130 | 131 | public function lowest(int $interval = 2) 132 | { 133 | $slice = array_slice( $this->indicator, count( $this->indicator ) - $interval ); 134 | return min( $slice ); 135 | } 136 | 137 | public function highest(int $interval = 2) 138 | { 139 | $slice = array_slice( $this->indicator, count( $this->indicator ) - $interval ); 140 | return max( $slice ); 141 | } 142 | 143 | public function toArray(): array 144 | { 145 | return $this->indicator; 146 | } 147 | 148 | public function toReversedArray(): array 149 | { 150 | return array_reverse($this->indicator); 151 | } 152 | 153 | public function push($element) 154 | { 155 | array_push($this->indicator, $element); 156 | } 157 | 158 | public function count(): int 159 | { 160 | return count($this->indicator); 161 | } 162 | 163 | public function slice($count): ArrayIndicator 164 | { 165 | return new ArrayIndicator(array_slice($this->indicator, -$count)); 166 | } 167 | 168 | public function part($start, $end = null) 169 | { 170 | return new ArrayIndicator(array_slice($this->indicator, $start, $end)); 171 | } 172 | 173 | public function ago(int $count) 174 | { 175 | $count = count($this->indicator) - $count; 176 | return new ArrayIndicator(array_slice($this->indicator, 0, $count)); 177 | } 178 | 179 | /** 180 | * @param int $count 181 | * @param bool $safe 182 | * @return bool 183 | * @throws CantCompareLessTwoElementsException 184 | */ 185 | public function equals(int $count = 2, bool $safe = true): bool 186 | { 187 | if( $count < 2 && !$safe ) { 188 | throw new CantCompareLessTwoElementsException("You cant compare less 2 elements."); 189 | } 190 | 191 | if ( $count == 1 && $safe ) { 192 | return true; 193 | } 194 | 195 | $equals = true; 196 | 197 | $slice = array_slice($this->indicator, -$count); 198 | 199 | for($i = 1; $i < count($slice) - 1; ++$i ) { 200 | if ( $slice[$i] != $slice[$i - 1] ) { 201 | $equals = false; 202 | } 203 | } 204 | 205 | return $equals; 206 | } 207 | 208 | public function map( callable $callback ) 209 | { 210 | return new ArrayIndicator( array_map( $callback, $this->indicator ) ); 211 | } 212 | 213 | public function rma( int $period ): ArrayIndicator 214 | { 215 | return $this->indicators->rma( $this, $period ); 216 | } 217 | 218 | public function sma( int $period ): ArrayIndicator 219 | { 220 | return $this->indicators->sma( $this, $period ); 221 | } 222 | 223 | public function ema( int $period ): ArrayIndicator 224 | { 225 | return $this->indicators->ema( $this, $period ); 226 | } 227 | 228 | /** 229 | * Whether a offset exists 230 | * @link https://php.net/manual/en/arrayaccess.offsetexists.php 231 | * @param mixed $offset

232 | * An offset to check for. 233 | *

234 | * @return boolean true on success or false on failure. 235 | *

236 | *

237 | * The return value will be casted to boolean if non-boolean was returned. 238 | * @since 5.0.0 239 | */ 240 | public function offsetExists( $offset ) 241 | { 242 | return $this->isExists( $offset ); 243 | } 244 | 245 | /** 246 | * Offset to retrieve 247 | * @link https://php.net/manual/en/arrayaccess.offsetget.php 248 | * @param mixed $offset

249 | * The offset to retrieve. 250 | *

251 | * @return mixed Can return all value types. 252 | * @since 5.0.0 253 | */ 254 | public function offsetGet( $offset ) 255 | { 256 | return $this->last( $offset ); 257 | } 258 | 259 | /** 260 | * Offset to set 261 | * @link https://php.net/manual/en/arrayaccess.offsetset.php 262 | * @param mixed $offset

263 | * The offset to assign the value to. 264 | *

265 | * @param mixed $value

266 | * The value to set. 267 | *

268 | * @return void 269 | * @since 5.0.0 270 | * @throws Exception 271 | */ 272 | public function offsetSet( $offset, $value ) 273 | { 274 | throw new Exception("You cant set element by one."); 275 | } 276 | 277 | /** 278 | * Offset to unset 279 | * @link https://php.net/manual/en/arrayaccess.offsetunset.php 280 | * @param mixed $offset

281 | * The offset to unset. 282 | *

283 | * @return void 284 | * @since 5.0.0 285 | * @throws Exception 286 | */ 287 | public function offsetUnset( $offset ) 288 | { 289 | throw new Exception("You cant unset element by one."); 290 | } 291 | 292 | /** 293 | * @param int $period 294 | * @throws PeriodCantBeNegativeException 295 | */ 296 | public function assertPeriodNegative( int $period ): void 297 | { 298 | if ( $period < 0 ) { 299 | throw new PeriodCantBeNegativeException(); 300 | } 301 | } 302 | 303 | /** 304 | * @param int $period 305 | * @param int $number 306 | * @throws PeriodCantBeLessNumberException 307 | */ 308 | public function assertPeriodLess( int $period, int $number ): void 309 | { 310 | if ( $period < $number ) { 311 | throw new PeriodCantBeLessNumberException(); 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/Exceptions/CantCompareLessTwoElementsException.php: -------------------------------------------------------------------------------- 1 | defaultIndicatorsRoutes(); 43 | 44 | foreach ( $classes as $method => $class ) { 45 | $this->register( $method, $class ); 46 | } 47 | } 48 | 49 | public function register( string $key, string $indicator ) 50 | { 51 | $this->registry[ $key ] = $indicator; 52 | } 53 | 54 | /** 55 | * @param string $name 56 | * @param array $arguments 57 | * @return mixed 58 | * @throws IndicatorNotFoundException 59 | */ 60 | public function __call( string $name, array $arguments = [] ) 61 | { 62 | if ( isset( $this->registry[ $name ] ) && class_exists( $this->registry[ $name ] ) ) { 63 | return new $this->registry[ $name ]( ...$arguments ); 64 | } 65 | 66 | throw new IndicatorNotFoundException(); 67 | } 68 | 69 | private function defaultIndicatorsRoutes() 70 | { 71 | return [ 72 | 'atr' => ATR::class, 73 | 'bbands' => BBands::class, 74 | 'ema' => EMA::class, 75 | 'sma' => SMA::class, 76 | 'rsi' => RSI::class, 77 | 'rma' => RMA::class, 78 | 'macd' => MACD::class, 79 | 'stochrsi' => StochRSI::class, 80 | ]; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Indicators/ATR.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $period, 2 ); 22 | 23 | parent::__construct( array_values( trader_atr( $high->toArray(), $low->toArray(), $close->toArray(), $period ) )); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/Indicators/BBands.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $timePeriod, 2 ); 26 | 27 | parent::__construct( array_values( trader_bbands( $indicator->toArray(), $timePeriod, $nbDevUp, $nbDevDn, $mAType ) ) ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Indicators/EMA.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $period, 2 ); 23 | 24 | parent::__construct( array_values( trader_ema($indicator->toArray(), $period) ) ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/Indicators/MACD.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $fastPeriod, 2 ); 25 | $this->assertPeriodLess( $slowPeriod, 2 ); 26 | 27 | parent::__construct( array_values( trader_macd($indicator->toArray(), $fastPeriod, $slowPeriod, $signalPeriod)[0] ) ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/Indicators/RMA.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $period, 2 ); 24 | 25 | $smaSlice = $indicator->part( 0, $period ); 26 | $sma = new SMA( $smaSlice, $period ); 27 | 28 | $data = $indicator->toArray(); 29 | 30 | $rma = [ $sma->value() ]; 31 | 32 | for( $i = $period; $i < count( $data ); ++$i ) { 33 | $iterator = $i - $period; 34 | 35 | $rma1 = $rma[ $iterator ]; 36 | 37 | $rma[] = ( $rma1 * ( $period - 1 ) + $data[ $i ] ) / $period; 38 | } 39 | 40 | parent::__construct( $rma ); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Indicators/RSI.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $period, 2 ); 23 | 24 | parent::__construct( array_values( trader_rsi($indicator->toArray(), $period) ) ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/Indicators/SMA.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $period, 2 ); 23 | 24 | parent::__construct( array_values( trader_sma($indicator->toArray(), $period) ) ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/Indicators/StochRSI.php: -------------------------------------------------------------------------------- 1 | assertPeriodLess( $period, 2 ); 24 | 25 | parent::__construct( array_values( trader_stochrsi($indicator->toArray(), $period) ) ); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/NumberIndicator.php: -------------------------------------------------------------------------------- 1 | value = $value; 13 | } 14 | 15 | public function value() 16 | { 17 | return $this->value; 18 | } 19 | 20 | public function last(int $n = 1) 21 | { 22 | return $this->value; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/OHLVC.php: -------------------------------------------------------------------------------- 1 | open = new ArrayIndicator( array_column( $candles, 'open' ) ); 21 | $this->high = new ArrayIndicator( array_column( $candles, 'high' ) ); 22 | $this->low = new ArrayIndicator( array_column( $candles, 'low' ) ); 23 | $this->volume = new ArrayIndicator( array_column( $candles, 'volume' ) ); 24 | $this->close = new ArrayIndicator( array_column( $candles, 'close' ) ); 25 | 26 | $this->indicators = new Indicators(); 27 | } 28 | 29 | /** 30 | * @return ArrayIndicator 31 | */ 32 | public function open(): ArrayIndicator 33 | { 34 | return $this->open; 35 | } 36 | 37 | /** 38 | * @return ArrayIndicator 39 | */ 40 | public function high(): ArrayIndicator 41 | { 42 | return $this->high; 43 | } 44 | 45 | /** 46 | * @return ArrayIndicator 47 | */ 48 | public function low(): ArrayIndicator 49 | { 50 | return $this->low; 51 | } 52 | 53 | /** 54 | * @return ArrayIndicator 55 | */ 56 | public function volume(): ArrayIndicator 57 | { 58 | return $this->volume; 59 | } 60 | 61 | /** 62 | * @return ArrayIndicator 63 | */ 64 | public function close(): ArrayIndicator 65 | { 66 | return $this->close; 67 | } 68 | 69 | /** 70 | * @param int $period 71 | * @return ArrayIndicator 72 | * @throws Exception 73 | */ 74 | public function atr( int $period ): ArrayIndicator 75 | { 76 | return $this->indicators->atr( $this->high, $this->low, $this->close, $period); 77 | } 78 | 79 | /** 80 | * @return float 81 | */ 82 | public function hl2(): float 83 | { 84 | return ( $this->high[0] + $this->low[0] ) / 2; 85 | } 86 | 87 | /** 88 | * @return float 89 | */ 90 | public function co2(): float 91 | { 92 | return ( $this->close[0] + $this->open[0] ) / 2; 93 | } 94 | 95 | /** 96 | * @param callable $callback 97 | * @return ArrayIndicator 98 | */ 99 | public function mapOC( callable $callback ): ArrayIndicator 100 | { 101 | return new ArrayIndicator( 102 | array_map( $callback, 103 | $this->open->toArray(), 104 | $this->close->toArray() 105 | ) 106 | ); 107 | } 108 | 109 | /** 110 | * @param callable $callback 111 | * @return ArrayIndicator 112 | */ 113 | public function mapHL( callable $callback ): ArrayIndicator 114 | { 115 | return new ArrayIndicator( 116 | array_map( $callback, 117 | $this->high->toArray(), 118 | $this->low->toArray() 119 | ) 120 | ); 121 | } 122 | 123 | /** 124 | * @param callable $callback 125 | * @return ArrayIndicator 126 | */ 127 | public function mapOHLC( callable $callback ): ArrayIndicator 128 | { 129 | return new ArrayIndicator( 130 | array_map( $callback, 131 | $this->open->toArray(), 132 | $this->high->toArray(), 133 | $this->low->toArray(), 134 | $this->close->toArray() 135 | ) 136 | ); 137 | } 138 | 139 | /** 140 | * @param callable $callback 141 | * @return ArrayIndicator 142 | */ 143 | public function mapOHLVC( callable $callback ): ArrayIndicator 144 | { 145 | return new ArrayIndicator( 146 | array_map( $callback, 147 | $this->open->toArray(), 148 | $this->high->toArray(), 149 | $this->low->toArray(), 150 | $this->volume->toArray(), 151 | $this->close->toArray() 152 | ) 153 | ); 154 | } 155 | } 156 | --------------------------------------------------------------------------------