Evaluations of Manoj Kumar Jain's Fixed-Fixed-Double Strategies
import glob
import sys
import os
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
plt.rcParams['figure.figsize'] = [14, 7]
# Config options
cwd = 'data'
dateparse = lambda x: pd.datetime.strptime(x, '%d-%b-%Y')
nifty = pd.concat([pd.read_csv(cwd + '/' + f, parse_dates=['Date'], date_parser=dateparse) for f in sorted(os.listdir(cwd))], ignore_index = True)
nifty.describe()
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
</style>
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
Open | High | Low | Close | Shares Traded | Turnover (Rs. Cr) | |
---|---|---|---|---|---|---|
count | 4903.000000 | 4903.000000 | 4903.000000 | 4903.000000 | 4.903000e+03 | 4903.000000 |
mean | 5028.744983 | 5062.516990 | 4989.049154 | 5026.400959 | 1.498470e+08 | 6290.407073 |
std | 3138.698027 | 3146.534063 | 3123.482854 | 3134.811321 | 9.859852e+07 | 4425.010146 |
min | 853.000000 | 877.000000 | 849.950000 | 854.200000 | 1.394931e+06 | 40.120000 |
25% | 1919.050000 | 1942.400000 | 1901.650000 | 1918.425000 | 7.620246e+07 | 2958.745000 |
50% | 5005.350000 | 5057.500000 | 4956.450000 | 5003.950000 | 1.345816e+08 | 5647.400000 |
75% | 7633.225000 | 7676.725000 | 7580.650000 | 7619.150000 | 1.909097e+08 | 8186.865000 |
max | 12052.650000 | 12103.050000 | 12005.850000 | 12088.550000 | 7.411532e+08 | 35131.190000 |
nifty.dtypes
Date datetime64[ns]
Open float64
High float64
Low float64
Close float64
Shares Traded int64
Turnover (Rs. Cr) float64
dtype: object
nifty.head()
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
</style>
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
Date | Open | High | Low | Close | Shares Traded | Turnover (Rs. Cr) | |
---|---|---|---|---|---|---|---|
0 | 2000-01-03 | 1482.15 | 1592.90 | 1482.15 | 1592.2 | 25358322 | 884.15 |
1 | 2000-01-04 | 1594.40 | 1641.95 | 1594.40 | 1638.7 | 38787872 | 1973.69 |
2 | 2000-01-05 | 1634.55 | 1635.50 | 1555.05 | 1595.8 | 62153431 | 3084.79 |
3 | 2000-01-06 | 1595.80 | 1639.00 | 1595.80 | 1617.6 | 51272875 | 2531.18 |
4 | 2000-01-07 | 1616.60 | 1628.25 | 1597.20 | 1613.3 | 54315945 | 1914.63 |
nifty = nifty.set_index('Date')
for i in nifty.index[:10]:
if (i.month==1 and i.day==11):
print('equal')
print(i)
nifty[datetime(2001,1,3):datetime(2001,1,3)]
2000-01-03 00:00:00
2000-01-04 00:00:00
2000-01-05 00:00:00
2000-01-06 00:00:00
2000-01-07 00:00:00
2000-01-10 00:00:00
equal
2000-01-11 00:00:00
2000-01-12 00:00:00
2000-01-13 00:00:00
2000-01-14 00:00:00
Timestamp('2019-09-19 00:00:00')
nifty["52 week high"] = pd.Series.rolling(nifty.High, window=200, min_periods=1).max()
nifty["52 week low"] = pd.Series.rolling(nifty.Low, window=200, min_periods=1).min()
nifty[["52 week high", "52 week low", "Close"]].plot()
# nifty[["High"]].plot()
<matplotlib.axes._subplots.AxesSubplot at 0x7ff98aa50358>
highs = (nifty["52 week high"] == nifty.High).value_counts()
print("Fraction of times market is at highs:", highs[True]/(highs[True] + highs[False]))
lows = (nifty["52 week low"] == nifty.Low).value_counts()
print("Fraction of times market is at lows:", lows[True]/(lows[True] + lows[False]))
Fraction of times market is at highs: 0.11054456455231491
Fraction of times market is at lows: 0.016724454415663878
class Parameters:
def __init__(self, **kwds):
self.__dict__.update(kwds)
params = Parameters(bear_delta=5, bear_percent=4, bear_ndays=100)
# Variables almost remaining constants
debt_rate = 0.074 # percent
# DebtCorpus with both a deposit() and a withdraw() function
class DebtCorpus:
def __init__(self):
self.balance = 0
self.date = datetime(1990, 1, 1)
def Check(self, date):
if (date < self.date):
print("Date cannot be less than last account operation date")
return False
return True
def Deposit(self, date, amount):
if (self.Check(date) == False):
return False
delta = (date - self.date).days / 365.25
self.balance = amount + self.balance * pow(1 + debt_rate, delta)
self.date = date
return True
def Withdraw(self, date, amount):
if (self.Check(date) == False):
return False
delta = (date - self.date).days / 365.25
self.balance = self.balance * pow(1 + debt_rate, delta)
self.date = date
fulfilled = min(self.balance, amount)
if self.balance <= amount:
print('Balance is lesser than requested', self.balance, amount)
self.balance -= fulfilled
return fulfilled
def Get(self, date):
delta = (date - self.date).days / 365.25
self.balance = self.balance * pow(1 + debt_rate, delta)
self.date = date
return self.balance
# debt = DebtCorpus()
# print(debt.Deposit(datetime(2000, 1, 1), 100))
# print(debt.Withdraw(datetime(2010, 1, 1), 100))
# debt.Get(datetime(2020, 1, 1))
True
100
212.77761431538931
def EvaluateStrategy(df, params, pa = 100):
naive_sips, curr_year = [], 0
equity_investments = []
debt_corpus = DebtCorpus()
total_invested = 0
min_chkpts = []
bear_delta = params.bear_delta
bear_percent = params.bear_percent
nifty["bear_ndays"] = pd.Series.rolling(nifty.Low, window=params.bear_ndays, min_periods=1).min()
for ind in df.index:
if (naive_sips == [] or curr_year < ind.year):
total_invested += pa
naive_sips.append((ind, df.Close[0], pa))
debt_corpus.Deposit(ind, pa)
curr_year = ind.year
if (df["bear_ndays"][ind] == df.Low[ind]):
if (min_chkpts == []):
min_chkpts = [ind]
if (df.Close[ind] < (1 - bear_percent/100.0) * df.Close[min_chkpts[-1]]):
min_chkpts.append(ind)
if (len(min_chkpts)%2 == 0):
bear_delta = bear_delta * 2
# Make investment
amount = debt_corpus.Withdraw(ind, bear_delta)
if (amount>0):
equity_investments.append((df.Close[ind], amount))
print (ind, df.Close[ind], amount, (1 - bear_percent/100.0))
print("total_invested", total_invested)
# Calculate the final profits
index_close = df.Close.iloc[-1]
equity_amount, equity_invested = 0, 0
for i in equity_investments:
equity_invested += i[1]
equity_amount += i[1] * index_close / i[0]
print('equity_amount:', equity_amount)
print('equity_invested:', equity_invested)
print("Equity returns:", equity_amount/equity_invested)
debt_amount = debt_corpus.Get(df.index[-1])
print('debt_amount:', debt_amount)
overall_amount = debt_amount + equity_amount
print("Overall returns:", overall_amount/total_invested)
sip_amount = 0
for i in naive_sips:
print(i[2], index_close, i[1])
sip_amount += i[2] * index_close / i[1]
print("Naive SIP returns:", sip_amount / total_invested)
# CAGR = [ (Ending value/Beginning Value)^(1/N) ] - 1
EvaluateStrategy(nifty, params)
2000-04-04 00:00:00 1428.1 10 0.96
2000-04-25 00:00:00 1359.45 10 0.96
2000-05-15 00:00:00 1299.25 20 0.96
2000-05-23 00:00:00 1224.4 20 0.96
2000-10-17 00:00:00 1158.05 40 0.96
2001-04-10 00:00:00 1103.05 40 0.96
Balance is lesser than requested 65.96764728036544 80
2001-04-12 00:00:00 1024.9 65.96764728036544 0.96
Balance is lesser than requested 0.0 80
Balance is lesser than requested 0.0 160
Balance is lesser than requested 0.0 160
total_invested 2000
equity_amount: 1940.2998688468788
equity_invested: 205.96764728036544
Equity returns: 9.420410896890623
debt_amount: 3717.814819874003
Overall returns: 2.8290573443604408
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
100 10704.8 1592.2
Naive SIP returns: 6.723275970355481
import glob
import sys
import os
import enum
import json
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
plt.rcParams['figure.figsize'] = [14, 7]
import import_ipynb
import drivers
import prepare
importing Jupyter notebook from drivers.ipynb
importing Jupyter notebook from prepare.ipynb
nifty = prepare.MergedDf()
nifty.describe()
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
</style>
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
Open | High | Low | Close | Shares Traded | Turnover (Rs. Cr) | P/E | P/B | Div Yield | |
---|---|---|---|---|---|---|---|---|---|
count | 5225.000000 | 5225.000000 | 5225.000000 | 5225.000000 | 5.225000e+03 | 5225.000000 | 5225.000000 | 5225.000000 | 5225.000000 |
mean | 4931.530211 | 4964.677742 | 4892.778804 | 4929.321809 | 1.515437e+08 | 6250.129768 | 19.882100 | 3.539041 | 1.419129 |
std | 3247.573752 | 3256.301398 | 3231.571643 | 3243.876880 | 1.191109e+08 | 4840.834747 | 4.159403 | 0.798190 | 0.400195 |
min | 853.000000 | 877.000000 | 849.950000 | 854.200000 | 1.394931e+06 | 40.120000 | 10.680000 | 1.920000 | 0.590000 |
25% | 1667.450000 | 1688.250000 | 1644.400000 | 1668.750000 | 6.926587e+07 | 2620.680000 | 17.010000 | 3.020000 | 1.160000 |
50% | 4877.850000 | 4930.250000 | 4833.050000 | 4875.050000 | 1.305892e+08 | 5462.340000 | 19.940000 | 3.470000 | 1.320000 |
75% | 7588.550000 | 7635.550000 | 7532.450000 | 7580.200000 | 1.900432e+08 | 8149.000000 | 22.660000 | 3.800000 | 1.540000 |
max | 12274.900000 | 12293.900000 | 12252.750000 | 12271.800000 | 1.414837e+09 | 54081.530000 | 29.900000 | 6.550000 | 3.180000 |
nifty.loc['1999-01-04']
Open 896.40
High 905.45
Low 895.75
Close 897.80
Shares Traded 32224833.00
Turnover (Rs. Cr) 811.39
P/E 11.72
P/B 2.08
Div Yield 1.81
Name: 1999-01-04 00:00:00, dtype: float64
# debt = DebtCorpus()
# print(debt.Deposit(datetime(2000, 1, 1), 100))
# print(debt.Withdraw(datetime(2010, 1, 1), 100))
# debt.Get(datetime(2020, 1, 1))
# vanilla strategy with params
# monthly_sip = 100
# default_exposure = 0.5
# green_pe = 15
# red_pe = 28
# Every month, invest monthly_sip * default_exposure in index and invest monthly_sip * (1 - default_exposure) in debt.
# If nifty pe > red_pe, pull out all money from index to debt.
# if nifty pe < green_pe, pull out all money from debt to index.
nifty.index[-1].to_pydatetime()
datetime.datetime(2019, 12, 31, 0, 0)
def EvaluateStrategy(df, params):
print('params:', json.dumps(params.__dict__, indent=2))
push_num_installments = int(params.push_num_installments)
pull_num_installments = int(params.pull_num_installments)
# strategy
curr_month = -1
e = drivers.EquityCorpus(df)
d = drivers.DebtCorpus()
total_invested = 0
num_installments = 0
size_installment = 0;
for ind in df.index:
if ind.month != curr_month:
curr_month = ind.month
index_sip = params.monthly_sip * params.default_exposure
debt_sip = params.monthly_sip * (1 - params.default_exposure)
current_pe = df['P/E'][ind]
if (current_pe < params.green_pe):
# we are in bear market.
if (0 == num_installments):
debt_funds = d.Get(ind)
# print('debt_funds', debt_funds, ind)
size_installment = debt_funds / params.push_num_installments
to_invest = size_installment if num_installments < params.push_num_installments else d.Get(ind)
# print('to_invest', to_invest, size_installment, d.Get(ind))
debt_sip -= to_invest
index_sip += to_invest
num_installments+=1
elif (current_pe > params.red_pe):
# we are in bull market
equity_funds = e.Get(ind)
if (0 == num_installments):
# print('equity_funds', equity_funds, ind)
size_installment = equity_funds / params.pull_num_installments
to_redeem = min(size_installment, equity_funds)
# print('to_redeem', to_redeem, size_installment, e.Get(ind), ind)
index_sip -= to_redeem
debt_sip += to_redeem
num_installments+=1
else:
num_installments = 0
assert abs(index_sip + debt_sip - params.monthly_sip) < 0.01,\
'index_sip:' + str(index_sip) + ', debt_sip: ' + str(debt_sip) + ', monthly_sip:' + str(params.monthly_sip)
if (index_sip > 0):
# print('deposit in equity', index_sip, ind)
e.Deposit(ind, index_sip)
elif (index_sip < 0):
# print('withdraw from equity', index_sip, ind)
e.Withdraw(ind, - index_sip)
if (debt_sip > 0):
d.Deposit(ind, debt_sip)
elif (debt_sip < 0):
d.Withdraw(ind, - debt_sip)
total_invested += params.monthly_sip
start_date = df.index[0].to_pydatetime()
end_date = df.index[-1].to_pydatetime()
# print('start-end', start_date, end_date)
# print('total_invested', total_invested)
# print('e.Get()', e.Get(end_date))
# print('d.Get()', d.Get(end_date))
returns = (e.Get(end_date) + d.Get(end_date)) / total_invested
print('returns', returns)
return returns
# # Debug Strategy
# params = drivers.Parameters(monthly_sip=100,
# default_exposure=0.0,
# green_pe=12,
# red_pe=22,
# pull_num_installments=2.0,
# push_num_installments=2.0)
# EvaluateStrategy(nifty, params)
# Always and only equity investor
params = drivers.Parameters(monthly_sip=100,
default_exposure=1,
green_pe=-1,
red_pe=100,
pull_num_installments=12,
push_num_installments=12)
EvaluateStrategy(nifty, params)
params: {
"monthly_sip": 100,
"default_exposure": 1,
"green_pe": -1,
"red_pe": 100,
"pull_num_installments": 12,
"push_num_installments": 12
}
returns 4.4901087169736345
4.4901087169736345
# Always and only debt investor
params = drivers.Parameters(monthly_sip=100,
default_exposure=0,
green_pe=-1,
red_pe=100,
pull_num_installments=12,
push_num_installments=12)
EvaluateStrategy(nifty, params)
params: {
"monthly_sip": 100,
"default_exposure": 0,
"green_pe": -1,
"red_pe": 100,
"pull_num_installments": 12,
"push_num_installments": 12
}
returns 2.326306709798203
2.326306709798203
# Mixed investor, no rebalancing.
params = drivers.Parameters(monthly_sip=100,
default_exposure=0.5,
green_pe=-1,
red_pe=100,
pull_num_installments=12,
push_num_installments=12)
EvaluateStrategy(nifty, params)
params: {
"monthly_sip": 100,
"default_exposure": 0.5,
"green_pe": -1,
"red_pe": 100,
"pull_num_installments": 12,
"push_num_installments": 12
}
returns 3.4082077133859188
3.4082077133859188
# Mixed investor, with rebalancing.
params = drivers.Parameters(monthly_sip=100,
default_exposure=0.5,
green_pe=18,
red_pe=28,
pull_num_installments=12,
push_num_installments=12)
EvaluateStrategy(nifty, params)
params: {
"monthly_sip": 100,
"default_exposure": 0.5,
"green_pe": 18,
"red_pe": 28,
"pull_num_installments": 12,
"push_num_installments": 12
}
returns 4.791885658856569
4.791885658856569
# A good strategy
params = drivers.Parameters(monthly_sip=100,
default_exposure=0.15789473684210525,
green_pe=16.63157894736842,
red_pe=24.526315789473685,
pull_num_installments=2.0,
push_num_installments=2.0)
EvaluateStrategy(nifty, params)
params: {
"monthly_sip": 100,
"default_exposure": 0.15789473684210525,
"green_pe": 16.63157894736842,
"red_pe": 24.526315789473685,
"pull_num_installments": 2.0,
"push_num_installments": 2.0
}
returns 11.413761644520301
11.413761644520301
from scipy import optimize
def f(z, *params):
de, gpe, rpe, pli, psi = z
p = drivers.Parameters(monthly_sip=100,
default_exposure=de,
green_pe=gpe,
red_pe=rpe,
pull_num_installments=pli,
push_num_installments=psi)
return - 1 * EvaluateStrategy(nifty, p)
rranges = ((0.0, 1.0), (12, 20), (22, 30), slice(2, 12), slice(2, 12))
resbrute = optimize.brute(f, rranges, args=None, full_output=True,
finish=optimize.fmin)