From 9a63cd32ed9d2455e508b8b021ecc8af05d5d49b Mon Sep 17 00:00:00 2001 From: Ben Liu Date: Mon, 15 Jan 2024 13:10:33 +0800 Subject: [PATCH] rebalance comparison --- .DS_Store | Bin 14340 -> 14340 bytes lib_rebalance.py | 278 ++++++++++++++++++++++ main_avg_yld.py | 2 +- main_rebal_rebal.py | 555 ++++++++++++++++++++++++++++++++++++++++++++ poetry.lock | 115 ++++++++- pyproject.toml | 1 + 6 files changed, 949 insertions(+), 2 deletions(-) create mode 100644 lib_rebalance.py create mode 100644 main_rebal_rebal.py diff --git a/.DS_Store b/.DS_Store index a0f52d493987c73c5aa585c0d07e818655085805..cafd6176380bc91f48bf9ed6d106cd02090c2ed7 100644 GIT binary patch delta 117 zcmZoEXepTBFDlN!z`)GFAi%&-!cf3a%1}~}VHiB|qVi+|6PAhHqLbM)1UPeY(hY-? z^K%O}8%p@GZ)WFU;b7#NI6-zYpP=MsHGz}-lerc6Ia7+0bCUA&a~L-BDC96QGH$L^ VtYzE$QT!KBhru_N&CgWWnE^cuAq`6PAhHqLXC=c!gORav1U% zk{J>ia&q%sT#|C~lP2m*Y`mb)zL}kag@ciE@&R$F%}xR*`6phGnar)gzu8bBj**dJ z^903Ow#hHm1tu;K-~pMNlb@Fkw2yJKqr@rpi5H|dvm1P4VarcqU|?E3`HqO>W@FV` IOh65M0B<2Ky8r+H diff --git a/lib_rebalance.py b/lib_rebalance.py new file mode 100644 index 0000000..b0b1d19 --- /dev/null +++ b/lib_rebalance.py @@ -0,0 +1,278 @@ +import numpy as np +import main as m +from math import sqrt +import pandas as pd + +def get_lp_evaluation_scenarios(): + + + date_begin = '2021-05-05' # price: 0.060538 + date_end = '2023-12-20' # price: 0.060388 + + list_scenarios = [ + ['recent 1 year backtest', '2022-12-15', '2023-12-15'], + ['1 year price revert-back', '2022-05-13', '2023-05-13'], + ['1 year maximum price-down','2022-10-30', '2023-10-30' ], + ['1 year maximum price-up', '2022-06-12', '2023-06-12' ], + ['history case max price-down','2021-12-09', '2022-06-19' ], + ['history case max price-up','2022-06-19', '2022-09-09' ], + ['history case max first-down-then-up','2021-12-09', '2022-09-09' ] + + ] + + list_scenarios_name = ['scenario_name', 'date_begin', 'date_end'] + + df_price = m.get_df_daily_price(date_begin,date_end) + + df_scenarios = pd.DataFrame(list_scenarios, columns=list_scenarios_name) + df_scenarios['index'] = df_scenarios.index + + # first get the beign_date price + df_scenarios['date'] = pd.to_datetime(df_scenarios['date_begin']) + df_scenarios.set_index('date', inplace=True) + df_p_tmp = df_price[['price']].rename(columns={'price': 'begin_price'}) + df_scenarios = pd.merge(df_scenarios, df_p_tmp, left_index=True, right_index=True, how='left' ) + + + # then get end_date price + df_scenarios['date'] = pd.to_datetime(df_scenarios['date_end']) + df_scenarios.set_index('date', inplace=True) + df_p_tmp = df_price[['price']].rename(columns={'price': 'end_price'}) + df_scenarios = pd.merge(df_scenarios, df_p_tmp, left_index=True, right_index=True, how='left' ) + + # reset index as 0,1,... to maintan the sequence + df_scenarios.set_index("index", inplace=True) + df_scenarios.sort_index(ascending=True, inplace=True) + + return df_scenarios + + + + +def get_lp_asset_qty_after_price_chg(p0, pn, qty0, qty1, range_down, range_up, b_input_pct = True): + + # convert to price + if b_input_pct: + range_down = p0*(1+range_down) + range_up = p0*(1+range_up) + + if p0 < range_down: + p0 = range_down + elif p0>range_up: + p0 = range_up + else: + p0=p0 + + if pn < range_down: + pn = range_down + elif pn>range_up: + pn = range_up + else: + pn=pn + + + p0_sqrt = sqrt(p0) + pn_sqrt = sqrt(pn) + + p_rg_down_sqrt = sqrt(range_down) + p_rg_up_sqrt = sqrt(range_up) + + if p0 == range_down: # only x has + Lx = qty0 / (1/p0_sqrt - 1/p_rg_up_sqrt) + Ly = 0 + elif p0 == range_up: # only y has + Lx =0 + Ly = qty1 / (p0_sqrt - p_rg_down_sqrt) + else: + Lx = qty0 / (1/p0_sqrt - 1/p_rg_up_sqrt) + Ly = qty1 / (p0_sqrt - p_rg_down_sqrt) + + + if pn < p0: # price down, using y as liquidity + L = Ly + pn_sqrt = max(pn_sqrt, p_rg_down_sqrt) + final_token0_qty = L*(1/pn_sqrt - 1/p0_sqrt) + qty0 + final_token1_qty = L*(pn_sqrt - p_rg_down_sqrt) + elif pn > p0: # price up, using x as liquidity + L = Lx + pn_sqrt = min(pn_sqrt, p_rg_up_sqrt) + final_token0_qty = L*(1/pn_sqrt - 1/p_rg_up_sqrt) + final_token1_qty = L*(pn_sqrt -p0_sqrt) + qty1 + else: + final_token0_qty, final_token1_qty = qty0, qty1 + + return final_token0_qty, final_token1_qty + + +import pandas as pd +import lib_logic +# break the data into monthly, start from inital postion, and rebalance monthly. + +range_down = -0.1 +range_up = -1*range_down/(1+range_down) + + + +def portfolio_value_no_rebalance(df, range_down, range_up, initial_qty_0_and_1 = None, benchmark_avg_yld_range = -0.3 ): + + df.sort_index(ascending=True) + + starting_price = df['price'].iat[0] + end_price = df['price'].iat[-1] + + if initial_qty_0_and_1 is None: + initial_qty0 = 1 + initial_qty1 = initial_qty0 * starting_price + else: + if(len(initial_qty_0_and_1)!=2): + raise ValueError("Input error: initial_qty_0_and_1 must have 2 elements!") + initial_qty0 = initial_qty_0_and_1[0] + initial_qty1 = initial_qty_0_and_1[1] + + + boost_factor = lib_logic.get_liquidity_boost_given_range(range_down, benchmark_avg_yld_range ) + + p0 = starting_price + pn = end_price + + qty0 = initial_qty0 + qty1 = initial_qty1 + + end_qty0, end_qty1 = get_lp_asset_qty_after_price_chg(p0, pn, qty0, qty1, range_down, range_up, b_input_pct = True) + + + + range_price_up = p0 * (1+range_up) + range_price_down = p0 * (1+range_down) + ps_b_within_range = df['price'].apply(lambda x: int(x>=range_price_down and x<=range_price_up )) + + df[ 'price_range_up'] = range_price_up + df[ 'price_range_down'] = range_price_down + df[ 'b_within_range'] = ps_b_within_range + + + fee_yield =boost_factor* (df['daily_fee_rate']* ps_b_within_range ).sum() + + value_mon_begin = qty0*p0+qty1 + # note the below formula is simplifation. + value_mon_end = end_qty0*pn+end_qty1 + (qty0*pn+qty1)*fee_yield #is it so? + + array_col_names = ['p0', 'end_p', 'qty0', 'qty1', 'end_qty0', 'end_qty1', 'month_fee_yield', 'value_mon_begin', 'value_mon_end'] + + this_mon_array = np.array([p0, pn, qty0, qty1, end_qty0, end_qty1, fee_yield, value_mon_begin,value_mon_end ]) + rst_df = pd.DataFrame(data=[this_mon_array], columns=array_col_names) + + return rst_df + + + + +def portfolio_monthly_rebalance(df, range_down, range_up, initial_qty_0_and_1 = None ): + + df.sort_index(ascending=True) + + starting_price = df['price'].iat[0] + if initial_qty_0_and_1 is None: + initial_qty0 = 1 + initial_qty1 = initial_qty0 * starting_price + else: + if(len(initial_qty_0_and_1)!=2): + raise ValueError("Input error: initial_qty_0_and_1 must have 2 elements!") + initial_qty0 = initial_qty_0_and_1[0] + initial_qty1 = initial_qty_0_and_1[1] + + boost_factor = lib_logic.get_liquidity_boost_given_range(range_down, -0.3 ) + + + labels_ym = df['YYYYMM'].unique() + array_col_names = ['p0', 'end_p', 'qty0', 'qty1', 'end_qty0', 'end_qty1', 'month_fee_yield', 'value_mon_begin', 'value_mon_end'] + + # Create an empty DataFrame with specified column names + rst_df_ym = pd.DataFrame(index=labels_ym, columns=array_col_names) + + + + mon_groups = df.groupby('YYYYMM') + p0 = starting_price + qty0 = initial_qty0 + qty1 = initial_qty1 + + for ym, df_ym in mon_groups: # index is the date. + pn = df_ym['price'].iat[-1] + end_qty0, end_qty1 = get_lp_asset_qty_after_price_chg(p0, pn, qty0, qty1, range_down, range_up, b_input_pct = True) + + # get 3 value, price_range_up, price_range_down, b_within_range, daily_fee_rate + # note that if we don't do swap, and allocate the leftover capitcal for LP, our deployed liqudity can be very low. + # my final return will be: average_daily_yield * boost * coverage + # daily_yield * Lx/Lx+Ly * 2 * boost * (1, 0) + range_price_up = p0 * (1+range_up) + range_price_down = p0 * (1+range_down) + ps_b_within_range = df_ym['price'].apply(lambda x: int(x>=range_price_down and x<=range_price_up )) + + + df.loc[df_ym.index, 'price_range_up'] = range_price_up + df.loc[df_ym.index, 'price_range_down'] = range_price_down + df.loc[df_ym.index, 'b_within_range'] = ps_b_within_range + + + month_fee_yield =boost_factor* (df_ym['daily_fee_rate']* ps_b_within_range ).sum() + value_mon_begin = qty0*p0+qty1 + # note the below formula is simplifation. + value_mon_end = end_qty0*pn+end_qty1 + (qty0*pn+qty1)*month_fee_yield #is it so? + + + # no need to calculate imp loss for now. we + this_mon_array = np.array([p0, pn, qty0, qty1, end_qty0, end_qty1, month_fee_yield, value_mon_begin,value_mon_end ]) + rst_df_ym.loc[ym] = this_mon_array + + # update for next round calc + # this part calc assumes re-balance (since price range has changed, but deposit exactly same amount) + # it is like i use qty0 for right liquidity, and qty1 for left side liquidity. + p0 = pn + qty0 = end_qty0 + qty0*month_fee_yield + qty1 = end_qty1 + qty1*month_fee_yield + + rst_names = ['p0', 'qty0', 'qty1', 'end_p', 'end_qty0', 'end_qty1'] + rst_values = [starting_price,initial_qty0,initial_qty1, rst_df_ym['end_p'].iloc[-1], rst_df_ym['end_qty0'].iloc[-1],rst_df_ym['end_qty1'].iloc[-1] ] + + # Create a dictionary using zip and dictionary comprehension + rst_dict = {name: value for name, value in zip(rst_names, rst_values)} + + + return rst_dict, rst_df_ym + + +if __name__ == "__main__": + + b_get_eval_scenarios = True; + if b_get_eval_scenarios: + df_scenarios = get_lp_evaluation_scenarios() + print(df_scenarios) + + run_rebalance = True + if run_rebalance: + date_begin = '2022-12-01' + + df_price = m.get_df_daily_price(date_begin) + df_fee = m.get_df_daily_fees(date_begin = date_begin) + df = m.get_df_comb_price_fee(df_price, df_fee) + print("\n check data df first 3 rows:") + print(df.head(3)) + + rst_dict, rst_df_ym = portfolio_monthly_rebalance(df, range_down, range_up ) + + rst_df_ym.to_clipboard() + print("\n results: starting position vs ending position: ") + print(rst_dict) + print(rst_df_ym) + + + + print("\n results: no balance: ") + benchmark_avg_yld_range = -0.3 + range_down = -0.2 # yearly no rebalance + range_up = -1*range_down/(1+range_down) + + rst_df_ym = portfolio_value_no_rebalance(df, range_down, range_up, benchmark_avg_yld_range = benchmark_avg_yld_range ) + + print(rst_df_ym) \ No newline at end of file diff --git a/main_avg_yld.py b/main_avg_yld.py index 2d21d62..1936b12 100644 --- a/main_avg_yld.py +++ b/main_avg_yld.py @@ -72,7 +72,7 @@ def get_all_range_coverage_rate_monthly(range_down, df): plt.title('Range (bin) Asset amount vs range_down value') plt.xlabel('range_down') plt.ylabel('Range Asset amount') -plt.ylim(0, 3) +plt.ylim(0, 7) plt.grid(True) plt.show() diff --git a/main_rebal_rebal.py b/main_rebal_rebal.py new file mode 100644 index 0000000..5d1e76a --- /dev/null +++ b/main_rebal_rebal.py @@ -0,0 +1,555 @@ +import pandas as pd +import numpy as np +import main as m +from math import sqrt + +import lib_logic +from lib_rebalance import get_lp_asset_qty_after_price_chg +import lib_rebalance as lib_reb + + +def get_portfolio_initial_value(starting_price, initial_qty_0_and_1 = None): + if initial_qty_0_and_1 is None: + initial_qty1 = 1 /2 + initial_qty0 = initial_qty1 / starting_price + + else: + if(len(initial_qty_0_and_1)!=2): + raise ValueError("Input error: initial_qty_0_and_1 must have 2 elements!") + initial_qty0 = initial_qty_0_and_1[0] + initial_qty1 = initial_qty_0_and_1[1] + + return initial_qty0, initial_qty1 + +def portfolio_noLP_justhold(df, initial_qty_0_and_1 = None ): + + df.sort_index(ascending=True) + p0 = df['price'].iat[0] + pn = df['price'].iat[-1] + + qty0, qty1 = get_portfolio_initial_value(p0, initial_qty_0_and_1) + + array_col_names = [ 'end_p', 'end_qty0', 'end_qty1', 'end_value_base', 'end_value_quote', 'total_fee_yield', + 'begin_p', 'begin_qty0', 'begin_qty1', 'begin_value_base', 'begin_value_quote' + ] + + begin_value_base, begin_value_quote = value_base_and_quote(qty0, qty1,p0) + end_value_base, end_value_quote = value_base_and_quote(qty0, qty1,pn) + + rst = np.array([ pn, qty0, qty1, end_value_base, end_value_quote, 0, + p0, qty0, qty1, begin_value_base, begin_value_quote ]) + + + df_rst = pd.DataFrame(data=[rst], columns=array_col_names) + + return df_rst + + +def portfolio_norebalance_fixed_range(df, range_down, range_up, initial_qty_0_and_1 = None, benchmark_avg_yld_range = -0.1 ): + + df.sort_index(ascending=True) + starting_price = df['price'].iat[0] + end_price = df['price'].iat[-1] + + initial_qty0, initial_qty1 = get_portfolio_initial_value(starting_price, initial_qty_0_and_1) + + boost_factor = lib_logic.get_liquidity_boost_given_range(range_down, benchmark_avg_yld_range ) + + p0 = starting_price + pn = end_price + + qty0 = initial_qty0 + qty1 = initial_qty1 + + end_qty0, end_qty1 = lib_reb.get_lp_asset_qty_after_price_chg(p0, pn, qty0, qty1, range_down, range_up, b_input_pct = True) + + + + range_price_up = p0 * (1+range_up) + range_price_down = p0 * (1+range_down) + ps_b_within_range = df['price'].apply(lambda x: int(x>=range_price_down and x<=range_price_up )) + + df[ 'price_range_up'] = range_price_up + df[ 'price_range_down'] = range_price_down + df[ 'b_within_range'] = ps_b_within_range + + + fee_yield =boost_factor* (df['daily_fee_rate']* ps_b_within_range ).sum() + end_qty0, end_qty1 = end_qty0 * (1+fee_yield), end_qty1*(1+fee_yield) + + array_col_names = [ 'end_p', 'end_qty0', 'end_qty1', 'end_value_base', 'end_value_quote', 'total_fee_yield', + 'begin_p', 'begin_qty0', 'begin_qty1', 'begin_value_base', 'begin_value_quote' + ] + + begin_value_base, begin_value_quote = value_base_and_quote(qty0, qty1,p0) + end_value_base, end_value_quote = value_base_and_quote(end_qty0, end_qty1,pn) + + rst = np.array([ pn, end_qty0, end_qty1, end_value_base, end_value_quote, fee_yield, + p0, qty0, qty1, begin_value_base, begin_value_quote ]) + + + df_rst = pd.DataFrame(data=[rst], columns=array_col_names) + + return df_rst + + +def rebal_get_price_predict(df): + df_p = df[['price']].copy() + df_p.sort_index(ascending=True, inplace=True) + + df_p['1d_chg'] = np.log(df_p['price'] / df_p['price'].shift(1)) + df_p['1dchg_MA7'] = df_p['1d_chg'].rolling(window=7).mean() + df_p['price_MA7'] = df_p['price'].rolling(window=14).mean() + df_p['p_predict'] = df_p['price_MA7'].shift(1) * np.exp( df_p['1dchg_MA7'].shift(1) ) + return df_p['p_predict'] + +def rebal_between(p, up, down): + if up < down: + up, down = down, up + return int(p<=up and p>=down) + +def value_base_and_quote(qty0, qty1, p_token0): + value_quote = qty0*p_token0 + qty1 + value_base = qty0 + qty1/p_token0 + return value_base, value_quote + + +def mav_pool_get_bin_pos(p_cur, price_bin_left_0, price_bin_right_0 ): + mode = "middle" + if p_cur < price_bin_left_0 : # describe where the bin sit vs price_current + mode = "right" + elif p_cur > price_bin_right_0 : + mode = "left" + return mode + +def get_new_range_given_range_pos(p0, gap = None, width = None, range_pos = "middle"): + + if(gap is None): + gap = p0*0.01 + if(width is None): + width = p0*0.05 + + if(range_pos == "both" ): + range_left = p0 + width/2 + elif(range_pos == "left"): + range_left = p0 - gap - width + elif(range_pos == "right"): + range_left = p0 + gap + + return range_left, range_left + width + + +def get_bin_price_limit_given_cur_p_and_mode(p_new, price_bin_left_0, price_bin_right_0, gap, width, rebalance_mode = "both"): + cur_pos = mav_pool_get_bin_pos(p_new, price_bin_left_0, price_bin_right_0 ) + + if cur_pos == "middle": # if cur price pos is middle, then no need change at all + return price_bin_left_0, price_bin_right_0 + + if rebalance_mode == "both": + return get_new_range_given_range_pos(p_new, gap, width, range_pos = cur_pos) + elif rebalance_mode == "left": + if cur_pos == "right": + return get_new_range_given_range_pos(p_new, range_pos =cur_pos) # this will move bin left to closet bin near cur price + else: + return price_bin_left_0, price_bin_right_0 + else: # rebalance_mode == "right" + if cur_pos == "left": + return get_new_range_given_range_pos(p_new, range_pos =cur_pos) # this will move bin left to closet bin near cur price + else: + return price_bin_left_0, price_bin_right_0 + + + +def mav_pool_get_earn_pct(string_bin_pos_0, string_bin_pos_new, p0, p_new, price_bin_left_0, price_bin_right_0): + pct = 0 + case = string_bin_pos_0 + "-" +string_bin_pos_new + bin_width = price_bin_right_0 - price_bin_left_0 + + if case == 'middle-middle': + pct = 1 + elif case == 'middle-left': + pct = (price_bin_right_0 - p0) / (p_new - p0) + elif case == 'middle-right': + pct = (p0 - price_bin_left_0) / (p0-p_new) + elif case == 'left-middle': + pct = (price_bin_right_0 - p_new) / (p0-p_new) + elif case == 'left-left': + pct = 0 + elif case == 'left-right': + pct = bin_width / (p0-p_new) + elif case == 'right-middle': + pct = (p_new - price_bin_left_0) / (p_new - p0) + elif case == 'right-left': + pct = bin_width / (p_new - p0) + elif case == 'right-right': + pct = 0 + else: + print ("error in mav pool get_earn_pct") + + return pct + + +def portfolio_rebal_follow_p(df, range_down, range_up, initial_qty_0_and_1 = None, benchmark_avg_yld_range = -0.1 , follow_mode = 'both' ): + + df.sort_index(ascending=True) + + starting_price = df['price'].iat[0] + initial_qty0, initial_qty1 = get_portfolio_initial_value(starting_price, initial_qty_0_and_1) + + boost_factor = lib_logic.get_liquidity_boost_given_range(range_down, benchmark_avg_yld_range ) + + array_col_names = ['date', 'qty0', 'qty1', 'price', 'value_base', 'value_quote', 'period_fee_yield', + 'old_range_down', 'old_range_up', 'new_range_down', 'new_range_up'] + + # Create an empty DataFrame with specified column names + df_rst = pd.DataFrame(columns=array_col_names) + + + p0 = starting_price + qty0 = initial_qty0 + qty1 = initial_qty1 + + p0_index = df.index[0] + range_price_up = p0 * (1+range_up) + range_price_down = p0 * (1+range_down) + gap = 0.01*p0 # + width = range_price_up - range_price_down + last_index = df.index[-1] + + + value_base, value_quote = value_base_and_quote(qty0, qty1, p0) + + df_rst.loc[len(df_rst)] = np.array([p0_index, qty0, qty1, p0, value_base, value_quote, 0, + range_price_down, range_price_up, range_price_down, range_price_up ]) + + + range_pos_p0 = mav_pool_get_bin_pos(p0, range_price_down, range_price_up ) # start from middle + + for index, row in df.iloc[1:].iterrows(): + pn = row['price'] + range_pos_pn = mav_pool_get_bin_pos(pn, range_price_down, range_price_up ) + + + if(index != last_index and range_pos_p0 == range_pos_pn and range_pos_pn != "middle") : + # price stays outside and didn't move in. we just need to update range limit + range_price_down, range_price_up = get_new_range_given_range_pos(pn, gap = gap, width = width, range_pos = range_pos_p0) + p0, p0_index =pn, index + continue + + + fee_rate_pct = mav_pool_get_earn_pct(range_pos_p0, range_pos_pn, p0, pn, range_price_down, range_price_up) + period_fee_yield =boost_factor* df['daily_fee_rate'].at[index] * fee_rate_pct + + end_qty0, end_qty1 = get_lp_asset_qty_after_price_chg(p0, pn, qty0, qty1, range_price_down, range_price_up, b_input_pct = False) + end_qty0, end_qty1 = end_qty0*(1+period_fee_yield), end_qty1*(1+period_fee_yield) + + + range_price_down_n, range_price_up_n = get_bin_price_limit_given_cur_p_and_mode(pn, range_price_down, range_price_up, gap, width, rebalance_mode = follow_mode) + p0_new = pn + + value_base, value_quote = value_base_and_quote(end_qty0, end_qty1, pn) + + df_rst.loc[len(df_rst)] = np.array([index, end_qty0, end_qty1, pn, value_base, value_quote, period_fee_yield, + range_price_down, range_price_up, range_price_down_n, range_price_up_n ]) + + + p0, p0_index =p0_new, index + qty0, qty1 = end_qty0, end_qty1 + range_price_up, range_price_down = range_price_up_n, range_price_down_n + range_pos_p0 = range_pos_pn + + + rst_names = [ 'end_p', 'end_qty0', 'end_qty1', 'end_value_base', 'end_value_quote', 'total_fee_yield', + 'begin_p', 'begin_qty0', 'begin_qty1', 'begin_value_base', 'begin_value_quote' + ] + begin_value_base, begin_value_quote =value_base_and_quote(initial_qty0, initial_qty1, starting_price) + total_fee_yield = df_rst['period_fee_yield'].sum() + last_row = df_rst.iloc[-1] + end_p, end_qty0, end_qty1, value_base, value_quote = last_row['price'], last_row['qty0'], last_row['qty1'], last_row['value_base'], last_row['value_quote'] + rst_values = [end_p, end_qty0, end_qty1, value_base, value_quote, total_fee_yield, + starting_price,initial_qty0,initial_qty1, begin_value_base, begin_value_quote + ] + + + rst_summary = pd.DataFrame(data=[rst_values], columns=rst_names) + return rst_summary, df_rst + + + +def portfolio_rebal_buylowsellhigh_predict(df, range_down, range_up, initial_qty_0_and_1 = None, benchmark_avg_yld_range = -0.1 ): + + df.sort_index(ascending=True) + df['p_predict'] = rebal_get_price_predict(df) + + + starting_price = df['price'].iat[0] + initial_qty0, initial_qty1 = get_portfolio_initial_value(starting_price, initial_qty_0_and_1) + + + boost_factor = lib_logic.get_liquidity_boost_given_range(range_down, benchmark_avg_yld_range ) + + array_col_names = ['date', 'qty0', 'qty1', 'price', 'value_base', 'value_quote', 'period_fee_yield', + 'old_range_down', 'old_range_up', 'new_range_down', 'new_range_up'] + + # Create an empty DataFrame with specified column names + df_rst = pd.DataFrame(columns=array_col_names) + + + p0 = starting_price + qty0 = initial_qty0 + qty1 = initial_qty1 + + p0_index = df.index[0] + range_price_up = p0 * (1+range_up) + range_price_down = p0 * (1+range_down) + shift_step = p0*0.02 # + period_days = 0 + last_index = df.index[-1] + + + value_base, value_quote = value_base_and_quote(qty0, qty1, p0) + + df_rst.loc[len(df_rst)] = np.array([p0_index, qty0, qty1, p0, value_base, value_quote, 0, + range_price_down, range_price_up, range_price_down, range_price_up ]) + + for index, row in df.iloc[1:].iterrows(): + pn = row['price'] + p_pred = row['p_predict'] + + + if(rebal_between(pn, range_price_up, range_price_down)): + period_days=period_days+1 + continue + if(period_days == 0 and index != last_index) : + continue + + + end_qty0, end_qty1 = get_lp_asset_qty_after_price_chg(p0, pn, qty0, qty1, range_price_down, range_price_up, b_input_pct = False) + + + # daily_yield * Lx/Lx+Ly * 2 * boost * (1, 0) + prev_period_index = (df.index > p0_index) & (df.index <= index) + ps_b_within_range = df.loc[prev_period_index, 'price'].apply(lambda x: rebal_between(x,range_price_up, range_price_down) ) + + df.loc[prev_period_index, 'price_range_up'] = range_price_up + df.loc[prev_period_index, 'price_range_down'] = range_price_down + df.loc[prev_period_index, 'b_within_range'] = ps_b_within_range + + period_fee_yield =boost_factor* ( df.loc[prev_period_index, 'daily_fee_rate'] * ps_b_within_range ).sum() + + end_qty0, end_qty1 = end_qty0*(1+period_fee_yield), end_qty1*(1+period_fee_yield) + + # the best case, range (down, up) is between current price and prediction price. + range_down_predict, range_up_predict = p_pred * (1+range_down), p_pred * (1+range_up) + + + if (pn < range_price_down) : + range_price_down_n = max(range_price_down + shift_step, range_down_predict) + range_price_up_n = max(range_price_up + shift_step, range_up_predict) + p0_new = range_price_down_n + + elif pn > range_price_up: # # pos: range_down, range up, pn + range_price_down_n = min(range_price_down - shift_step, range_down_predict) + range_price_up_n = min(range_price_up - shift_step, range_up_predict) + p0_new = range_price_up_n + + else: + # ??? will this happen? or no? when last row, where price not moving outside + range_price_up_n, range_price_down_n = range_price_up, range_price_down + p0_new = pn # it is due to the qty is reproduced by pn + + value_base, value_quote = value_base_and_quote(end_qty0, end_qty1, pn) + + df_rst.loc[len(df_rst)] = np.array([index, end_qty0, end_qty1, pn, value_base, value_quote, period_fee_yield, + range_price_down, range_price_up, range_price_down_n, range_price_up_n ]) + + + p0, p0_index, period_days =p0_new, index, 0 + qty0, qty1 = end_qty0, end_qty1 + range_price_up, range_price_down = range_price_up_n, range_price_down_n + + + rst_names = [ 'end_p', 'end_qty0', 'end_qty1', 'end_value_base', 'end_value_quote', 'total_fee_yield', + 'begin_p', 'begin_qty0', 'begin_qty1', 'begin_value_base', 'begin_value_quote' + ] + begin_value_base, begin_value_quote =value_base_and_quote(initial_qty0, initial_qty1, starting_price) + total_fee_yield = df_rst['period_fee_yield'].sum() + last_row = df_rst.iloc[-1] + end_p, end_qty0, end_qty1, value_base, value_quote = last_row['price'], last_row['qty0'], last_row['qty1'], last_row['value_base'], last_row['value_quote'] + rst_values = [end_p, end_qty0, end_qty1, value_base, value_quote, total_fee_yield, + starting_price,initial_qty0,initial_qty1, begin_value_base, begin_value_quote + ] + + + rst_summary = pd.DataFrame(data=[rst_values], columns=rst_names) + return rst_summary, df_rst + + # Create a dictionary using zip and dictionary comprehension + # rst_dict = {name: value for name, value in zip(rst_names, rst_values)} + +def portfolio_rebal_recentre(df, range_down, range_up, initial_qty_0_and_1 = None, benchmark_avg_yld_range = -0.1 ): + + df.sort_index(ascending=True) + df['p_predict'] = rebal_get_price_predict(df) + + + starting_price = df['price'].iat[0] + initial_qty0, initial_qty1 = get_portfolio_initial_value(starting_price, initial_qty_0_and_1) + + + boost_factor = lib_logic.get_liquidity_boost_given_range(range_down, benchmark_avg_yld_range ) + + array_col_names = ['date', 'qty0', 'qty1', 'price', 'value_base', 'value_quote', 'period_fee_yield', + 'old_range_down', 'old_range_up', 'new_range_down', 'new_range_up'] + + # Create an empty DataFrame with specified column names + df_rst = pd.DataFrame(columns=array_col_names) + + + p0 = starting_price + qty0 = initial_qty0 + qty1 = initial_qty1 + + p0_index = df.index[0] + range_price_up = p0 * (1+range_up) + range_price_down = p0 * (1+range_down) + shift_step = 0.0005 # + period_days = 0 + last_index = df.index[-1] + + + value_base, value_quote = value_base_and_quote(qty0, qty1, p0) + + df_rst.loc[len(df_rst)] = np.array([p0_index, qty0, qty1, p0, value_base, value_quote, 0, + range_price_down, range_price_up, range_price_down, range_price_up ]) + + for index, row in df.iloc[1:].iterrows(): + pn = row['price'] + + + + if(rebal_between(pn, range_price_up, range_price_down) and index != last_index): + period_days=period_days+1 + continue + + + end_qty0, end_qty1 = get_lp_asset_qty_after_price_chg(p0, pn, qty0, qty1, range_price_down, range_price_up, b_input_pct = False) + + + # daily_yield * Lx/Lx+Ly * 2 * boost * (1, 0) + prev_period_index = (df.index > p0_index) & (df.index <= index) + ps_b_within_range = df.loc[prev_period_index, 'price'].apply(lambda x: rebal_between(x,range_price_up, range_price_down) ) + + df.loc[prev_period_index, 'price_range_up'] = range_price_up + df.loc[prev_period_index, 'price_range_down'] = range_price_down + df.loc[prev_period_index, 'b_within_range'] = ps_b_within_range + + period_fee_yield =boost_factor* ( df.loc[prev_period_index, 'daily_fee_rate'] * ps_b_within_range ).sum() + + end_qty0, end_qty1 = end_qty0*(1+period_fee_yield), end_qty1*(1+period_fee_yield) + + + # swap and reblance to centre. + if( rebal_between(pn, range_price_up, range_price_down) ): + range_price_up_n = range_price_up + range_price_down_n = range_price_down + else: + range_price_up_n = pn * (1+range_up) + range_price_down_n = pn * (1+range_down) + + total_value_quote = end_qty0*pn + end_qty1 + end_qty0 = total_value_quote/2 / pn + end_qty1 = total_value_quote/2 + + + # note: place this function after rebalance + value_base, value_quote = value_base_and_quote(end_qty0, end_qty1, pn) + + df_rst.loc[len(df_rst)] = np.array([index, end_qty0, end_qty1, pn, value_base, value_quote, period_fee_yield, + range_price_down, range_price_up, range_price_down_n, range_price_up_n ]) + + + p0, p0_index, period_days =pn, index, 0 + qty0, qty1 = end_qty0, end_qty1 + range_price_up, range_price_down = range_price_up_n, range_price_down_n + + + rst_names = [ 'end_p', 'end_qty0', 'end_qty1', 'end_value_base', 'end_value_quote', 'total_fee_yield', + 'begin_p', 'begin_qty0', 'begin_qty1', 'begin_value_base', 'begin_value_quote' + ] + begin_value_base, begin_value_quote =value_base_and_quote(initial_qty0, initial_qty1, starting_price) + total_fee_yield = df_rst['period_fee_yield'].sum() + last_row = df_rst.iloc[-1] + end_p, end_qty0, end_qty1, value_base, value_quote = last_row['price'], last_row['qty0'], last_row['qty1'], last_row['value_base'], last_row['value_quote'] + rst_values = [end_p, end_qty0, end_qty1, value_base, value_quote, total_fee_yield, + starting_price,initial_qty0,initial_qty1, begin_value_base, begin_value_quote + ] + + + rst_summary = pd.DataFrame(data=[rst_values], columns=rst_names) + return rst_summary, df_rst + + # Create a dictionary using zip and dictionary comprehension + # rst_dict = {name: value for name, value in zip(rst_names, rst_values)} + + + +def get_performance_given_scenario(date_begin= '2021-12-09', date_end= '2022-09-09'): + + df_price = m.get_df_daily_price(date_begin,date_end) + df_fee = m.get_df_daily_fees(date_begin, date_end) + df = m.get_df_comb_price_fee(df_price, df_fee) + print("\ndf date", max(df['date']), min(df['date'])) + + df_noLP = portfolio_noLP_justhold(df) + + range_down = -0.15 # yearly no rebalance + range_up = -1*range_down/(1+range_down) + df_noreb_fixed_range = portfolio_norebalance_fixed_range(df.copy(), range_down, range_up ) + + range_down = -0.1 # recenter-methodology + range_up = -1*range_down/(1+range_down) + df_rebal_recentre, df_rst = portfolio_rebal_recentre(df.copy(), range_down, range_up ) + + range_down = -0.05 + range_up = -1*range_down/(1+range_down) + df_rebal_blsh, df_rst = portfolio_rebal_buylowsellhigh_predict(df.copy(), range_down, range_up ) + + range_down = -0.01 + range_up = -1*range_down/(1+range_down) + df_rebal_md_both, df_rst = portfolio_rebal_follow_p (df.copy(), range_down, range_up, follow_mode = 'both') + df_rebal_md_right, df_rst = portfolio_rebal_follow_p (df.copy(), range_down, range_up, follow_mode = 'right') + df_rebal_md_left, df_rst = portfolio_rebal_follow_p (df.copy(), range_down, range_up, follow_mode = 'left') + + + df_1scen = pd.concat((df_noLP, df_noreb_fixed_range, df_rebal_recentre, df_rebal_blsh, df_rebal_md_both, df_rebal_md_right, df_rebal_md_left)) + df_1scen.index = ['noLP', "fixed", "swap_recntr", "rg_blsh", 'md-both', 'md-right', 'md-left'] + + return df_1scen[[ 'begin_value_quote','end_value_quote', 'total_fee_yield' ]] + + + +df_scenarios = lib_reb.get_lp_evaluation_scenarios() +print(df_scenarios) + +method_names = ['noLP', "fixed", "swap_recntr", "rg_blsh", 'md-both', 'md-right', 'md-left'] +df_all_acen = pd.DataFrame(columns=method_names) +df_all_start = df_all_acen.copy() + + +for index, row in df_scenarios.iterrows(): + date_begin = row["date_begin"] + date_end = row["date_end"] + df_scen = get_performance_given_scenario(date_begin, date_end) + df_all_acen.loc[len(df_all_acen)] = np.array(df_scen["end_value_quote"]) + df_all_start.loc[len(df_all_acen)] = np.array(df_scen["begin_value_quote"]) + +df_all_acen.index = df_scenarios['scenario_name'] + +df_divided = df_all_acen.apply(lambda row: row / row.iloc[0], axis=1) + +print(df_all_start) +print(df_all_acen) +print(df_divided) + +#df_all_acen.to_clipboard() \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index a97803e..f560c37 100644 --- a/poetry.lock +++ b/poetry.lock @@ -905,6 +905,24 @@ files = [ qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] +[[package]] +name = "patsy" +version = "0.5.6" +description = "A Python package for describing statistical models and for building design matrices." +optional = false +python-versions = "*" +files = [ + {file = "patsy-0.5.6-py2.py3-none-any.whl", hash = "sha256:19056886fd8fa71863fa32f0eb090267f21fb74be00f19f5c70b2e9d76c883c6"}, + {file = "patsy-0.5.6.tar.gz", hash = "sha256:95c6d47a7222535f84bff7f63d7303f2e297747a598db89cf5c67f0c0c7d2cdb"}, +] + +[package.dependencies] +numpy = ">=1.4" +six = "*" + +[package.extras] +test = ["pytest", "pytest-cov", "scipy"] + [[package]] name = "pexpect" version = "4.9.0" @@ -1282,6 +1300,48 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "scipy" +version = "1.11.4" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, + {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, + {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, + {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, + {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, + {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, + {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, + {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, + {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, + {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, + {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, + {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, + {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, + {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, + {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, + {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, + {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, + {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, + {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, + {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, + {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, + {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, + {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, + {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, + {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, +] + +[package.dependencies] +numpy = ">=1.21.6,<1.28.0" + +[package.extras] +dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] +test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "six" version = "1.16.0" @@ -1312,6 +1372,59 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] +[[package]] +name = "statsmodels" +version = "0.14.1" +description = "Statistical computations and models for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "statsmodels-0.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:43af9c0b07c9d72f275cf14ea54a481a3f20911f0b443181be4769def258fdeb"}, + {file = "statsmodels-0.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16975ab6ad505d837ba9aee11f92a8c5b49c4fa1ff45b60fe23780b19e5705e"}, + {file = "statsmodels-0.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e278fe74da5ed5e06c11a30851eda1af08ef5af6be8507c2c45d2e08f7550dde"}, + {file = "statsmodels-0.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0564d92cb05b219b4538ed09e77d96658a924a691255e1f7dd23ee338df441b"}, + {file = "statsmodels-0.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5385e22e72159a09c099c4fb975f350a9f3afeb57c1efce273b89dcf1fe44c0f"}, + {file = "statsmodels-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:0a8aae75a2e08ebd990e5fa394f8e32738b55785cb70798449a3f4207085e667"}, + {file = "statsmodels-0.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b69a63ad6c979a6e4cde11870ffa727c76a318c225a7e509f031fbbdfb4e416a"}, + {file = "statsmodels-0.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7562cb18a90a114f39fab6f1c25b9c7b39d9cd5f433d0044b430ca9d44a8b52c"}, + {file = "statsmodels-0.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3abaca4b963259a2bf349c7609cfbb0ce64ad5fb3d92d6f08e21453e4890248"}, + {file = "statsmodels-0.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f727fe697f6406d5f677b67211abe5a55101896abdfacdb3f38410405f6ad8"}, + {file = "statsmodels-0.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6838ac6bdb286daabb5e91af90fd4258f09d0cec9aace78cc441cb2b17df428"}, + {file = "statsmodels-0.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:709bfcef2dbe66f705b17e56d1021abad02243ee1a5d1efdb90f9bad8b06a329"}, + {file = "statsmodels-0.14.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f32a7cd424cf33304a54daee39d32cccf1d0265e652c920adeaeedff6d576457"}, + {file = "statsmodels-0.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f8c30181c084173d662aaf0531867667be2ff1bee103b84feb64f149f792dbd2"}, + {file = "statsmodels-0.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de2b97413913d52ad6342dece2d653e77f78620013b7705fad291d4e4266ccb"}, + {file = "statsmodels-0.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3420f88289c593ba2bca33619023059c476674c160733bd7d858564787c83d3"}, + {file = "statsmodels-0.14.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c008e16096f24f0514e53907890ccac6589a16ad6c81c218f2ee6752fdada555"}, + {file = "statsmodels-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc0351d279c4e080f0ce638a3d886d312aa29eade96042e3ba0a73771b1abdfb"}, + {file = "statsmodels-0.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf293ada63b2859d95210165ad1dfcd97bd7b994a5266d6fbeb23659d8f0bf68"}, + {file = "statsmodels-0.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44ca8cb88fa3d3a4ffaff1fb8eb0e98bbf83fc936fcd9b9eedee258ecc76696a"}, + {file = "statsmodels-0.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5373d176239993c095b00d06036690a50309a4e00c2da553b65b840f956ae6"}, + {file = "statsmodels-0.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532dfe899f8b6632cd8caa0b089b403415618f51e840d1817a1e4b97e200c73"}, + {file = "statsmodels-0.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:4fe0a60695952b82139ae8750952786a700292f9e0551d572d7685070944487b"}, + {file = "statsmodels-0.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04293890f153ffe577e60a227bd43babd5f6c1fc50ea56a3ab1862ae85247a95"}, + {file = "statsmodels-0.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e70a2e93d54d40b2cb6426072acbc04f35501b1ea2569f6786964adde6ca572"}, + {file = "statsmodels-0.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab3a73d16c0569adbba181ebb967e5baaa74935f6d2efe86ac6fc5857449b07d"}, + {file = "statsmodels-0.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eefa5bcff335440ee93e28745eab63559a20cd34eea0375c66d96b016de909b3"}, + {file = "statsmodels-0.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:bc43765710099ca6a942b5ffa1bac7668965052542ba793dd072d26c83453572"}, + {file = "statsmodels-0.14.1.tar.gz", hash = "sha256:2260efdc1ef89f39c670a0bd8151b1d0843567781bcafec6cda0534eb47a94f6"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.3,<2", markers = "python_version == \"3.10\" and platform_system == \"Windows\" and platform_python_implementation != \"PyPy\""}, + {version = ">=1.18,<2", markers = "python_version != \"3.10\" or platform_system != \"Windows\" or platform_python_implementation == \"PyPy\""}, +] +packaging = ">=21.3" +pandas = ">=1.0,<2.1.0 || >2.1.0" +patsy = ">=0.5.4" +scipy = ">=1.4,<1.9.2 || >1.9.2" + +[package.extras] +build = ["cython (>=0.29.33)"] +develop = ["colorama", "cython (>=0.29.33)", "cython (>=0.29.33,<4.0.0)", "flake8", "isort", "joblib", "matplotlib (>=3)", "oldest-supported-numpy (>=2022.4.18)", "pytest (>=7.3.0)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty", "setuptools-scm[toml] (>=8.0,<9.0)"] +docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] + [[package]] name = "tornado" version = "6.4" @@ -1388,4 +1501,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10, <3.12" -content-hash = "bb6ca260e0d1507d38f9d7118e38fc0e45f89be6a23d82aaf2d9bc5a22e69579" +content-hash = "08f0609018ff4a6112c6dc65fbe4c76b46653202312d5b322a05f30608307413" diff --git a/pyproject.toml b/pyproject.toml index 13e520b..de182ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ numpy = "^1.26.2" pandas = "^2.1.4" requests = "^2.31.0" matplotlib = "^3.8.2" +statsmodels = "^0.14.1" [tool.poetry.group.dev.dependencies]