Skip to content

Commit b57cede

Browse files
authored
Merge pull request freqtrade#11178 from TheJoeSchr/fix/orderflow_imbalance_list
Fix:orderflow returns a list for stacked imbalances
2 parents b863c68 + 5f5e513 commit b57cede

File tree

3 files changed

+64
-39
lines changed

3 files changed

+64
-39
lines changed

docs/advanced-orderflow.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ dataframe["delta"] # Difference between ask and bid volume.
7070
dataframe["min_delta"] # Minimum delta within the candle
7171
dataframe["max_delta"] # Maximum delta within the candle
7272
dataframe["total_trades"] # Total number of trades
73-
dataframe["stacked_imbalances_bid"] # Price level of stacked bid imbalance
74-
dataframe["stacked_imbalances_ask"] # Price level of stacked ask imbalance
73+
dataframe["stacked_imbalances_bid"] # List of price levels of stacked bid imbalance range beginnings
74+
dataframe["stacked_imbalances_ask"] # List of price levels of stacked ask imbalance range beginnings
7575
```
7676

7777
You can access these columns in your strategy code for further analysis. Here's an example:

freqtrade/data/converter/orderflow.py

+19-29
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,12 @@ def populate_dataframe_with_trades(
164164
dataframe.at[index, "imbalances"] = imbalances.to_dict(orient="index")
165165

166166
stacked_imbalance_range = config_orderflow["stacked_imbalance_range"]
167-
dataframe.at[index, "stacked_imbalances_bid"] = stacked_imbalance_bid(
168-
imbalances, stacked_imbalance_range=stacked_imbalance_range
167+
dataframe.at[index, "stacked_imbalances_bid"] = stacked_imbalance(
168+
imbalances, label="bid", stacked_imbalance_range=stacked_imbalance_range
169169
)
170170

171-
dataframe.at[index, "stacked_imbalances_ask"] = stacked_imbalance_ask(
172-
imbalances, stacked_imbalance_range=stacked_imbalance_range
171+
dataframe.at[index, "stacked_imbalances_ask"] = stacked_imbalance(
172+
imbalances, label="ask", stacked_imbalance_range=stacked_imbalance_range
173173
)
174174

175175
bid = np.where(
@@ -256,34 +256,24 @@ def trades_orderflow_to_imbalances(df: pd.DataFrame, imbalance_ratio: int, imbal
256256
return dataframe
257257

258258

259-
def stacked_imbalance(
260-
df: pd.DataFrame, label: str, stacked_imbalance_range: int, should_reverse: bool
261-
):
259+
def stacked_imbalance(df: pd.DataFrame, label: str, stacked_imbalance_range: int):
262260
"""
263261
y * (y.groupby((y != y.shift()).cumsum()).cumcount() + 1)
264262
https://stackoverflow.com/questions/27626542/counting-consecutive-positive-values-in-python-pandas-array
265263
"""
266264
imbalance = df[f"{label}_imbalance"]
267265
int_series = pd.Series(np.where(imbalance, 1, 0))
268-
stacked = int_series * (
269-
int_series.groupby((int_series != int_series.shift()).cumsum()).cumcount() + 1
270-
)
271-
272-
max_stacked_imbalance_idx = stacked.index[stacked >= stacked_imbalance_range]
273-
stacked_imbalance_price = np.nan
274-
if not max_stacked_imbalance_idx.empty:
275-
idx = (
276-
max_stacked_imbalance_idx[0]
277-
if not should_reverse
278-
else np.flipud(max_stacked_imbalance_idx)[0]
279-
)
280-
stacked_imbalance_price = imbalance.index[idx]
281-
return stacked_imbalance_price
282-
283-
284-
def stacked_imbalance_ask(df: pd.DataFrame, stacked_imbalance_range: int):
285-
return stacked_imbalance(df, "ask", stacked_imbalance_range, should_reverse=True)
286-
287-
288-
def stacked_imbalance_bid(df: pd.DataFrame, stacked_imbalance_range: int):
289-
return stacked_imbalance(df, "bid", stacked_imbalance_range, should_reverse=False)
266+
# Group consecutive True values and get their counts
267+
groups = (int_series != int_series.shift()).cumsum()
268+
counts = int_series.groupby(groups).cumsum()
269+
270+
# Find indices where count meets or exceeds the range requirement
271+
valid_indices = counts[counts >= stacked_imbalance_range].index
272+
273+
stacked_imbalance_prices = []
274+
if not valid_indices.empty:
275+
# Get all prices from valid indices from beginning of the range
276+
stacked_imbalance_prices = [
277+
imbalance.index.values[idx - (stacked_imbalance_range - 1)] for idx in valid_indices
278+
]
279+
return stacked_imbalance_prices

tests/data/test_converter_orderflow.py

+43-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import numpy as np
21
import pandas as pd
32
import pytest
43

54
from freqtrade.constants import DEFAULT_TRADES_COLUMNS
65
from freqtrade.data.converter import populate_dataframe_with_trades
76
from freqtrade.data.converter.orderflow import (
87
ORDERFLOW_ADDED_COLUMNS,
8+
stacked_imbalance,
99
timeframe_to_DateOffset,
1010
trades_to_volumeprofile_with_total_delta_bid_ask,
1111
)
@@ -185,24 +185,24 @@ def test_public_trades_mock_populate_dataframe_with_trades__check_orderflow(
185185
assert results["max_delta"] == 17.298
186186

187187
# Assert that stacked imbalances are NaN (not applicable in this test)
188-
assert np.isnan(results["stacked_imbalances_bid"])
189-
assert np.isnan(results["stacked_imbalances_ask"])
188+
assert results["stacked_imbalances_bid"] == []
189+
assert results["stacked_imbalances_ask"] == []
190190

191191
# Repeat assertions for the third from last row
192192
results = df.iloc[-2]
193193
assert pytest.approx(results["delta"]) == -20.862
194194
assert pytest.approx(results["min_delta"]) == -54.559999
195195
assert 82.842 == results["max_delta"]
196-
assert 234.99 == results["stacked_imbalances_bid"]
197-
assert 234.96 == results["stacked_imbalances_ask"]
196+
assert results["stacked_imbalances_bid"] == [234.97]
197+
assert results["stacked_imbalances_ask"] == [234.94]
198198

199199
# Repeat assertions for the last row
200200
results = df.iloc[-1]
201201
assert pytest.approx(results["delta"]) == -49.302
202202
assert results["min_delta"] == -70.222
203203
assert pytest.approx(results["max_delta"]) == 11.213
204-
assert np.isnan(results["stacked_imbalances_bid"])
205-
assert np.isnan(results["stacked_imbalances_ask"])
204+
assert results["stacked_imbalances_bid"] == []
205+
assert results["stacked_imbalances_ask"] == []
206206

207207

208208
def test_public_trades_trades_mock_populate_dataframe_with_trades__check_trades(
@@ -358,7 +358,8 @@ def test_public_trades_binned_big_sample_list(public_trades_list):
358358
assert 197.512 == df["bid_amount"].iloc[0] # total bid amount
359359
assert 88.98 == df["ask_amount"].iloc[0] # total ask amount
360360
assert 26 == df["ask"].iloc[0] # ask price
361-
assert -108.532 == pytest.approx(df["delta"].iloc[0]) # delta (bid amount - ask amount)
361+
# delta (bid amount - ask amount)
362+
assert -108.532 == pytest.approx(df["delta"].iloc[0])
362363

363364
assert 3 == df["bid"].iloc[-1] # bid price
364365
assert 50.659 == df["bid_amount"].iloc[-1] # total bid amount
@@ -567,6 +568,40 @@ def test_analyze_with_orderflow(
567568
assert isinstance(lastval_of2, dict)
568569

569570

571+
def test_stacked_imbalances_multiple_prices():
572+
"""Test that stacked imbalances correctly returns multiple price levels when present"""
573+
# Test with empty result
574+
df_no_stacks = pd.DataFrame(
575+
{
576+
"bid_imbalance": [False, False, True, False],
577+
"ask_imbalance": [False, True, False, False],
578+
},
579+
index=[234.95, 234.96, 234.97, 234.98],
580+
)
581+
no_stacks = stacked_imbalance(df_no_stacks, "bid", stacked_imbalance_range=2)
582+
assert no_stacks == []
583+
584+
# Create a sample DataFrame with known imbalances
585+
df = pd.DataFrame(
586+
{
587+
"bid_imbalance": [True, True, True, False, False, True, True, False, True],
588+
"ask_imbalance": [False, False, True, True, True, False, False, True, True],
589+
},
590+
index=[234.95, 234.96, 234.97, 234.98, 234.99, 235.00, 235.01, 235.02, 235.03],
591+
)
592+
# Test bid imbalances (should return prices in ascending order)
593+
bid_prices = stacked_imbalance(df, "bid", stacked_imbalance_range=2)
594+
assert bid_prices == [234.95, 234.96, 235.00]
595+
596+
# Test ask imbalances (should return prices in descending order)
597+
ask_prices = stacked_imbalance(df, "ask", stacked_imbalance_range=2)
598+
assert ask_prices == [234.97, 234.98, 235.02]
599+
600+
# Test with higher stacked_imbalance_range
601+
bid_prices_higher = stacked_imbalance(df, "bid", stacked_imbalance_range=3)
602+
assert bid_prices_higher == [234.95]
603+
604+
570605
def test_timeframe_to_DateOffset():
571606
assert timeframe_to_DateOffset("1s") == pd.DateOffset(seconds=1)
572607
assert timeframe_to_DateOffset("1m") == pd.DateOffset(minutes=1)

0 commit comments

Comments
 (0)