Get bar oriented RSI working correctly

bar_select
Tyler Goodlet 2020-09-23 13:15:27 -04:00
parent 268e748417
commit 3f0e175011
1 changed files with 87 additions and 45 deletions

View File

@ -1,10 +1,13 @@
""" """
Momentum bby. Momentum bby.
""" """
from typing import AsyncIterator from typing import AsyncIterator, Optional
import numpy as np import numpy as np
from numba import jit, float64, optional from ringbuf import RingBuffer
from numba import jit, float64, optional, int64
from ..data._normalize import iterticks
# TODO: things to figure the fuck out: # TODO: things to figure the fuck out:
@ -47,6 +50,9 @@ def ema(
s[0] = y[0]; t = 0 s[0] = y[0]; t = 0
s[t] = a*y[t] + (1-a)*s[t-1], t > 0. s[t] = a*y[t] + (1-a)*s[t-1], t > 0.
} }
More discussion here:
https://stackoverflow.com/questions/42869495/numpy-version-of-exponential-weighted-moving-average-equivalent-to-pandas-ewm
""" """
n = y.shape[0] n = y.shape[0]
@ -67,6 +73,7 @@ def ema(
else: else:
s[0] = ylast s[0] = ylast
print(s)
for i in range(1, n): for i in range(1, n):
s[i] = y[i] * alpha + s[i-1] * (1 - alpha) s[i] = y[i] * alpha + s[i-1] * (1 - alpha)
@ -77,34 +84,40 @@ def ema(
# float64[:]( # float64[:](
# float64[:], # float64[:],
# int64, # int64,
# float64,
# float64,
# ), # ),
# # nopython=True, # nopython=True,
# nogil=True # nogil=True
# ) # )
def rsi( def rsi(
signal: 'np.ndarray[float64]', signal: 'np.ndarray[float64]',
period: int = 14, period: int64 = 14,
up_ema_last: float64 = None, up_ema_last: float64 = None,
down_ema_last: float64 = None, down_ema_last: float64 = None,
) -> 'np.ndarray[float64]': ) -> 'np.ndarray[float64]':
alpha = 1/period alpha = 1/period
# print(signal)
df = np.diff(signal) df = np.diff(signal)
up, down = np.where(df > 0, df, 0), np.where(df < 0, -df, 0)
up = np.where(df > 0, df, 0)
up_ema = ema(up, alpha, up_ema_last) up_ema = ema(up, alpha, up_ema_last)
down = np.where(df < 0, -df, 0)
down_ema = ema(down, alpha, down_ema_last) down_ema = ema(down, alpha, down_ema_last)
# avoid dbz errors
rs = np.divide( rs = np.divide(
up_ema, up_ema,
down_ema, down_ema,
out=np.zeros_like(up_ema), out=np.zeros_like(up_ema),
where=down_ema != 0 where=down_ema != 0
) )
# print(f'up_ema: {up_ema}\ndown_ema: {down_ema}')
# print(f'rs: {rs}')
# map rs through sigmoid (with range [0, 100]) # map rs through sigmoid (with range [0, 100])
rsi = 100 - 100 / (1 + rs) rsi = 100 - 100 / (1 + rs)
# rsi = 100 * (up_ema / (up_ema + down_ema)) # rsi = 100 * (up_ema / (up_ema + down_ema))
# also return the last ema state for next iteration # also return the last ema state for next iteration
return rsi, up_ema[-1], down_ema[-1] return rsi, up_ema[-1], down_ema[-1]
@ -114,67 +127,96 @@ def rsi(
# ) # )
async def _rsi( async def _rsi(
source: 'QuoteStream[Dict[str, Any]]', # noqa source: 'QuoteStream[Dict[str, Any]]', # noqa
ohlcv: np.ndarray, ohlcv: "ShmArray[T<'close'>]",
period: int = 14, period: int = 14,
) -> AsyncIterator[np.ndarray]: ) -> AsyncIterator[np.ndarray]:
"""Multi-timeframe streaming RSI. """Multi-timeframe streaming RSI.
https://en.wikipedia.org/wiki/Relative_strength_index https://en.wikipedia.org/wiki/Relative_strength_index
""" """
sig = ohlcv['close'] sig = ohlcv.array['close']
# TODO: the emas here should be seeded with a period SMA as per # TODO: the emas here should be seeded with a period SMA as per
# wilder's original formula.. # wilder's original formula..
rsi_h, up_ema_last, down_ema_last = rsi(sig, period, None, None) rsi_h, last_up_ema_close, last_down_ema_close = rsi(sig, period, None, None)
# deliver history # deliver history
yield rsi_h yield rsi_h
_last = sig[-1] index = ohlcv.index
async for quote in source: async for quote in source:
# tick based updates # tick based updates
for tick in quote.get('ticks', ()): for tick in iterticks(quote):
if tick.get('type') == 'trade': # though incorrect below is interesting
curr = tick['price'] # sig = ohlcv.last(period)['close']
last = np.array([_last, curr]) sig = ohlcv.last(2)['close']
# await tractor.breakpoint()
# the ema needs to be computed from the "last bar"
if ohlcv.index > index:
last_up_ema_close = up_ema_last
last_down_ema_close = down_ema_last
index = ohlcv.index
rsi_out, up_ema_last, down_ema_last = rsi( rsi_out, up_ema_last, down_ema_last = rsi(
last, sig,
period=period, period=period,
up_ema_last=up_ema_last, up_ema_last=last_up_ema_close,
down_ema_last=down_ema_last, down_ema_last=last_down_ema_close,
) )
_last = curr print(f'rsi_out: {rsi_out}')
# print(f'last: {last}\n rsi: {rsi_out}') yield rsi_out[-1:]
yield rsi_out[-1]
def wma(
signal: np.ndarray,
) -> np.ndarray:
if weights is None:
# default is a standard arithmetic mean
seq = np.full((length,), 1)
weights = seq / seq.sum()
assert length == len(weights)
async def wma( async def wma(
source, #: AsyncStream[np.ndarray], source, #: AsyncStream[np.ndarray],
length: int,
ohlcv: np.ndarray, # price time-frame "aware" ohlcv: np.ndarray, # price time-frame "aware"
lookback: np.ndarray, # price time-frame "aware" weights: Optional[np.ndarray] = None,
weights: np.ndarray, ) -> AsyncIterator[np.ndarray]: # maybe something like like FspStream?
) -> AsyncIterator[np.ndarray]: # i like FinSigStream """Streaming weighted moving average.
"""Weighted moving average.
``weights`` is a sequence of already scaled values. As an example ``weights`` is a sequence of already scaled values. As an example
for the WMA often found in "techincal analysis": for the WMA often found in "techincal analysis":
``weights = np.arange(1, N) * N*(N-1)/2``. ``weights = np.arange(1, N) * N*(N-1)/2``.
""" """
length = len(weights) # deliver historical output as "first yield"
_lookback = np.zeros(length - 1) yield np.convolve(ohlcv.array['close'], weights, 'valid')
ohlcv.from_tf('5m') # begin real-time section
# async for frame_len, frame in source: # fill length samples as lookback history
async for frame in source: # ringbuf = RingBuffer(format='f', capacity=2*length)
wma = np.convolve( # overflow = ringbuf.push(ohlcv['close'][-length + 1:])
ohlcv[-length:]['close'], # assert overflow is None
# np.concatenate((_lookback, frame)),
weights, # lookback = np.zeros((length,))
'valid' # lookback[:-1] = ohlcv['close'][-length + 1:]
)
# todo: handle case where frame_len < length - 1 # async for frame in atleast(length, source):
_lookback = frame[-(length-1):] # noqa async for quote in source:
yield wma for tick in iterticks(quote, type='trade'):
# writes no matter what
overflow = ringbuf.push(np.array([tick['price']]))
assert overflow is None
# history = np.concatenate(ringbuf.pop(length - 1), frame)
sig = ohlcv.last(length)
history = ringbuf.pop(ringbuf.read_available)
yield np.convolve(history, weights, 'valid')
# push back `length-1` datums as lookback in preparation
# for next minimum 1 datum arrival which will require
# another "window's worth" of history.
ringbuf.push(history[-length + 1:])