max curve time error in Heston calibration quantlib python - python
I am running a compiled from source SWIG python 1.16 version of QuantLib.
I have been trying to calibrate a heston model following this example.
I am only using the QL calibration at the moment to test it out before trying others.
I need time dependent parameters so I am using PiecewiseTimeDependentHestonModel.
Here is the relevant portion of my code.
Helper functions :
def tenor2date(s, base_date=None,ql=False):
# returns a date from a tenor and a base date
if base_date is None:
base_date = datetime.today()
num = float(s[:-1])
period = s[-1].upper()
if period == "Y":
return_date = base_date + relativedelta(years=num)
elif period == "M":
return_date = base_date + relativedelta(months=num)
elif period == "W":
return_date = base_date + relativedelta(weeks=num)
elif period == "D":
return_date = base_date + relativedelta(days=num)
else:
return_date = base_date
if ql:
return Date(return_date.strftime("%F"),"yyyy-mm-dd")
else:
return return_date
def setup_model(yield_ts, dividend_ts, spot, times,init_condition=(0.02, 0.2, 0.5, 0.1, 0.01)):
theta, kappa, sigma, rho, v0 = init_condition
model = ql.PiecewiseTimeDependentHestonModel(yield_ts, dividend_ts, ql.QuoteHandle(ql.SimpleQuote(spot)), v0, ql.Parameter(), ql.Parameter(),
ql.Parameter(), ql.Parameter(), ql.TimeGrid(times))
engine = ql.AnalyticPTDHestonEngine(model)
return model, engine
def setup_helpers(engine, vol_surface, ref_date, spot, yield_ts, dividend_ts):
heston_helpers = []
grid_data = []
for tenor in vol_surface:
expiry_date = tenor2date(tenor, datetime(ref_date.year(), ref_date.month(), ref_date.dayOfMonth()), True)
t = (expiry_date - ref_date)
print(f"{tenor} : {t / 365}")
p = ql.Period(t, ql.Days)
for strike, vol in zip(vol_surface[tenor]["strikes"], vol_surface[tenor]["volatilities"]):
print((strike, vol))
helper = ql.HestonModelHelper(p, calendar, spot, strike, ql.QuoteHandle(ql.SimpleQuote(vol / 100)), yield_ts, dividend_ts)
helper.setPricingEngine(engine)
heston_helpers.append(helper)
grid_data.append((expiry_date, strike))
return heston_helpers, grid_data
Market data :
vol_surface = {'12M': {'strikes': [1.0030154025220293, 0.9840808634190958, 0.9589657270688433, 0.9408279805370683, 0.9174122318462831, 0.8963792435025802, 0.8787138822765832, 0.8538712672800733, 0.8355036501980958], 'volatilities': [6.7175, 6.5, 6.24375, 6.145, 6.195, 6.425, 6.72125, 7.21, 7.5625], 'forward': 0.919323}, '1M': {'strikes': [0.9369864196692815, 0.9324482223892986, 0.9261255003380027, 0.9213195223581382, 0.9150244003650484, 0.9088253068972495, 0.9038936313900919, 0.897245676067657, 0.8924388848562849], 'volatilities': [6.3475, 6.23375, 6.1075, 6.06, 6.09, 6.215, 6.3725, 6.63125, 6.8225], 'forward': 0.915169}, '1W': {'strikes': [0.9258809998009043, 0.9236526412979602, 0.920487656155217, 0.9180490618315417, 0.9148370595017086, 0.9116231311263782, 0.9090950947170667, 0.9057357691404444, 0.9033397443834199], 'volatilities': [6.7175, 6.63375, 6.53625, 6.5025, 6.53, 6.6425, 6.77875, 6.99625, 7.1525], 'forward': 0.914875}, '2M': {'strikes': [0.9456173410343232, 0.9392447942175677, 0.9304717860942596, 0.9238709412876663, 0.9152350197527926, 0.9068086964842931, 0.9000335970840222, 0.8908167643473346, 0.884110721680849], 'volatilities': [6.1575, 6.02625, 5.8825, 5.8325, 5.87, 6.0175, 6.1975, 6.48875, 6.7025], 'forward': 0.915506}, '3M': {'strikes': [0.9533543407827232, 0.945357456067501, 0.9343646071178692, 0.9261489737826977, 0.9154251386183144, 0.9050707394248945, 0.8966770979707913, 0.8851907303568785, 0.876803402158318], 'volatilities': [6.23, 6.09125, 5.93, 5.8725, 5.915, 6.0775, 6.28, 6.60375, 6.84], 'forward': 0.915841}, '4M': {'strikes': [0.9603950279333742, 0.9509237742916833, 0.9379657828957041, 0.928295643018581, 0.9156834006905108, 0.9036539552069216, 0.8938804229269658, 0.8804999196762403, 0.870730837142799], 'volatilities': [6.3175, 6.17125, 6.005, 5.94375, 5.985, 6.15125, 6.36, 6.69375, 6.9375], 'forward': 0.916255}, '6M': {'strikes': [0.9719887962018352, 0.9599837798239937, 0.943700651576822, 0.9316544554849711, 0.9159768970939797, 0.9013018796367052, 0.8892904835162911, 0.8727031923006017, 0.8605425787295339], 'volatilities': [6.3925, 6.22875, 6.04125, 5.9725, 6.01, 6.1875, 6.41375, 6.78625, 7.0575], 'forward': 0.916851}, '9M': {'strikes': [0.9879332225745909, 0.9724112749400833, 0.951642771321364, 0.936450663789222, 0.9167103888580063, 0.8985852649047051, 0.8835274087791912, 0.8625837214139542, 0.8472311260811375], 'volatilities': [6.54, 6.34875, 6.1325, 6.055, 6.11, 6.32, 6.5875, 7.01625, 7.32], 'forward': 0.918086}}
spotDates = [ql.Date(1,7,2019), ql.Date(8,7,2019), ql.Date(1,8,2019), ql.Date(1,9,2019), ql.Date(1,10,2019), ql.Date(1,11,2019), ql.Date(1,1,2020), ql.Date(1,4,2020), ql.Date(1,7,2020)]
spotRates = [0.9148, 0.914875, 0.915169, 0.915506, 0.915841, 0.916255, 0.916851, 0.918086, 0.919323]
udl_value = 0.9148
todaysDate = ql.Date("2019-07-01","yyyy-mm-dd")
settlementDate = ql.Date("2019-07-03","yyyy-mm-dd")
and the script itself:
ql.Settings.instance().evaluationDate = todaysDate
dayCounter = ql.Actual365Fixed()
interpolation = ql.Linear()
compounding = ql.Compounded
compoundingFrequency = ql.Annual
times = [(x - spotDates[0]) / 365 for x in spotDates][1:]
discountFactors = [-log(x / spotRates[0]) / (times[i]) for i, x in enumerate(spotRates[1:])]
fwdCurve = ql.ZeroCurve(spotDates, [0] + discountFactors, dayCounter, calendar, interpolation, compounding, compoundingFrequency)
fwdCurveHandle = ql.YieldTermStructureHandle(fwdCurve)
dividendCurveHandle = ql.YieldTermStructureHandle(ql.FlatForward(settlementDate, 0, dayCounter))
hestonModel, hestonEngine = setup_model(fwdCurveHandle, dividendCurveHandle, udl_value, times)
heston_helpers, grid_data = setup_helpers(hestonEngine, vol_surface, todaysDate, udl_value, fwdCurveHandle, dividendCurveHandle)
lm = ql.LevenbergMarquardt(1e-8, 1e-8, 1e-8)
hestonModel.calibrate(heston_helpers, lm, ql.EndCriteria(500, 300, 1.0e-8, 1.0e-8, 1.0e-8))
When I run the last line I get the following error message :
RuntimeError: time (1.42466) is past max curve time (1.00274)
I do not understand how it can try to price things beyond 1Y as both the helpers and the forwards curve are defined on the same set of dates.
In case it helps someone, posting here the answer I got from the quantlb mailing :
specifying the maturity in days
t = (expiry_date - ref_date)
print(f"{tenor} : {t / 365}")
p = ql.Period(t, ql.Days)
might have an counterintuitive effect here as the specified calendar is used
to calculate the real expiry date. If the calendar is e.g. ql.UnitedStates
then this takes weekends and holidays into consideration,
ql.UnitedStates().advance(ql.Date(1,1,2019),ql.Period(365, ql.Days)) => Date(12,6,2020)
whereas
ql.NullCalendar().advance(ql.Date(1,1,2019),ql.Period(365, ql.Days)) => Date(1,1,2020)
hence I guess the interest rate curve is not long enough and throws the
error message.
So the fix is to make sure to use ql.NullCalendar() accross.
Related
Getting Expired Options Contract pricing from Interactive Brokers
I am looking to reconstruct Expired Options pricing with the help of ib_insync library and Interactive Brokers available data.. Because IB provides OPTION_IMPLIED_VOLATILITY as an output for reqHistoricalData I was thinking proceeding this way: Have a function to infer Expired Options Contract Prices from Black-Scholes Model: def black_scholes(stock_price,strike_price,vol,time,rate,right="Call"): d1 = (np.log(stock_price/strike_price) + (rate + 0.5* vol**2)*time)/(vol*np.sqrt(time)) d2 = (np.log(stock_price/strike_price) + (rate - 0.5* vol**2)*time)/(vol*np.sqrt(time)) nd1 = norm.cdf(d1) nd2 = norm.cdf(d2) n_d1 = norm.cdf(-1*d1) n_d2 = norm.cdf(-1*d2) if right.capitalize()[0] == "C": return round((stock_price*nd1) - (strike_price*np.exp(-1*rate*time)*nd2),2) else: return round((strike_price*np.exp(-1*rate*time)*n_d2) - (stock_price*n_d1),2) Using the contract on the underlying stock assuming I have a valid ib connection opened elsewhere in my code to retrieve the data def get_stock_history(symbol,whattoshow_string): contract = Stock(symbol, 'SMART', 'USD') ib.reqMarketDataType(2) bars = ib.reqHistoricalData( contract, endDateTime='', durationStr='2 Y', barSizeSetting='1 Hour', whatToShow=whattoshow_string, useRTH=True, formatDate=1) ib.sleep(5) df = util.df(bars) df['date'] = pd.to_datetime(df['date'] ).dt.date return df I also have a handy function to compute maturity in the BSM based on an hourly time decay: def hourCount(DF, expiry): DF["maturity"] = ((dt.datetime.strptime(expiry, "%Y-%m-%d") - pd.to_datetime(DF.index))/pd.Timedelta(hours=1))/(365*24) I could then get the data as below assuming I have an expiration date and strike from elsewhere I wish to backtest: strike = 148 expiration_date = '2022-12-02' symbol = 'AAPL' historicalData = get_stock_history(symbol,'ADJUSTED_LAST') impVolData = get_stock_history(symbol,'OPTION_IMPLIED_VOLATILITY') option_price_call = pd.DataFrame(columns=["open","high","low","close"]) option_price_put = pd.DataFrame(columns=["open","high","low","close"]) hourCount(historicalData, expiration_date) hourCount(impVolData, expiration_date) historicalData = historicalData [(historicalData["maturity"] > 0)] impVolData = impVolData[(impVolData["maturity"] > 0)] for column in ["open","high","low","close"]: option_price_call[column] = black_scholes(historicalData[column], strike, impVolData[column], historicalData["maturity"], 0.03,right="Call") option_price_put[column] = black_scholes(historicalData[column], strike, impVolData[column], historicalData["maturity"], 0.03,right="Put") Would that be a good approach to reconstruct/backtest the Expired Options contract pricing or am I overlooking something here? and maybe a smarter way to achieve this operation? Thanks in advance for your suggestions! (y)
How can I price Digital option with short maturity
I am trying to price a Digital Call option using Quantlib Python package, but I have an inconsistent result. I am trying to price a deep ITM option with a 1 day maturity. The thing is that it returns me 0, which doesn't make sense. Could you please help me? def Digital_Call(s0: float, strike: float, risk_free: float, vol: float, today: datetime, maturity: datetime, cash_payoff: float): "Output: option_npv" dividend_yield = 0 riskFreeTS = ql.YieldTermStructureHandle(ql.FlatForward(today, risk_free, ql.ActualActual())) dividendTS = ql.YieldTermStructureHandle(ql.FlatForward(today, dividend_yield, ql.ActualActual())) volatility = ql.BlackVolTermStructureHandle(ql.BlackConstantVol(today, ql.NullCalendar(), vol, ql.ActualActual())) initialValue = ql.QuoteHandle(ql.SimpleQuote(s0)) process = ql.BlackScholesMertonProcess(initialValue, dividendTS, riskFreeTS, volatility) engine = ql.AnalyticEuropeanEngine(process) option_type = ql.Option.Call option = ql.VanillaOption(ql.CashOrNothingPayoff(option_type, strike, cash_payoff), ql.EuropeanExercise(maturity)) option.setPricingEngine(engine) return option s0=150 strike = 14.36 risk_free = 0.0302373913 #risk_free = 0 vol = 0.2567723271 today = datetime(2022,9,28) maturity = datetime(2022,9,30) #it doesn't work with Maturity of both 1 & 2 days cash_payoff = 30 It returns 0. Thank You
How to backtest portfolio compositions using backtrader?
I have a csv file / pandas dataframe which looks like this. It contains various portfolio compositions for a portfolio which is re-balanced everyday according to my own calculations. date asset percentage 4-Jan-21 AAPL 12.00% 4-Jan-21 TSM 1.00% 4-Jan-21 IBM 31.00% 4-Jan-21 KO 15.00% 4-Jan-21 AMD 41.00% 5-Jan-21 DELL 23.00% 5-Jan-21 TSM 12.20% 5-Jan-21 IBM 15.24% 5-Jan-21 KO 1.50% 5-Jan-21 NKE 7.50% 5-Jan-21 TSLA 9.50% 5-Jan-21 CSCO 3.30% 5-Jan-21 JPM 27.76% 6-Jan-21 AMD 45% 6-Jan-21 BA 0.50% 6-Jan-21 ORCL 54.50% 7-Jan-21 AAPL 50.00% 7-Jan-21 KO 50.00% ... I want to test a strategy with a 12 asset portfolio. AAPL,TSM,IBM,KO,AMD,DELL,NKE,TSLA,CSCO,JPM,BA,ORCL So let's say on 4Jan2021, the portfolio's composition would be 12% in apple, 1% in TSM.. etc. I want to be able to check the prices and know how many I should be holding. The next day, 5Jan2021, the composition will change to 23% in Dell.. etc, if the stock isn't in this list means its 0% for that day. I have been looking at backtrader as a backtesting platform, however, the code I have seen in the repo mostly shows how to do stuff with indicators, like SMA cross over, RSI... My question is: Is it possible to create and test a portfolio based on these compositions I have so I can check the return of this strategy? It would check this frame, and know how many stocks in a ticker to buy or sell on that particular day. So the universe of stocks I am buying or sell is AAPL,TSM,IBM,KO,AMD,DELL,NKE,TSLA,CSCO,JPM,BA,ORCL So on 4-Jan-21 it might look like, dictionary['4Jan2021'] = {'AAPL':0.12, 'TSM':0.01, 'IBM':0.31, 'KO':0.15, 'AMD':0.41,} On 5-Jan-21 it will look like, dictionary['5Jan2021'] = {'DELL':0.23, 'TSM':0.122, 'IBM':0.1524, 'KO':0.015, 'NKE':0.075, 'TSLA':0.095, 'CSCO':0.033, 'JPM':0.2776,} If the ticker isnt there means its 0%. The portfolio composition needs to change everyday.
The first thing you will want to do it load your targets with your datas. I like personally to attach the target to the dataline as I add it to backtrader. tickers = {"FB": 0.25, "MSFT": 0.4, "TSLA": 0.35} for ticker, target in tickers.items(): data = bt.feeds.YahooFinanceData( dataname=ticker, timeframe=bt.TimeFrame.Days, fromdate=datetime.datetime(2019, 1, 1), todate=datetime.datetime(2020, 12, 31), reverse=False, ) data.target = target cerebro.adddata(data, name=ticker) In next you will want to go through each data, and determine the current allocation. If the current allocation is too far from the desired allocation (threshold) you trade all datas. Notice there is a buffer variable. This will reduce the overall value of the account for calculating units to trade. This helps avoid margin. You will use a dictionary to track this information. def next(self): track_trades = dict() total_value = self.broker.get_value() * (1 - self.p.buffer) for d in self.datas: track_trades[d] = dict() value = self.broker.get_value(datas=[d]) allocation = value / total_value units_to_trade = (d.target - allocation) * total_value / d.close[0] track_trades[d]["units"] = units_to_trade # Can check to make sure there is enough distance away from ideal to trade. track_trades[d]["threshold"] = abs(d.target - allocation) > self.p.threshold Check all the thresholds to determine if trading. If any of datas need trading, then all need trading. rebalance = False for values in track_trades.values(): if values['threshold']: rebalance = True if not rebalance: return Finally, execute your trades. Always sell first to generate cash in the account and avoid margins. # Sell shares first for d, value in track_trades.items(): if value["units"] < 0: self.sell(d, size=value["units"]) # Buy shares second for d, value in track_trades.items(): if value["units"] > 0: self.buy(d, size=value["units"]) Here is the all of the code for your reference. import datetime import backtrader as bt class Strategy(bt.Strategy): params = ( ("buffer", 0.05), ("threshold", 0.025), ) def log(self, txt, dt=None): """ Logging function fot this strategy""" dt = dt or self.data.datetime[0] if isinstance(dt, float): dt = bt.num2date(dt) print("%s, %s" % (dt.date(), txt)) def print_signal(self): self.log( f"o {self.datas[0].open[0]:7.2f} " f"h {self.datas[0].high[0]:7.2f} " f"l {self.datas[0].low[0]:7.2f} " f"c {self.datas[0].close[0]:7.2f} " f"v {self.datas[0].volume[0]:7.0f} " ) def notify_order(self, order): """ Triggered upon changes to orders. """ # Suppress notification if it is just a submitted order. if order.status == order.Submitted: return # Print out the date, security name, order number and status. type = "Buy" if order.isbuy() else "Sell" self.log( f"{order.data._name:<6} Order: {order.ref:3d} " f"Type: {type:<5}\tStatus" f" {order.getstatusname():<8} \t" f"Size: {order.created.size:9.4f} Price: {order.created.price:9.4f} " f"Position: {self.getposition(order.data).size:5.2f}" ) if order.status == order.Margin: return # Check if an order has been completed if order.status in [order.Completed]: self.log( f"{order.data._name:<6} {('BUY' if order.isbuy() else 'SELL'):<5} " # f"EXECUTED for: {dn} " f"Price: {order.executed.price:6.2f} " f"Cost: {order.executed.value:6.2f} " f"Comm: {order.executed.comm:4.2f} " f"Size: {order.created.size:9.4f} " ) def notify_trade(self, trade): """Provides notification of closed trades.""" if trade.isclosed: self.log( "{} Closed: PnL Gross {}, Net {},".format( trade.data._name, round(trade.pnl, 2), round(trade.pnlcomm, 1), ) ) def next(self): track_trades = dict() total_value = self.broker.get_value() * (1 - self.p.buffer) for d in self.datas: track_trades[d] = dict() value = self.broker.get_value(datas=[d]) allocation = value / total_value units_to_trade = (d.target - allocation) * total_value / d.close[0] track_trades[d]["units"] = units_to_trade # Can check to make sure there is enough distance away from ideal to trade. track_trades[d]["threshold"] = abs(d.target - allocation) > self.p.threshold rebalance = False for values in track_trades.values(): if values['threshold']: rebalance = True if not rebalance: return # Sell shares first for d, value in track_trades.items(): if value["units"] < 0: self.sell(d, size=value["units"]) # Buy shares second for d, value in track_trades.items(): if value["units"] > 0: self.buy(d, size=value["units"]) if __name__ == "__main__": cerebro = bt.Cerebro() tickers = {"FB": 0.25, "MSFT": 0.4, "TSLA": 0.35} for ticker, target in tickers.items(): data = bt.feeds.YahooFinanceData( dataname=ticker, timeframe=bt.TimeFrame.Days, fromdate=datetime.datetime(2019, 1, 1), todate=datetime.datetime(2020, 12, 31), reverse=False, ) data.target = target cerebro.adddata(data, name=ticker) cerebro.addstrategy(Strategy) # Execute cerebro.run() #################################### ############# EDIT ############### #################################### There was an additional requiest for adding in variable allocations per day per security. The following code accomplishes that. import datetime import backtrader as bt class Strategy(bt.Strategy): params = ( ("buffer", 0.05), ("threshold", 0.025), ) def log(self, txt, dt=None): """ Logging function fot this strategy""" dt = dt or self.data.datetime[0] if isinstance(dt, float): dt = bt.num2date(dt) print("%s, %s" % (dt.date(), txt)) def print_signal(self): self.log( f"o {self.datas[0].open[0]:7.2f} " f"h {self.datas[0].high[0]:7.2f} " f"l {self.datas[0].low[0]:7.2f} " f"c {self.datas[0].close[0]:7.2f} " f"v {self.datas[0].volume[0]:7.0f} " ) def notify_order(self, order): """ Triggered upon changes to orders. """ # Suppress notification if it is just a submitted order. if order.status == order.Submitted: return # Print out the date, security name, order number and status. type = "Buy" if order.isbuy() else "Sell" self.log( f"{order.data._name:<6} Order: {order.ref:3d} " f"Type: {type:<5}\tStatus" f" {order.getstatusname():<8} \t" f"Size: {order.created.size:9.4f} Price: {order.created.price:9.4f} " f"Position: {self.getposition(order.data).size:5.2f}" ) if order.status == order.Margin: return # Check if an order has been completed if order.status in [order.Completed]: self.log( f"{order.data._name:<6} {('BUY' if order.isbuy() else 'SELL'):<5} " # f"EXECUTED for: {dn} " f"Price: {order.executed.price:6.2f} " f"Cost: {order.executed.value:6.2f} " f"Comm: {order.executed.comm:4.2f} " f"Size: {order.created.size:9.4f} " ) def notify_trade(self, trade): """Provides notification of closed trades.""" if trade.isclosed: self.log( "{} Closed: PnL Gross {}, Net {},".format( trade.data._name, round(trade.pnl, 2), round(trade.pnlcomm, 1), ) ) def __init__(self): for d in self.datas: d.target = { datetime.datetime.strptime(date, "%d-%b-%y").date(): allocation for date, allocation in d.target.items() } def next(self): date = self.data.datetime.date() track_trades = dict() total_value = self.broker.get_value() * (1 - self.p.buffer) for d in self.datas: if date not in d.target: if self.getposition(d): self.close(d) continue target_allocation = d.target[date] track_trades[d] = dict() value = self.broker.get_value(datas=[d]) current_allocation = value / total_value net_allocation = target_allocation - current_allocation units_to_trade = ( (net_allocation) * total_value / d.close[0] ) track_trades[d]["units"] = units_to_trade # Can check to make sure there is enough distance away from ideal to trade. track_trades[d]["threshold"] = abs(net_allocation) > self.p.threshold rebalance = False for values in track_trades.values(): if values["threshold"]: rebalance = True if not rebalance: return # Sell shares first for d, value in track_trades.items(): if value["units"] < 0: self.sell(d, size=value["units"]) # Buy shares second for d, value in track_trades.items(): if value["units"] > 0: self.buy(d, size=value["units"]) if __name__ == "__main__": cerebro = bt.Cerebro() allocations = [ ("AAPL", "4-Jan-21", 0.300), ("TSM", "4-Jan-21", 0.200), ("IBM", "4-Jan-21", 0.300), ("KO", "4-Jan-21", 0.2000), ("AMD", "4-Jan-21", 0.1000), ("DELL", "5-Jan-21", 0.200), ("TSM", "5-Jan-21", 0.20), ("IBM", "5-Jan-21", 0.1), ("KO", "5-Jan-21", 0.1), ("NKE", "5-Jan-21", 0.15), ("TSLA", "5-Jan-21", 0.10), ("CSCO", "5-Jan-21", 0.050), ("JPM", "5-Jan-21", 0.1), ("AMD", "6-Jan-21", 0.25), ("BA", "6-Jan-21", 0.25), ("ORCL", "6-Jan-21", 0.50), ("AAPL", "7-Jan-21", 0.5000), ("KO", "7-Jan-21", 0.5000), ] ticker_names = list(set([alls[0] for alls in allocations])) targets = {ticker: {} for ticker in ticker_names} for all in allocations: targets[all[0]].update({all[1]: all[2]}) for ticker, target in targets.items(): data = bt.feeds.YahooFinanceData( dataname=ticker, timeframe=bt.TimeFrame.Days, fromdate=datetime.datetime(2020, 12, 21), todate=datetime.datetime(2021, 1, 8), reverse=False, ) data.target = target cerebro.adddata(data, name=ticker) cerebro.addstrategy(Strategy) cerebro.broker.setcash(1000000) # Execute cerebro.run()
dict object returned inf values, but it should have been numeric
Tried appling the following code: df = pd.read_csv('data_sample_ltv.csv') # Convert date to year date_mapper = {date: pd.to_datetime(date).year for date in df['transaction_date'].unique()} year = df['transaction_date'].map(date_mapper) df['year'] = year # Convert tier to categorical tiers = pd.Categorical(df['customer_tier'], categories=['Gold', 'Silver', 'Bronze', 'Free-Trial'], ordered=False) df['customer_tier'] = tiers # Create highest tier mapper def highest_tier(c_id, df=df): tier = df.loc[df.customer_id == c_id]['customer_tier'].sort_values().iloc[0] return tier tier_mapper = { cust_id: highest_tier(cust_id) for cust_id in df['customer_id'].unique() } # Aggregate the data customer_df = df.groupby(['customer_id']).agg({ 'transaction_amount': ['sum', 'count'], 'year': [pd.Series.nunique] }) customer_df['highest_tier'] = customer_df.index.map(tier_mapper) customer_df['lifespan'] = customer_df[('year', 'nunique')] customer_df['avg_trn_amt'] = customer_df[('transaction_amount', 'sum')] / customer_df[('transaction_amount', 'count')] customer_df['avg_trn_per_yr'] = customer_df[('transaction_amount', 'count')] / customer_df['lifespan'] # Create the LTV function def ltv(df, tier=None): if tier: df = df.loc[df['highest_tier'] == tier] ltv_dict = { 'avg_lifespan': round(df['lifespan'].mean(), 1), 'avg_trn_per_yr': round(df['avg_trn_per_yr'].mean(), 1), 'avg_trn_amt': round(df['avg_trn_amt'].mean(), 2), 'ltv': None } ltv_dict['ltv'] = round( ltv_dict['avg_lifespan'] * ltv_dict['avg_trn_per_yr'] * ltv_dict['avg_trn_amt'], 2) return ltv_dict # Calculate the LTVs for each of our customer segments ltv_all = ltv(customer_df) ltv_gold = ltv(customer_df, 'Gold') ltv_silver = ltv(customer_df, 'Silver') ltv_bronze = ltv(customer_df, 'Bronze') print(f"The lifetime value of our Gold tier is: {ltv_gold['ltv']} while the ltv of bronze is {ltv_bronze['ltv']}") But the ltv results were inf, and not a numeric value: The lifetime value of our Gold tier is: inf while the ltv of bronze is inf The results from each ltv were all inf: {'avg_lifespan': 1.2, 'avg_trn_per_yr': inf, 'avg_trn_amt': 82.23, 'ltv': inf} {'avg_lifespan': 1.1, 'avg_trn_per_yr': inf, 'avg_trn_amt': 39.9, 'ltv': inf} {'avg_lifespan': 1.3, 'avg_trn_per_yr': inf, 'avg_trn_amt': 128.13, 'ltv': inf} Can someone help me understand what went wrong and what should I do to transform the inf into a numeric value? Thanks.
How do I use timesince
I found this snippet: def timesince(dt, default="just now"): now = datetime.utcnow() diff = now - dt periods = ( (diff.days / 365, "year", "years"), (diff.days / 30, "month", "months"), (diff.days / 7, "week", "weeks"), (diff.days, "day", "days"), (diff.seconds / 3600, "hour", "hours"), (diff.seconds / 60, "minute", "minutes"), (diff.seconds, "second", "seconds"), ) for period, singular, plural in periods: if period: return "%d %s ago" % (period, singular if period == 1 else plural) return default and want to use it in an output when doing a query to my database in Google Appegine. My database looks like so: class Service(db.Model): name = db.StringProperty(multiline=False) urla = db.StringProperty(multiline=False) urlb = db.StringProperty(multiline=False) urlc = db.StringProperty(multiline=False) timestampcreated = db.DateTimeProperty(auto_now_add=True) timestamplastupdate = db.DateTimeProperty(auto_now=True) In the mainpage of the webapp requesthandler I want to do: elif self.request.get('type') == 'list': q = db.GqlQuery('SELECT * FROM Service') count = q.count() if count == 0: self.response.out.write('Success: No services registered, your database is empty.') else: results = q.fetch(1000) for result in results: resultcreated = timesince(result.timestampcreated) resultupdated = timesince(result.timestamplastupdate) self.response.out.write(result.name + '\nCreated:' + resultcreated + '\nLast Updated:' + resultupdated + '\n\n') What am I doing wrong? I'm having troubles with formatting my code using the snippet. Which one of these should I do? this? def timesince: class Service class Mainpage def get(self): or this? class Service class Mainpage def timesince: def get(self): I'm not too familiar with Python and would appreciate any input on how to fix this. Thanks!
I'm not totally clear what the problem you're having is, so bear with me. A Traceback would be helpful. :) timesince() doesn't require any member variables, so I don't think it should be inside one of the classes. If I were in your situation, I would probably put timesince in its own file and then import that module in the file where Mainpage is defined. If you're putting them all in the same file, make sure that your spacing is consistent and you don't have any tabs.
This works fine for me: from datetime import datetime def timesince(dt, default="now"): now = datetime.now() diff = now - dt periods = ( (diff.days / 365, "year", "years"), (diff.days / 30, "month", "months"), (diff.days / 7, "week", "weeks"), (diff.days, "day", "days"), (diff.seconds / 3600, "hour", "hours"), (diff.seconds / 60, "minute", "minutes"), (diff.seconds, "second", "seconds"), ) for period, singular, plural in periods: if period >= 1: return "%d %s ago" % (period, singular if period == 1 else plural) return default timesince(datetime(2016,6,7,12,0,0)) timesince(datetime(2016,6,7,13,0,0)) timesince(datetime(2016,6,7,13,30,0)) timesince(datetime(2016,6,7,13,50,0)) timesince(datetime(2016,6,7,13,52,0))