Skip to content

Commit dd27504

Browse files
Merge pull request #75 from joshyattridge/Increased_liquidity_speed
Increased the speed to calculate the liquidity function
2 parents 257282e + 7e8ed40 commit dd27504

File tree

1 file changed

+111
-81
lines changed

1 file changed

+111
-81
lines changed

smartmoneyconcepts/smc.py

Lines changed: 111 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def ob(
378378
ohlc: DataFrame,
379379
swing_highs_lows: DataFrame,
380380
close_mitigation: bool = False,
381-
) -> DataFrame:
381+
) -> Series:
382382
"""
383383
OB - Order Blocks
384384
This method detects order blocks when there is a high amount of market orders exist on a price range.
@@ -564,13 +564,11 @@ def ob(
564564
)
565565

566566
@classmethod
567-
def liquidity(
568-
cls, ohlc: DataFrame, swing_highs_lows: DataFrame, range_percent: float = 0.01
569-
) -> Series:
567+
def liquidity(cls, ohlc: DataFrame, swing_highs_lows: DataFrame, range_percent: float = 0.01) -> Series:
570568
"""
571569
Liquidity
572-
Liquidity is when there are multiply highs within a small range of each other.
573-
or multiply lows within a small range of each other.
570+
Liquidity is when there are multiple highs within a small range of each other,
571+
or multiple lows within a small range of each other.
574572
575573
parameters:
576574
swing_highs_lows: DataFrame - provide the dataframe from the swing_highs_lows function
@@ -583,83 +581,115 @@ def liquidity(
583581
Swept = the index of the candle that swept the liquidity
584582
"""
585583

586-
swing_highs_lows = swing_highs_lows.copy()
587-
588-
# subtract the highest high from the lowest low
589-
pip_range = (max(ohlc["high"]) - min(ohlc["low"])) * range_percent
590-
591-
# go through all of the high level and if there are more than 1 within the pip range, then it is liquidity
592-
liquidity = np.zeros(len(ohlc), dtype=np.int32)
593-
liquidity_level = np.zeros(len(ohlc), dtype=np.float32)
594-
liquidity_end = np.zeros(len(ohlc), dtype=np.int32)
595-
liquidity_swept = np.zeros(len(ohlc), dtype=np.int32)
596-
597-
for i in range(len(ohlc)):
598-
if swing_highs_lows["HighLow"][i] == 1:
599-
high_level = swing_highs_lows["Level"][i]
600-
range_low = high_level - pip_range
601-
range_high = high_level + pip_range
602-
temp_liquidity_level = [high_level]
603-
start = i
604-
end = i
584+
# Work on a copy so the original is not modified.
585+
shl = swing_highs_lows.copy()
586+
n = len(ohlc)
587+
588+
# Calculate the pip range based on the overall high-low range.
589+
pip_range = (ohlc["high"].max() - ohlc["low"].min()) * range_percent
590+
591+
# Preconvert required columns to numpy arrays.
592+
ohlc_high = ohlc["high"].values
593+
ohlc_low = ohlc["low"].values
594+
# Make a copy to allow in-place marking of used candidates.
595+
shl_HL = shl["HighLow"].values.copy()
596+
shl_Level = shl["Level"].values.copy()
597+
598+
# Initialise output arrays with NaN (to match later replacement of zeros).
599+
liquidity = np.full(n, np.nan, dtype=np.float32)
600+
liquidity_level = np.full(n, np.nan, dtype=np.float32)
601+
liquidity_end = np.full(n, np.nan, dtype=np.float32)
602+
liquidity_swept = np.full(n, np.nan, dtype=np.float32)
603+
604+
# Process bullish liquidity (HighLow == 1)
605+
bull_indices = np.nonzero(shl_HL == 1)[0]
606+
for i in bull_indices:
607+
# Skip if this candidate has already been used.
608+
if shl_HL[i] != 1:
609+
continue
610+
high_level = shl_Level[i]
611+
range_low = high_level - pip_range
612+
range_high = high_level + pip_range
613+
group_levels = [high_level]
614+
group_end = i
615+
616+
# Determine the swept index:
617+
# Find the first candle after i where the high reaches or exceeds range_high.
618+
c_start = i + 1
619+
if c_start < n:
620+
cond = ohlc_high[c_start:] >= range_high
621+
if np.any(cond):
622+
swept = c_start + int(np.argmax(cond))
623+
else:
624+
swept = 0
625+
else:
605626
swept = 0
606-
for c in range(i + 1, len(ohlc)):
607-
if (
608-
swing_highs_lows["HighLow"][c] == 1
609-
and range_low <= swing_highs_lows["Level"][c] <= range_high
610-
):
611-
end = c
612-
temp_liquidity_level.append(swing_highs_lows["Level"][c])
613-
swing_highs_lows.loc[c, "HighLow"] = 0
614-
if ohlc["high"].iloc[c] >= range_high:
615-
swept = c
616-
break
617-
if len(temp_liquidity_level) > 1:
618-
average_high = sum(temp_liquidity_level) / len(temp_liquidity_level)
619-
liquidity[i] = 1
620-
liquidity_level[i] = average_high
621-
liquidity_end[i] = end
622-
liquidity_swept[i] = swept
623-
624-
# now do the same for the lows
625-
for i in range(len(ohlc)):
626-
if swing_highs_lows["HighLow"][i] == -1:
627-
low_level = swing_highs_lows["Level"][i]
628-
range_low = low_level - pip_range
629-
range_high = low_level + pip_range
630-
temp_liquidity_level = [low_level]
631-
start = i
632-
end = i
627+
628+
# Iterate only over candidate indices greater than i.
629+
for j in bull_indices:
630+
if j <= i:
631+
continue
632+
# Emulate the inner loop break: if we've reached or passed the swept index, stop.
633+
if swept and j >= swept:
634+
break
635+
# If candidate j is within the liquidity range, add it and mark it as used.
636+
if shl_HL[j] == 1 and (range_low <= shl_Level[j] <= range_high):
637+
group_levels.append(shl_Level[j])
638+
group_end = j
639+
shl_HL[j] = 0 # mark candidate as used
640+
# Only record liquidity if more than one candidate is grouped.
641+
if len(group_levels) > 1:
642+
avg_level = sum(group_levels) / len(group_levels)
643+
liquidity[i] = 1
644+
liquidity_level[i] = avg_level
645+
liquidity_end[i] = group_end
646+
liquidity_swept[i] = swept
647+
648+
# Process bearish liquidity (HighLow == -1)
649+
bear_indices = np.nonzero(shl_HL == -1)[0]
650+
for i in bear_indices:
651+
if shl_HL[i] != -1:
652+
continue
653+
low_level = shl_Level[i]
654+
range_low = low_level - pip_range
655+
range_high = low_level + pip_range
656+
group_levels = [low_level]
657+
group_end = i
658+
659+
# Find the first candle after i where the low reaches or goes below range_low.
660+
c_start = i + 1
661+
if c_start < n:
662+
cond = ohlc_low[c_start:] <= range_low
663+
if np.any(cond):
664+
swept = c_start + int(np.argmax(cond))
665+
else:
666+
swept = 0
667+
else:
633668
swept = 0
634-
for c in range(i + 1, len(ohlc)):
635-
if (
636-
swing_highs_lows["HighLow"][c] == -1
637-
and range_low <= swing_highs_lows["Level"][c] <= range_high
638-
):
639-
end = c
640-
temp_liquidity_level.append(swing_highs_lows["Level"][c])
641-
swing_highs_lows.loc[c, "HighLow"] = 0
642-
if ohlc["low"].iloc[c] <= range_low:
643-
swept = c
644-
break
645-
if len(temp_liquidity_level) > 1:
646-
average_low = sum(temp_liquidity_level) / len(temp_liquidity_level)
647-
liquidity[i] = -1
648-
liquidity_level[i] = average_low
649-
liquidity_end[i] = end
650-
liquidity_swept[i] = swept
651-
652-
liquidity = np.where(liquidity != 0, liquidity, np.nan)
653-
liquidity_level = np.where(~np.isnan(liquidity), liquidity_level, np.nan)
654-
liquidity_end = np.where(~np.isnan(liquidity), liquidity_end, np.nan)
655-
liquidity_swept = np.where(~np.isnan(liquidity), liquidity_swept, np.nan)
656-
657-
liquidity = pd.Series(liquidity, name="Liquidity")
658-
level = pd.Series(liquidity_level, name="Level")
659-
liquidity_end = pd.Series(liquidity_end, name="End")
660-
liquidity_swept = pd.Series(liquidity_swept, name="Swept")
661-
662-
return pd.concat([liquidity, level, liquidity_end, liquidity_swept], axis=1)
669+
670+
for j in bear_indices:
671+
if j <= i:
672+
continue
673+
if swept and j >= swept:
674+
break
675+
if shl_HL[j] == -1 and (range_low <= shl_Level[j] <= range_high):
676+
group_levels.append(shl_Level[j])
677+
group_end = j
678+
shl_HL[j] = 0
679+
if len(group_levels) > 1:
680+
avg_level = sum(group_levels) / len(group_levels)
681+
liquidity[i] = -1
682+
liquidity_level[i] = avg_level
683+
liquidity_end[i] = group_end
684+
liquidity_swept[i] = swept
685+
686+
# Convert arrays to Series with the proper names.
687+
liq_series = pd.Series(liquidity, name="Liquidity")
688+
level_series = pd.Series(liquidity_level, name="Level")
689+
end_series = pd.Series(liquidity_end, name="End")
690+
swept_series = pd.Series(liquidity_swept, name="Swept")
691+
692+
return pd.concat([liq_series, level_series, end_series, swept_series], axis=1)
663693

664694
@classmethod
665695
def previous_high_low(cls, ohlc: DataFrame, time_frame: str = "1D") -> Series:

0 commit comments

Comments
 (0)