Coverage for source/indicators/volume_profile_indicator.py: 100%
21 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-30 15:13 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-30 15:13 +0000
1# indicators/volume_profile_indicator.py
3from .indicator_base import *
4from collections import defaultdict
6class VolumeProfileIndicatorHandler(IndicatorHandlerBase):
7 """
8 Implements static volume profile indicator. It denotes volume traded at
9 certain price levels. Calculated data can not be directly mapped to input
10 data and should be treated as the separate chart.
11 """
13 def __init__(self, number_of_steps: int = 40) -> None:
14 """
15 Class constructor.
17 Parameters:
18 number_of_steps (int): Number of bins that price should be put into
19 while creating volume profile.
20 """
22 self.number_of_steps = number_of_steps
24 def calculate(self, data: pd.DataFrame) -> pd.DataFrame:
25 """
26 Calculates static volume profile indicator values for given data.
28 Parameters:
29 data (pd.DataFrame): Data frame with input data.
31 Returns:
32 (pd.DataFrame): Output data with calculated static volume profile values.
33 """
35 volume_profile = defaultdict(float)
36 data_min = data['low'].min()
37 data_max = data['high'].max()
38 step = (data_max - data_min) / (self.number_of_steps - 1)
40 for _, row in data.iterrows():
41 equalized_low = row['low'] // step * step
42 equalized_high = row['high'] // step * step
43 price_range = np.linspace(equalized_low, equalized_high, num = int(round((equalized_high - equalized_low) / step + 1, 0)))
44 price_range = np.round(price_range, 6 - int(np.floor(np.log10(data_min))))
45 volume_per_step = row['volume'] / len(price_range)
47 for price in price_range:
48 volume_profile[price] += volume_per_step
50 profile_df = pd.DataFrame(list(volume_profile.items()), columns = ['price', 'volume'])
51 profile_df.sort_values(by='price', inplace=True)
53 return profile_df