@@ -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