Skip to content

Commit 06690ca

Browse files
committed
Introduce Fama-French values reader for the Indian Market
Currently, there is a way to download Fama-French factors for doing analysis on the US market. This diff introduces a reader that downloads the files from the IIM Ahemdabad website to be used for analysing portfolios and stocks in the Indian market. References: - https://faculty.iima.ac.in/iffm/Indian-Fama-French-Momentum/
1 parent 9a3e2fe commit 06690ca

3 files changed

Lines changed: 123 additions & 1 deletion

File tree

pandas_datareader/data.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pandas_datareader.eurostat import EurostatReader
1313
from pandas_datareader.exceptions import DEP_ERROR_MSG, ImmediateDeprecationError
1414
from pandas_datareader.famafrench import FamaFrenchReader
15+
from pandas_datareader.famafrench_in import FamaFrenchIndiaReader
1516
from pandas_datareader.fred import FredReader
1617
from pandas_datareader.oecd import OECDReader
1718

@@ -83,11 +84,15 @@ def DataReader(
8384
ff = DataReader("F-F_Research_Data_Factors_weekly", "famafrench")
8485
ff = DataReader("6_Portfolios_2x3", "famafrench")
8586
ff = DataReader("F-F_ST_Reversal_Factor", "famafrench")
87+
88+
# Data from Fama/French for Indian markets
89+
ff = DataReader("2025-12_FourFactors_and_Market_Returns_Daily", "famafrench_in")
8690
"""
8791
expected_source = [
8892
"bankofcanada",
8993
"fred",
9094
"famafrench",
95+
"famafrench_in",
9196
"oecd",
9297
"eurostat",
9398
"econdb",
@@ -126,7 +131,15 @@ def DataReader(
126131
pause=pause,
127132
session=session,
128133
).read()
129-
134+
if data_source == "famafrench_in":
135+
return FamaFrenchIndiaReader(
136+
symbols=name,
137+
start=start,
138+
end=end,
139+
retry_count=retry_count,
140+
pause=pause,
141+
session=session,
142+
).read()
130143
if data_source == "oecd":
131144
return OECDReader(
132145
symbols=name,

pandas_datareader/famafrench_in.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import datetime as dt
2+
import tempfile
3+
4+
from pandas import read_csv, to_datetime
5+
6+
from pandas_datareader.base import _BaseReader
7+
8+
_URL = "https://faculty.iima.ac.in/iffm/Indian-Fama-French-Momentum/"
9+
_URL_PREFIX = "DATA/"
10+
_URL_SUFFIX = "_SurvivorshipBiasAdjusted.csv"
11+
12+
13+
def _parse_date_famafrench(x):
14+
x = x.strip()
15+
try:
16+
return dt.datetime.strptime(x, "%Y%m")
17+
except Exception:
18+
pass
19+
return to_datetime(x)
20+
21+
22+
class FamaFrenchIndiaReader(_BaseReader):
23+
"""
24+
Get data for the given name from the Fama/French India data libary.
25+
26+
Credits to the IIM Ahemdabad
27+
https://faculty.iima.ac.in/iffm/Indian-Fama-French-Momentum/
28+
"""
29+
30+
@property
31+
def url(self):
32+
"""API URL"""
33+
return f"{_URL}{_URL_PREFIX}{self.symbols}{_URL_SUFFIX}"
34+
35+
def read(self):
36+
"""
37+
Read data
38+
39+
Returns
40+
-------
41+
df : DataFrame
42+
A data frame that is indexed by the date
43+
"""
44+
return super().read()
45+
46+
def _read_one_data(self, url, params):
47+
params = {
48+
"index_col": 0,
49+
"parse_dates": True,
50+
}
51+
52+
resp = self._get_response(url)
53+
54+
with tempfile.TemporaryFile() as tmpf:
55+
tmpf.write(resp.content)
56+
# seek to beginning so pandas can start reading the file
57+
tmpf.seek(0)
58+
59+
df = read_csv(tmpf, **params)
60+
61+
# this dataset defines (Market - Risk-Free) rate as `MF` instead
62+
# of `Mkt-RF`, so renaming it for better convenience.
63+
df.rename(columns={"MF": "Mkt-RF"}, inplace=True)
64+
65+
if self.start is not None:
66+
df = df[self.start :]
67+
elif self.start is not None and self.end is not None:
68+
df = df[self.start : self.end]
69+
elif self.end is not None:
70+
df = df[: self.end]
71+
72+
return df
73+
return None
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pandas as pd
2+
3+
from pandas_datareader import data as web
4+
5+
6+
class TestFamaFrenchIndia:
7+
def test_mkt_rf_rename(self):
8+
ff = web.DataReader(
9+
"2025-12_FourFactors_and_Market_Returns_Daily", "famafrench_in"
10+
)
11+
# assert that the col exists in the dataframe
12+
assert ff["Mkt-RF"] is not None
13+
14+
def test_download_works_for_date_range(self):
15+
ff = web.DataReader(
16+
"2025-12_FourFactors_and_Market_Returns_Daily",
17+
"famafrench_in",
18+
start="2025-01-01",
19+
end="2025-12-31",
20+
)
21+
22+
assert ff.iloc[0].name.date() == pd.to_datetime("2025-01-01").date()
23+
assert ff.iloc[-1].name.date() == pd.to_datetime("2025-12-31").date()
24+
25+
def test_download_monthly(self):
26+
ff = web.DataReader(
27+
"2025-12_FourFactors_and_Market_Returns_Monthly", "famafrench_in"
28+
)
29+
assert ff is not None
30+
31+
def test_download_yearly(self):
32+
ff = web.DataReader(
33+
"2025-12_FourFactors_and_Market_Returns_Yearly", "famafrench_in"
34+
)
35+
36+
assert ff is not None

0 commit comments

Comments
 (0)