Mastering Net Advance Volume Analysis with Python: A Comprehensive Guide for Investors

Apr 10, 2023

5 min read


programmer-with-chrome

In today’s financial markets, understanding market breadth is essential to gauge the overall market sentiment. Market breadth indicators can help investors and traders make informed decisions by analyzing the underlying strength or weakness of a market. One such indicator is the Net Advance Volume, which takes into account the number of advancing and declining stocks along with their trading volumes.


This code downloads stock data, cleans it, calculates value averages, percent changes, and signs, then groups the data by date and sign, and finally formats the data in a more readable way.


Importing Libraries:

Begin by importing the necessary libraries:


import pandas as pd
import numpy as np
import yfinance as yf

Loading the Data:


 symbol_list_set50
  ['ADVANC.BK',
     'AOT.BK',
     'AWC.BK',
     'BANPU.BK',
     'BBL.BK',
     'BDMS.BK',
     'BEM.BK',
     'BGRIM.BK',
     'BH.BK',
     'BTS.BK',
     'CBG.BK',
     'CENTEL.BK',
     'COM7.BK',
     'CPALL.BK',
     'CPF.BK',
     'CPN.BK',
     'CRC.BK',
     'DELTA.BK',
     'EA.BK',
     'EGCO.BK',
     'GLOBAL.BK',
     'GPSC.BK',
     'GULF.BK',
     'HMPRO.BK',
     'INTUCH.BK',
     'IVL.BK',
     'JMART.BK',
     'JMT.BK',
     'KBANK.BK',
     'KTB.BK',
     'KTC.BK',
     'LH.BK',
     'MINT.BK',
     'MTC.BK',
     'OR.BK',
     'OSP.BK',
     'PTT.BK',
     'PTTEP.BK',
     'PTTGC.BK',
     'RATCH.BK',
     'SAWAD.BK',
     'SCB.BK',
     'SCC.BK',
     'SCGP.BK',
     'TIDLOR.BK',
     'TISCO.BK',
     'TOP.BK',
     'TRUE.BK',
     'TTB.BK',
     'TU.BK']

Download data from the symbol ticker list:


data = yf.download(symbol_list_set50, start='2020-09-01',end='2022-09-01', interval='1d')
    [*********************100%***********************]  50 of 50 completed
data

Cleaning the Data:



data.fillna(method='ffill',inplace=True)
df=data.stack()
stock_df =df.sort_index(level=1)
stock_df.index.names=['Date','Stock']
stock_df
Adj CloseCloseHighLowOpenVolume
DateStock
2020-09-01ADVANC.BK165.805130182.500000184.000000182.500000183.5000003103300.0
2020-09-02ADVANC.BK166.713669183.500000184.000000182.000000182.5000002853100.0
2020-09-03ADVANC.BK166.713669183.500000184.000000182.500000183.5000002651600.0
2020-09-08ADVANC.BK164.896637181.500000183.500000181.500000182.5000004256700.0
2020-09-09ADVANC.BK163.988083180.500000181.500000180.000000180.0000002468800.0
2022-08-25TU.BK16.91848917.40000017.70000117.29999917.5000009456600.0
2022-08-26TU.BK16.82125717.29999917.50000017.10000017.40000015086800.0
2022-08-29TU.BK16.72402617.20000117.40000016.90000017.00000021703600.0
2022-08-30TU.BK17.01572417.50000017.60000017.20000117.20000120849700.0
2022-08-31TU.BK17.01572417.50000017.60000017.29999917.50000025821000.0

23304 rows × 6 columns


Calculating Value Average:


# should use average price to * volume , or vwap * average price
stock_df['value']=((stock_df['Open']+stock_df['High']+stock_df['Low']+stock_df['Close'])/4)*stock_df['Volume']
stock_df

Adj CloseCloseHighLowOpenVolumevalue
DateStock
2020-09-01ADVANC.BK165.805130182.500000184.000000182.500000183.5000003103300.05.682918e+08
2020-09-02ADVANC.BK166.713669183.500000184.000000182.000000182.5000002853100.05.221173e+08
2020-09-03ADVANC.BK166.713669183.500000184.000000182.500000183.5000002651600.04.862372e+08
2020-09-08ADVANC.BK164.896637181.500000183.500000181.500000182.5000004256700.07.757836e+08
2020-09-09ADVANC.BK163.988083180.500000181.500000180.000000180.0000002468800.04.456184e+08
2022-08-25TU.BK16.91848917.40000017.70000117.29999917.5000009456600.01.652541e+08
2022-08-26TU.BK16.82125717.29999917.50000017.10000017.40000015086800.02.613788e+08
2022-08-29TU.BK16.72402617.20000117.40000016.90000017.00000021703600.03.716742e+08
2022-08-30TU.BK17.01572417.50000017.60000017.20000117.20000120849700.03.622635e+08
2022-08-31TU.BK17.01572417.50000017.60000017.29999917.50000025821000.04.512220e+08

23304 rows × 7 columns


Calculating Percent Change and Sign:


stock_df['pct_change']=stock_df['Close'].pct_change()
stock_df['sign']=np.sign(stock_df['pct_change'])
stock_df


Adj CloseCloseHighLowOpenVolumevaluepct_changesign
DateStock
2020-09-01ADVANC.BK165.805130182.500000184.000000182.500000183.5000003103300.05.682918e+08NaNNaN
2020-09-02ADVANC.BK166.713669183.500000184.000000182.000000182.5000002853100.05.221173e+080.0054791.0
2020-09-03ADVANC.BK166.713669183.500000184.000000182.500000183.5000002651600.04.862372e+080.0000000.0
2020-09-08ADVANC.BK164.896637181.500000183.500000181.500000182.5000004256700.07.757836e+08-0.010899-1.0
2020-09-09ADVANC.BK163.988083180.500000181.500000180.000000180.0000002468800.04.456184e+08-0.005510-1.0
2022-08-25TU.BK16.91848917.40000017.70000117.29999917.5000009456600.01.652541e+080.0000000.0
2022-08-26TU.BK16.82125717.29999917.50000017.10000017.40000015086800.02.613788e+08-0.005747-1.0
2022-08-29TU.BK16.72402617.20000117.40000016.90000017.00000021703600.03.716742e+08-0.005780-1.0
2022-08-30TU.BK17.01572417.50000017.60000017.20000117.20000120849700.03.622635e+080.0174421.0
2022-08-31TU.BK17.01572417.50000017.60000017.29999917.50000025821000.04.512220e+080.0000000.0

23304 rows × 9 columns



stock_df.groupby(by=['Date','sign'])['value'].sum().to_frame()

value
Datesign
2020-09-01-1.01.107695e+10
1.01.356994e+10
2020-09-02-1.04.001688e+09
0.03.183170e+09
1.01.555938e+10
2022-08-300.09.138420e+09
1.02.835648e+10
2022-08-31-1.03.778302e+10
0.03.289448e+09
1.02.491144e+10

1428 rows × 1 columns


Grouping Data and Calculating AD Volume:



ad_volume_df = stock_df.groupby(by=['Date','sign'])['value'].sum().to_frame('ad_volume').reset_index()
ad_volume_df

Datesignad_volume
02020-09-01-1.01.107695e+10
12020-09-011.01.356994e+10
22020-09-02-1.04.001688e+09
32020-09-020.03.183170e+09
42020-09-021.01.555938e+10
14232022-08-300.09.138420e+09
14242022-08-301.02.835648e+10
14252022-08-31-1.03.778302e+10
14262022-08-310.03.289448e+09
14272022-08-311.02.491144e+10

1428 rows × 3 columns


Formatting the Data:


In this section, we format the data to make it easier to visualize and analyze. We start by pivoting the data using the ‘ad_volume’ values, and setting the ‘Date’ as the index and ‘sign’ as columns.


ad_volume_df = pd.pivot_table(ad_volume_df,values='ad_volume',index=['Date'],columns='sign')
ad_volume_df

sign-1.00.01.0
Date
2020-09-011.107695e+10NaN1.356994e+10
2020-09-024.001688e+093.183170e+091.555938e+10
2020-09-031.076820e+103.479676e+098.592459e+09
2020-09-081.692928e+103.530786e+091.023250e+09
2020-09-091.138479e+103.760937e+098.628323e+09
2022-08-253.004589e+096.668940e+093.409971e+10
2022-08-261.934971e+101.113192e+101.073419e+10
2022-08-293.348454e+101.236594e+098.556465e+09
2022-08-302.793406e+099.138420e+092.835648e+10
2022-08-313.778302e+103.289448e+092.491144e+10

480 rows × 3 columns


This creates a DataFrame ‘ad_volume_df’ with the columns -1.0, 0.0, and 1.0 representing declining volume, unchanged volume, and advancing volume, respectively.


Next, we rename the columns to make them more descriptive: ‘Vol_decline’ for -1.0, ‘Vol_unchanged’ for 0.0, and ‘Vol_advance’ for 1.0.


ad_volume_df = ad_volume_df.rename(columns={-1.0:'Vol_decline',0.0:'Vol_unchanged',1.0:'Vol_advance'})
ad_volume_df

signVol_declineVol_unchangedVol_advance
Date
2020-09-011.107695e+10NaN1.356994e+10
2020-09-024.001688e+093.183170e+091.555938e+10
2020-09-031.076820e+103.479676e+098.592459e+09
2020-09-081.692928e+103.530786e+091.023250e+09
2020-09-091.138479e+103.760937e+098.628323e+09
2022-08-253.004589e+096.668940e+093.409971e+10
2022-08-261.934971e+101.113192e+101.073419e+10
2022-08-293.348454e+101.236594e+098.556465e+09
2022-08-302.793406e+099.138420e+092.835648e+10
2022-08-313.778302e+103.289448e+092.491144e+10

480 rows × 3 columns


We then calculate the ‘net_ad_volume’ by subtracting the declining volume from the advancing volume and add a new column ‘AD_Volume_line’, which is the cumulative sum of the ‘net_ad_volume’ column. Finally, we reset the index of the DataFrame.


ad_volume_df['net_ad_volume'] = ad_volume_df['Vol_advance']- ad_volume_df['Vol_decline']
ad_volume_df['AD_Volume_line']=ad_volume_df['net_ad_volume'].cumsum()
ad_volume_df=ad_volume_df.reset_index()
ad_volume_df

signDateVol_declineVol_unchangedVol_advancenet_ad_volumeAD_Volume_line
02020-09-011.107695e+10NaN1.356994e+102.492986e+092.492986e+09
12020-09-024.001688e+093.183170e+091.555938e+101.155769e+101.405068e+10
22020-09-031.076820e+103.479676e+098.592459e+09-2.175740e+091.187494e+10
32020-09-081.692928e+103.530786e+091.023250e+09-1.590603e+10-4.031094e+09
42020-09-091.138479e+103.760937e+098.628323e+09-2.756465e+09-6.787559e+09
4752022-08-253.004589e+096.668940e+093.409971e+103.109512e+109.671174e+11
4762022-08-261.934971e+101.113192e+101.073419e+10-8.615519e+099.585018e+11
4772022-08-293.348454e+101.236594e+098.556465e+09-2.492807e+109.335738e+11
4782022-08-302.793406e+099.138420e+092.835648e+102.556308e+109.591368e+11
4792022-08-313.778302e+103.289448e+092.491144e+10-1.287158e+109.462653e+11

480 rows × 6 columns


Now, we create a bar chart using the Plotly library to visualize the Net Advance Volume over time.


fig = px.bar(ad_volume_df,y='net_ad_volume',
             x='Date',template='simple_white',
             barmode='relative',
             title='Market Breadth : Net Advance Volume')
fig.update_xaxes(rangebreaks=[dict(bounds=['sat','mo'])])
fig.update_xaxes(rangeslider_visible=True)
fig.show()


Print the index of the data DataFrame, showing the dates included in the dataset.


data.index

    DatetimeIndex(['2020-09-01', '2020-09-02', '2020-09-03', '2020-09-08',
                   '2020-09-09', '2020-09-10', '2020-09-11', '2020-09-14',
                   '2020-09-15', '2020-09-16',
                   ...
                   '2022-08-18', '2022-08-19', '2022-08-22', '2022-08-23',
                   '2022-08-24', '2022-08-25', '2022-08-26', '2022-08-29',
                   '2022-08-30', '2022-08-31'],
                  dtype='datetime64[ns]', name='Date', length=480, freq=None)



Print the ‘Date’ column of the ad_volume_df DataFrame, showing the dates included in this dataset.


ad_volume_df['Date']
# check you date range
    0     2020-09-01
    1     2020-09-02
    2     2020-09-03
    3     2020-09-08
    4     2020-09-09
             ...    
    475   2022-08-25
    476   2022-08-26
    477   2022-08-29
    478   2022-08-30
    479   2022-08-31
    Name: Date, Length: 480, dtype: datetime64[ns]

Calculate the difference between a complete date range from ‘2020-09-01’ to ‘2022-08-31’ and the dates in data.index. This will show the dates without data.


pd.date_range(start='2020-09-01',end='2022-08-31').difference(data.index)
# will show date with out data
DatetimeIndex(['2020-09-04', '2020-09-05', '2020-09-06', '2020-09-07',
                   '2020-09-12', '2020-09-13', '2020-09-19', '2020-09-20',
                   '2020-09-26', '2020-09-27',
                   ...
                   '2022-07-31', '2022-08-06', '2022-08-07', '2022-08-12',
                   '2022-08-13', '2022-08-14', '2022-08-20', '2022-08-21',
                   '2022-08-27', '2022-08-28'],
                  dtype='datetime64[ns]', length=250, freq=None)

Calculate the difference between a complete date range from ‘2020-09-01’ to ‘2022-08-31’ and the dates in ad_volume_df[‘Date’]. Store the result in a variable called df_break.


df_break=pd.date_range(start='2020-09-01',end='2022-08-31').difference(ad_volume_df['Date'])

Create a line chart using the Plotly library to visualize the ‘AD_Volume_line’ column of the ad_volume_df DataFrame. Update the x-axis range breaks using the df_break variable.


fig = px.line(ad_volume_df,y='AD_Volume_line',
             x='Date',template='simple_white',
             title='Market Breadth : Net Advance Volume')
fig.update_xaxes(rangebreaks=[dict(values=df_break)])
fig.update_xaxes(rangeslider_visible=True)
fig.show()

Buble-Chart


Extract a specific date range from the ad_volume_df DataFrame (between ‘2021-11-25’ and ‘2021-12-05’) by setting the ‘Date’ as the index and using the loc function.


ad_volume_df.set_index('Date').loc['2021-11-25':'2021-12-05']

signVol_declineVol_unchangedVol_advancenet_ad_volumeAD_Volume_line
Date
2021-11-251.511991e+106.928903e+091.083175e+10-4.288164e+095.730919e+11
2021-11-266.456038e+106.928903e+091.083175e+10-4.288164e+095.730919e+11
2021-11-295.391466e+101.317914e+091.009600e+10-4.381867e+105.292732e+11
2021-11-307.962636e+101.054784e+101.238412e+10-6.724224e+104.620310e+11
2021-12-015.496541e+096.043594e+093.955037e+103.405382e+104.960848e+11
2021-12-021.576617e+104.467534e+091.239807e+10-3.368106e+094.927167e+11
2021-12-039.278674e+098.349072e+095.375706e+09-3.902968e+094.888137e+11

Create a line chart using the Plotly library to visualize the ‘AD_Volume_line’ column of the ad_volume_df DataFrame for the specific date range mentioned earlier. Update the x-axis range breaks using the df_break variable.


fig = px.line(ad_volume_df,y='AD_Volume_line',
             x='Date',template='simple_white',
             title='Market Breadth : Net Advance Volume')
fig.update_xaxes(rangebreaks=[dict(values=df_break)])
fig.update_xaxes(rangeslider_visible=True)
fig.show()



Conclusion


In conclusion, this article demonstrates how to analyze market trends using the Net Advance Volume, a market breadth indicator that helps investors understand the overall market sentiment. By utilizing Python and its powerful libraries like Pandas and Plotly, we can efficiently handle large datasets and create insightful visualizations, such as line charts, to better comprehend market dynamics.



By understanding market breadth indicators like the Net Advance Volume, investors can gain valuable insights into the overall strength or weakness of the market, helping them identify potential investment opportunities and develop effective trading strategies.