diff --git a/README.md b/README.md index f1e056b..308717c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Download the following data from binance (USDT-M), bybit (linear) in one go. | | binance | bybit | |--------------|---------|-------| | klines | ✅ | ✅ | +| aggTrades | ✅ | TBD | | fundingRate | ✅ | ✅ | | liquidationSnapshot | ✅ | ❌ | diff --git a/crypto_features/feature/engineering.py b/crypto_features/feature/engineering.py index 4f6a28d..5f086af 100644 --- a/crypto_features/feature/engineering.py +++ b/crypto_features/feature/engineering.py @@ -1,187 +1,261 @@ """ Feature engineering module """ +import datetime + import numpy as np import pandas as pd +from .exceptions import (DataNotFoundError, InsufficientDataError, + InvalidParameterError) + class FeatureEngineering: def __init__(self, **kwargs): """ - :param feature: feature data - :param klines: klines data + :param feature: feature data (pd.Series) + :param klines: klines data (pd.DataFrame) + :param aggtrades: aggTrades data (pd.DataFrame) :param liquidationsnapshot: preprocessed liquidationSnapshot data (pd.DataFrame) + :param start_time: start time of feature data (datetime.datetime) + :param end_time: end time of feature data (datetime.datetime) + """ + self._feature: pd.Series = kwargs.get("feature", None) + self._klines: pd.DataFrame = kwargs.get("klines", None) + self._aggtrades: pd.DataFrame = kwargs.get("aggtrades", None) + self._liquidationSnapshot: pd.DataFrame = kwargs.get( + "liquidationsnapshot", None + ) + self._start_time: datetime.datetime = kwargs.get("start_time", None) + self._end_time: datetime.datetime = kwargs.get("end_time", None) + + def _parse_feature(self) -> pd.Series: + """ + Parse feature data + """ + if self._feature is None: + raise InvalidParameterError("The feature data is not given.") + if self._start_time is None: + raise InvalidParameterError("The start time is not given.") + if self._end_time is None: + raise InvalidParameterError("The end time is not given.") + self._feature = self._feature.loc[self._start_time : self._end_time] + return self._feature + + def _parse_klines(self) -> pd.DataFrame: + """ + Parse klines data + """ + if self._klines is None: + raise InvalidParameterError("The klines data is not given.") + if self._start_time is None: + raise InvalidParameterError("The start time is not given.") + if self._end_time is None: + raise InvalidParameterError("The end time is not given.") + self._klines = self._klines.loc[self._start_time : self._end_time] + return self._klines + + def _parse_aggtrades(self) -> pd.DataFrame: + """ + Parse aggtrades data """ - self._feature = kwargs.get("feature", None) - self._klines = kwargs.get("klines", None) - self._liquidationSnapshot = kwargs.get("liquidationsnapshot", None) + if self._aggtrades is None: + raise InvalidParameterError("The aggtrades data is not given.") + if self._start_time is None: + raise InvalidParameterError("The start time is not given.") + if self._end_time is None: + raise InvalidParameterError("The end time is not given.") + self._aggtrades = self._aggtrades.loc[self._start_time : self._end_time] + return self._aggtrades + + def _parse_liquidationsnapshot(self) -> pd.DataFrame: + """ + Parse liquidationSnapshot data + """ + if self._liquidationSnapshot is None: + raise InvalidParameterError("The liquidationSnapshot data is not given.") + if self._start_time is None: + raise InvalidParameterError("The start time is not given.") + if self._end_time is None: + raise InvalidParameterError("The end time is not given.") + self._liquidationSnapshot = self._liquidationSnapshot.loc[ + self._start_time : self._end_time + ] + return self._liquidationSnapshot + + def _check_start_end_time(self): + """ + Check start and end time + :return: True if start and end time are given, False otherwise + """ + return self._start_time is not None and self._end_time is not None def diff_feature(self) -> pd.Series: """ - Calculate difference of funding rate + Calculate difference of feature """ return self._feature.diff() def square_feature(self) -> pd.Series: """ - Calculate square of funding rate + Calculate square of feature """ return self._feature**2 def cube_feature(self) -> pd.Series: """ - Calculate cube of funding rate + Calculate cube of feature """ return self._feature**3 def exp_feature(self) -> pd.Series: """ - Calculate exp of funding rate + Calculate exp of feature """ return np.exp(self._feature) def sin_feature(self) -> pd.Series: """ - Calculate sin of funding rate + Calculate sin of feature """ return np.sin(self._feature) def cos_feature(self) -> pd.Series: """ - Calculate cos of funding rate + Calculate cos of feature """ return np.cos(self._feature) def tan_feature(self) -> pd.Series: """ - Calculate tan of funding rate + Calculate tan of feature """ return np.tan(self._feature) def tanh_feature(self) -> pd.Series: """ - Calculate tanh of funding rate + Calculate tanh of feature """ return np.tanh(self._feature) def sigmoid_feature(self) -> pd.Series: """ - Calculate sigmoid of funding rate + Calculate sigmoid of feature """ return 1 / (1 + np.exp(-self._feature)) def softmax_feature(self) -> pd.Series: """ - Calculate softmax of funding rate + Calculate softmax of feature """ return np.exp(self._feature) / np.sum(np.exp(self._feature)) def log_feature(self) -> pd.Series: """ - Calculate log of funding rate + Calculate log of feature """ return np.log(self._feature) def log10_feature(self) -> pd.Series: """ - Calculate log10 of funding rate + Calculate log10 of feature """ return np.log10(self._feature) def log2_feature(self) -> pd.Series: """ - Calculate log2 of funding rate + Calculate log2 of feature """ return np.log2(self._feature) def square_root_feature(self) -> pd.Series: """ - Calculate square root of funding rate + Calculate square root of feature """ return np.sqrt(self._feature) def arctan_feature(self) -> pd.Series: """ - Calculate arctan of funding rate + Calculate arctan of feature """ return np.arctan(self._feature) def arcsin_feature(self) -> pd.Series: """ - Calculate arcsin of funding rate + Calculate arcsin of feature """ return np.arcsin(self._feature) def arccos_feature(self) -> pd.Series: """ - Calculate arccos of funding rate + Calculate arccos of feature """ return np.arccos(self._feature) def arctanh_feature(self) -> pd.Series: """ - Calculate arctanh of funding rate + Calculate arctanh of feature """ return np.arctanh(self._feature) def arcsinh_feature(self) -> pd.Series: """ - Calculate arcsinh of funding rate + Calculate arcsinh of feature """ return np.arcsinh(self._feature) def arccosh_feature(self) -> pd.Series: """ - Calculate arccosh of funding rate + Calculate arccosh of feature """ return np.arccosh(self._feature) def absolute_feature(self) -> pd.Series: """ - Calculate absolute of funding rate + Calculate absolute of feature """ return np.absolute(self._feature) def reciprocal_feature(self) -> pd.Series: """ - Calculate reciprocal of funding rate + Calculate reciprocal of feature """ return np.reciprocal(self._feature) def negative_feature(self) -> pd.Series: """ - Calculate negative of funding rate + Calculate negative of feature """ return np.negative(self._feature) def sign_feature(self) -> pd.Series: """ - Calculate sign of funding rate + Calculate sign of feature """ return np.sign(self._feature) def ceil_feature(self) -> pd.Series: """ - Calculate ceil of funding rate + Calculate ceil of feature """ return np.ceil(self._feature) def floor_feature(self) -> pd.Series: """ - Calculate floor of funding rate + Calculate floor of feature """ return np.floor(self._feature) def rint_feature(self) -> pd.Series: """ - Calculate rint of funding rate + Calculate rint of feature """ return np.rint(self._feature) def trunc_feature(self) -> pd.Series: """ - Calculate trunc of funding rate + Calculate trunc of feature """ return np.trunc(self._feature) @@ -191,19 +265,27 @@ def count_liquidation(self, count_minutes: int) -> pd.Series: :param count_minutes: minutes to count liquidation """ + if self._check_start_end_time(): + parsed = self._parse_liquidationsnapshot() + else: + parsed = self._liquidationSnapshot - def count_buy_sell_in_last_1min(current_time, df): - subset = df[ - (df.index < current_time) - & (df.index >= current_time - pd.Timedelta(minutes=count_minutes)) - ] - buy_count = subset[subset["side"] == "BUY"].shape[0] - sell_count = subset[subset["side"] == "SELL"].shape[0] - return buy_count - sell_count - - return self._liquidationSnapshot.index.to_series().apply( - lambda x: count_buy_sell_in_last_1min(x, self._liquidationSnapshot) + parsed["trade_count"] = parsed["side"].map({"BUY": 1, "SELL": -1}) + index_range = pd.date_range( + start=self._start_time, end=self._end_time, freq="1S", tz="UTC" ) + empty_df = pd.DataFrame(index=index_range) + + def sum_count(ts): + start_time = ts - pd.Timedelta(minutes=count_minutes) + subset = parsed[(parsed.index > start_time) & (parsed.index <= ts)] + return subset["trade_count"].sum() + + empty_df["count"] = empty_df.index.to_series().apply(sum_count) + empty_df["count"] = empty_df["count"].fillna(0) + empty_df["count"] = empty_df["count"].astype(int) + + return empty_df["count"] def count_quote_liquidation(self, count_minutes: int) -> pd.Series: """ @@ -211,34 +293,37 @@ def count_quote_liquidation(self, count_minutes: int) -> pd.Series: :param count_minutes: minutes to count liquidation """ - self._liquidationSnapshot["amount"] = ( - self._liquidationSnapshot["price"] - * self._liquidationSnapshot["original_quantity"] + if self._check_start_end_time(): + parsed = self._parse_liquidationsnapshot() + else: + parsed = self._liquidationSnapshot + + parsed["amount"] = ( + parsed["price"] + * parsed["original_quantity"] + * parsed["side"].map({"BUY": 1, "SELL": -1}) + ) + index_range = pd.date_range( + start=self._start_time, end=self._end_time, freq="1S", tz="UTC" + ) + empty_df = pd.DataFrame(index=index_range) + + def sum_amount(ts): + start_time = ts - pd.Timedelta(minutes=count_minutes) + subset = parsed[(parsed.index > start_time) & (parsed.index <= ts)] + return subset["amount"].sum() + + empty_df["taker_buy_quote_volume"] = empty_df.index.to_series().apply( + sum_amount ) - times = self._liquidationSnapshot.index.values - amounts = self._liquidationSnapshot["amount"].values - sides = self._liquidationSnapshot["side"].values - - def np_adjusted_sum_amount_in_last_1min(idx): - current_time = times[idx] - mask = (times < current_time) & ( - times >= current_time - pd.Timedelta(minutes=count_minutes) - ) - adjusted_amounts = np.where( - sides[mask] == "BUY", amounts[mask], -amounts[mask] - ) - return adjusted_amounts.sum() - - se = pd.Series( - np.array( - [ - np_adjusted_sum_amount_in_last_1min(i) - for i in range(len(self._liquidationSnapshot)) - ] - ), - index=times, + empty_df["taker_buy_quote_volume"] = empty_df["taker_buy_quote_volume"].fillna( + 0 ) - return se + empty_df["taker_buy_quote_volume"] = empty_df["taker_buy_quote_volume"].astype( + float + ) + + return empty_df["taker_buy_quote_volume"] def mean_liquidation(self, count_minutes: int) -> pd.Series: """ @@ -246,11 +331,11 @@ def mean_liquidation(self, count_minutes: int) -> pd.Series: :param count_minutes: minutes to calculate mean liquidation """ - df = self.count_quote_liquidation(count_minutes) / self.count_liquidation( + df = self.count_quote_liquidation(count_minutes).abs() / self.count_liquidation( count_minutes ) df = df.fillna(0) - return df + return df.resample("1S").mean().fillna(0) def ratio_liquidation(self, count_minutes: int) -> pd.Series: """ @@ -259,15 +344,27 @@ def ratio_liquidation(self, count_minutes: int) -> pd.Series: :param count_minutes: minutes to calculate ratio liquidation """ - self._klines["count"] = self._klines["count"].astype(int) - return self.count_liquidation( - count_minutes - ) / self._liquidationSnapshot.index.to_series().apply( - lambda x: self._klines[ - (self._klines.index < x) - & (self._klines.index > x - pd.Timedelta(minutes=1)) - ]["count"].sum() + if self._check_start_end_time(): + aggtrades = self._parse_aggtrades() + else: + aggtrades = self._aggtrades + + index_range = pd.date_range( + start=self._start_time, end=self._end_time, freq="1S", tz="UTC" ) + empty_df = pd.DataFrame(index=index_range) + empty_df["aggtrade_count"] = empty_df.index.to_series().apply( + lambda x: aggtrades[ + (aggtrades.index < x) + & (aggtrades.index > x - pd.Timedelta(minutes=count_minutes)) + ].shape[0] + ) + empty_df["aggtrade_count"] = empty_df["aggtrade_count"].fillna(0) + empty_df["aggtrade_count"] = empty_df["aggtrade_count"].astype(int) + df = self.count_liquidation(count_minutes) / empty_df["aggtrade_count"] + df = df.fillna(0) + + return df def ratio_quote_liquidation(self, count_minutes: int) -> pd.Series: """ @@ -276,14 +373,31 @@ def ratio_quote_liquidation(self, count_minutes: int) -> pd.Series: :param count_minutes: minutes to calculate ratio liquidation """ - self._klines["taker_buy_quote_volume"] = self._klines[ - "taker_buy_quote_volume" - ].astype(float) - return self.count_quote_liquidation( - count_minutes - ) / self._liquidationSnapshot.index.to_series().apply( - lambda x: self._klines[ - (self._klines.index < x) - & (self._klines.index > x - pd.Timedelta(minutes=1)) - ]["taker_buy_quote_volume"].sum() + if self._check_start_end_time(): + aggtrades = self._parse_aggtrades() + else: + aggtrades = self._aggtrades + + aggtrades["price"] = aggtrades["price"].astype(float) + aggtrades["quantity"] = aggtrades["quantity"].astype(float) + aggtrades["amount"] = aggtrades["price"] * aggtrades["quantity"] + index_range = pd.date_range( + start=self._start_time, end=self._end_time, freq="1S", tz="UTC" + ) + empty_df = pd.DataFrame(index=index_range) + + def sum_amount(ts): + start_time = ts - pd.Timedelta(minutes=count_minutes) + subset = aggtrades[(aggtrades.index > start_time) & (aggtrades.index <= ts)] + return subset["amount"].sum() + + empty_df["aggtrade_amount"] = empty_df.index.to_series().apply(sum_amount) + empty_df["aggtrade_amount"] = empty_df["aggtrade_amount"].fillna(0) + empty_df["aggtrade_amount"] = empty_df["aggtrade_amount"].astype(float) + df = ( + self.count_quote_liquidation(count_minutes).abs() + / empty_df["aggtrade_amount"] ) + df = df.fillna(0) + + return df diff --git a/crypto_features/feature/evaluation.py b/crypto_features/feature/evaluation.py index be4c84e..c822e28 100644 --- a/crypto_features/feature/evaluation.py +++ b/crypto_features/feature/evaluation.py @@ -10,11 +10,15 @@ from scipy.stats import pearsonr from sklearn.linear_model import LinearRegression +# from crypto_features.feature.exceptions import InsufficientDataError +from .exceptions import DataNotFoundError, InsufficientDataError + class EvaluationFeature: def __init__(self, **kwargs): self._feature = kwargs.get("feature", None) self._klines = kwargs.get("klines", None) + self._aggtrades = kwargs.get("aggTrades", None) def _make_return(self, minutes: int) -> pd.Series: """ @@ -22,11 +26,13 @@ def _make_return(self, minutes: int) -> pd.Series: :param minutes: minutes to calculate return """ + if self._klines is None: + raise DataNotFoundError("The klines data is not given.") return self._klines["close"].pct_change(minutes) def visualize_histogram(self, return_minutes: int): """ - Visualize histogram of funding rate + Visualize histogram feature vs return :param return_minutes: minutes to calculate return """ @@ -34,9 +40,7 @@ def visualize_histogram(self, return_minutes: int): fig = plt.figure(figsize=(8, 8)) grid = plt.GridSpec(5, 4, hspace=0.5, wspace=0.5) - x, y = InformationCorrelation.format_array( - self._klines, self._feature, return_minutes - ) + x, y = self.format_array(return_minutes) main_ax = fig.add_subplot(grid[1:, 1:]) main_ax.scatter(x, y, alpha=0.5) main_ax.set_xlabel("feature") @@ -57,14 +61,65 @@ def visualize_histogram(self, return_minutes: int): plt.savefig(f"feature_vs_return_{return_minutes}.png") plt.close() + def resample_aggtrades(self): + """ + Resample aggtrades data + :return: resampled aggtrades data + """ + if self._aggtrades is None: + raise DataNotFoundError("The aggtrades data is not given.") + + self._aggtrades["is_buyer_maker_count"] = self._aggtrades["is_buyer_maker"].map( + {True: -1, False: 1} + ) + self._aggtrades["buy_quantity"] = self._aggtrades["quantity"].where( + self._aggtrades["is_buyer_maker"] == False, 0 + ) + self._aggtrades["sell_quantity"] = self._aggtrades["quantity"].where( + self._aggtrades["is_buyer_maker"] == True, 0 + ) + self._aggtrades["price"] = self._aggtrades["price"].astype(float) + + aggregation_updated = { + "price": "mean", + "buy_quantity": "sum", + "sell_quantity": "sum", + "is_buyer_maker_count": "sum", + } + + resampled_updated_data = self._aggtrades.resample("1S").agg(aggregation_updated) + resampled_updated_data["net_quantity"] = ( + resampled_updated_data["buy_quantity"] + - resampled_updated_data["sell_quantity"] + ) + resampled_updated_data["count"] = self._aggtrades.resample("1S").size() + resampled_updated_data.fillna(method="ffill", inplace=True) + resampled_updated_data["close"] = resampled_updated_data["price"].astype(float) + + return resampled_updated_data + def format_array(self, return_minutes=1): """ - Format the array. + Format the array for plotting :param return_minutes: The return minutes. :return: formatted feature and return array. """ - klines = self._klines - feature = self._feature + if self._feature.index.inferred_freq == "T": + feature = self._feature + if self._klines is None: + raise DataNotFoundError("The klines data is not given.") + else: + klines = self._klines + elif self._feature.index.inferred_freq == "S": + feature = self._feature + klines = self.resample_aggtrades() + else: + if self._feature is None: + raise DataNotFoundError("The feature data is not given.") + else: + raise InsufficientDataError( + "The feature data frequency is not in seconds or minutes." + ) close_chg_pct_header = f"close_chg_pct_after_{return_minutes}min" klines["close"] = klines["close"].astype(float) @@ -101,12 +156,7 @@ def information_correlation(self, return_minutes=1, **kwargs): if not os.path.exists("information_correlation"): os.mkdir("information_correlation") - klines = self._klines - feature = self._feature - - feature_arr, klines_arr = InformationCorrelation.format_array( - klines, feature, return_minutes - ) + feature_arr, klines_arr = self.format_array(return_minutes) print("[green] Start calculating the information correlation... [/green]") # Pearson's correlation coefficient @@ -126,13 +176,13 @@ def information_correlation(self, return_minutes=1, **kwargs): linewidth=1, linestyle="-.", ) - plt.xlabel(feature.name) + plt.xlabel(self._feature.name) plt.ylabel(f"close_chg_pct_after_{return_minutes}min [%]") plt.title( - f"rho={round(rho, 3)}, pval={round(pval, 3)}\ncoef={round(lr.coef_[0][0], 3)}, intercept={round(lr.intercept_[0], 3)}\n{feature.name} vs close_chg_pct_after_{return_minutes}min" + f"rho={round(rho, 3)}, pval={round(pval, 3)}\ncoef={round(lr.coef_[0][0], 3)}, intercept={round(lr.intercept_[0], 3)}\n{self._feature.name} vs close_chg_pct_after_{return_minutes}min" ) plt.tight_layout() - save_dir = f"information_correlation/{feature.name}_vs_close_chg_pct_after_{return_minutes}min.png" + save_dir = f"information_correlation/{self._feature.name}_vs_close_chg_pct_after_{return_minutes}min.png" if kwargs.get("save_name", False): save_dir = save_dir.replace(".png", f"_{kwargs['save_name']}.png") plt.savefig(save_dir) diff --git a/crypto_features/feature/exceptions.py b/crypto_features/feature/exceptions.py new file mode 100644 index 0000000..cb50ff7 --- /dev/null +++ b/crypto_features/feature/exceptions.py @@ -0,0 +1,27 @@ +""" +Exceptions for the feature module. +""" + + +class InsufficientDataError(Exception): + """ + Exception for insufficient data. + """ + + pass + + +class DataNotFoundError(Exception): + """ + Exception for data not found. + """ + + pass + + +class InvalidParameterError(Exception): + """ + Exception for invalid parameter. + """ + + pass diff --git a/crypto_features/feature/preprocessing.py b/crypto_features/feature/preprocessing.py index 33f0ad9..bddc6f9 100644 --- a/crypto_features/feature/preprocessing.py +++ b/crypto_features/feature/preprocessing.py @@ -15,6 +15,7 @@ class PreprocessingBinance: """ _BINANCE_KLINES_DIR = os.path.join("data", "futures", "um", "daily", "klines") + _BINANCE_AGGTRADES_DIR = os.path.join("data", "futures", "um", "daily", "aggTrades") _BINANCE_FUNDINGRATE_DIR = os.path.join( "data", "futures", "um", "monthly", "fundingRate" ) @@ -118,6 +119,85 @@ def _load_klines_data(self, symbol) -> pd.DataFrame: return df + def _load_aggtrades_data(self, symbol) -> pd.DataFrame: + """ + Load aggTrades data from csv files. + :param symbol: symbol name + :return: preprocessed aggTrades data + """ + # Load aggTrades data + # merge all csv files + raw_headers = [ + "agg_trade_id", + "price", + "quantity", + "first_trade_id", + "last_trade_id", + "transact_time", + "is_buyer_maker", + ] + + headers = [ + "agg_trade_id", + "price", + "quantity", + "first_trade_id", + "last_trade_id", + "timestamp_open", + "is_buyer_maker", + ] + df = pd.DataFrame(columns=headers) + for file in os.listdir( + os.path.join(self._data_dir, self._BINANCE_AGGTRADES_DIR, symbol) + ): + # header check + df_append_tmp = pd.read_csv( + "/".join( + [ + self._data_dir, + self._BINANCE_AGGTRADES_DIR, + symbol, + file, + ] + ), + nrows=1, + ) + + if list(df_append_tmp) != raw_headers: + df_append = pd.read_csv( + "/".join( + [ + self._data_dir, + self._BINANCE_AGGTRADES_DIR, + symbol, + file, + ] + ), + names=headers, + ) + else: + df_append = pd.read_csv( + "/".join( + [ + self._data_dir, + self._BINANCE_AGGTRADES_DIR, + symbol, + file, + ] + ), + header=None, + ) + df_append = df_append.drop(0, axis=0) + df_append.columns = headers + + df = pd.concat([df, df_append]) + + df["timestamp_open"] = pd.to_datetime(df["timestamp_open"], utc=True, unit="ms") + df.set_index("timestamp_open", inplace=True) + df = df.drop_duplicates(keep="first") + + return df + def _load_fundingrate_data(self, symbol) -> pd.DataFrame: """ Load funding rate data from csv files. @@ -194,21 +274,26 @@ def _load_liquidationsnapshot_data(self, symbol) -> pd.DataFrame: "last_fill_quantity", "accumulated_fill_quantity", ] + + # set index df["timestamp_open"] = pd.to_datetime(df["timestamp_open"], utc=True, unit="ms") df.set_index("timestamp_open", inplace=True) - df.index = df.index.map(lambda x: x.replace(microsecond=0)) df = df.drop_duplicates(keep="first") + df["amount"] = df["average_price"] * df["original_quantity"] return df - def load_klines_data(self): - self._load_klines_data() + def load_klines_data(self, symbol): + return self._load_klines_data(symbol) + + def load_aggtrades_data(self, symbol): + return self._load_aggtrades_data(symbol) - def load_fundingrate_data(self): - self._load_fundingrate_data() + def load_fundingrate_data(self, symbol): + return self._load_fundingrate_data(symbol) - def load_liquidationsnapshot_data(self): - self._load_liquidationsnapshot_data() + def load_liquidationsnapshot_data(self, symbol): + return self._load_liquidationsnapshot_data(symbol) class PreprocessingBybit: @@ -217,6 +302,7 @@ class PreprocessingBybit: """ _BYBIT_KLINES_DIR = os.path.join("bybit_data", "klines") + _BYBIT_AGGTRADES_DIR = os.path.join("bybit_data", "trades") _BYBIT_FUNDINGRATE_DIR = os.path.join("bybit_data", "fundingRate") def __init__(self, data_dir): @@ -268,8 +354,8 @@ def _load_fundingrate_data(self, symbol) -> pd.DataFrame: return df - def load_klines_data(self): - self._load_klines_data() + def load_klines_data(self, symbol): + return self._load_klines_data(symbol) - def load_fundingrate_data(self): - self._load_fundingrate_data() + def load_fundingrate_data(self, symbol): + return self._load_fundingrate_data(symbol)