feat: derivatives open-interest, flow & liquidation indicators (part 2 of 3) (#127)
* feat(derivatives): OIPriceDivergence indicator (core) * feat(derivatives): OIWeighted indicator (core) * feat(derivatives): LongShortRatio indicator (core) * feat(derivatives): TakerBuySellRatio indicator (core) * feat(derivatives): LiquidationFeatures multi-output indicator (core) * feat(derivatives): Python, Node and WASM bindings for OI, flow & liquidation indicators * test(derivatives): Python and Node tests for OI, flow & liquidation indicators * fuzz(derivatives): drive OI, flow & liquidation indicators in derivatives target * docs(derivatives): README row + counter 237->242, CHANGELOG part 2
This commit is contained in:
@@ -258,3 +258,13 @@ def test_funding_basis_non_positive_index_raises():
|
||||
def test_funding_rate_non_finite_raises():
|
||||
with pytest.raises(ValueError):
|
||||
ta.FundingRate().update(float("nan"))
|
||||
|
||||
|
||||
def test_oi_price_divergence_zero_window_raises():
|
||||
with pytest.raises(ValueError):
|
||||
ta.OIPriceDivergence(0)
|
||||
|
||||
|
||||
def test_oi_weighted_non_positive_mark_raises():
|
||||
with pytest.raises(ValueError):
|
||||
ta.OIWeighted().update(0.0, 100.0)
|
||||
|
||||
@@ -979,3 +979,37 @@ def test_open_interest_delta_reference_value():
|
||||
assert oid.update(1000.0) is None # seeds the previous OI
|
||||
assert oid.update(1250.0) == pytest.approx(250.0)
|
||||
assert oid.update(1100.0) == pytest.approx(-150.0)
|
||||
|
||||
|
||||
def test_oi_price_divergence_reference_value():
|
||||
div = ta.OIPriceDivergence(1)
|
||||
assert div.update(1000.0, 100.0) is None # warming up
|
||||
# OI +10% while price flat -> divergence +0.1.
|
||||
assert div.update(1100.0, 100.0) == pytest.approx(0.1)
|
||||
|
||||
|
||||
def test_oi_weighted_reference_value():
|
||||
oiw = ta.OIWeighted()
|
||||
assert oiw.update(100.0, 10.0) == pytest.approx(100.0)
|
||||
# (100·10 + 110·30) / 40 = 107.5.
|
||||
assert oiw.update(110.0, 30.0) == pytest.approx(107.5)
|
||||
|
||||
|
||||
def test_long_short_ratio_reference_value():
|
||||
# 600 longs vs 400 shorts -> 1.5.
|
||||
assert ta.LongShortRatio().update(600.0, 400.0) == pytest.approx(1.5)
|
||||
# No short side -> 0.0.
|
||||
assert ta.LongShortRatio().update(600.0, 0.0) == pytest.approx(0.0)
|
||||
|
||||
|
||||
def test_taker_buy_sell_ratio_reference_value():
|
||||
# 60 taker buys vs 40 taker sells -> 1.5.
|
||||
assert ta.TakerBuySellRatio().update(60.0, 40.0) == pytest.approx(1.5)
|
||||
# No taker sell volume -> 0.0.
|
||||
assert ta.TakerBuySellRatio().update(60.0, 0.0) == pytest.approx(0.0)
|
||||
|
||||
|
||||
def test_liquidation_features_reference_value():
|
||||
# 30 long vs 10 short: (long, short, net, total, imbalance).
|
||||
out = ta.LiquidationFeatures().update(30.0, 10.0)
|
||||
assert out == pytest.approx((30.0, 10.0, 20.0, 40.0, 0.5))
|
||||
|
||||
@@ -1994,3 +1994,56 @@ def test_open_interest_delta_streaming_equals_batch():
|
||||
streamed = np.array([streamer.update(oi[i]) for i in range(n)], dtype=np.float64)
|
||||
assert batch.shape == (n,)
|
||||
assert _eq_nan(batch, streamed)
|
||||
|
||||
|
||||
def test_oi_flow_indicators_streaming_equals_batch():
|
||||
n = 40
|
||||
oi = np.array([1000.0 + 50.0 * math.sin(i * 0.2) for i in range(n)], dtype=np.float64)
|
||||
mark = np.array([100.0 + math.cos(i * 0.3) for i in range(n)], dtype=np.float64)
|
||||
long_sz = np.array([500.0 + 20.0 * math.sin(i * 0.25) for i in range(n)], dtype=np.float64)
|
||||
short_sz = np.array([400.0 + 20.0 * math.cos(i * 0.25) for i in range(n)], dtype=np.float64)
|
||||
|
||||
# OIPriceDivergence carries a window; update(open_interest, mark_price).
|
||||
batch = ta.OIPriceDivergence(5).batch(oi, mark)
|
||||
streamer = ta.OIPriceDivergence(5)
|
||||
streamed = np.array(
|
||||
[streamer.update(oi[i], mark[i]) for i in range(n)], dtype=np.float64
|
||||
)
|
||||
assert batch.shape == (n,)
|
||||
assert _eq_nan(batch, streamed)
|
||||
|
||||
# OIWeighted; update(mark_price, open_interest).
|
||||
batch = ta.OIWeighted().batch(mark, oi)
|
||||
streamer = ta.OIWeighted()
|
||||
streamed = np.array(
|
||||
[streamer.update(mark[i], oi[i]) for i in range(n)], dtype=np.float64
|
||||
)
|
||||
assert _eq_nan(batch, streamed)
|
||||
|
||||
# LongShortRatio; update(long_size, short_size).
|
||||
batch = ta.LongShortRatio().batch(long_sz, short_sz)
|
||||
streamer = ta.LongShortRatio()
|
||||
streamed = np.array(
|
||||
[streamer.update(long_sz[i], short_sz[i]) for i in range(n)], dtype=np.float64
|
||||
)
|
||||
assert _eq_nan(batch, streamed)
|
||||
|
||||
# TakerBuySellRatio; update(taker_buy_volume, taker_sell_volume).
|
||||
batch = ta.TakerBuySellRatio().batch(long_sz, short_sz)
|
||||
streamer = ta.TakerBuySellRatio()
|
||||
streamed = np.array(
|
||||
[streamer.update(long_sz[i], short_sz[i]) for i in range(n)], dtype=np.float64
|
||||
)
|
||||
assert _eq_nan(batch, streamed)
|
||||
|
||||
|
||||
def test_liquidation_features_streaming_equals_batch():
|
||||
n = 30
|
||||
long_liq = np.array([abs(50.0 * math.sin(i * 0.4)) for i in range(n)], dtype=np.float64)
|
||||
short_liq = np.array([abs(40.0 * math.cos(i * 0.3)) for i in range(n)], dtype=np.float64)
|
||||
batch = ta.LiquidationFeatures().batch(long_liq, short_liq)
|
||||
streamer = ta.LiquidationFeatures()
|
||||
assert batch.shape == (n, 5)
|
||||
for i in range(n):
|
||||
row = streamer.update(long_liq[i], short_liq[i])
|
||||
assert tuple(batch[i]) == pytest.approx(row)
|
||||
|
||||
Reference in New Issue
Block a user