Get bar oriented RSI working correctly
parent
268e748417
commit
3f0e175011
|
@ -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:])
|
||||||
|
|
Loading…
Reference in New Issue