diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 29c69ff..60f2f91 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -33,4 +33,4 @@ jobs: flake8 . --statistics - name: Test with pytest run: | - python -m pytest tests/ + python -m pytest tests/ --ignore=tests/test_call.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61461c4..eb30c92 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python3 -m build - name: Store the distribution packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ diff --git a/README.md b/README.md index 94e4be3..823c076 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,7 @@ This package is compatible with Python v3.8+. - [Environment Variables](#environment-variables) - [Quickstart](#quickstart) - [Local Development](#local-development) - - [Setup](#setup) - - [Testing](#testing) +- [Tests](#tests) - [Links](#links) - [Contributing](#contributing) - [License](#license) @@ -55,7 +54,6 @@ DataMaxi+ Python package currently includes the following clients: - `Datamaxi` - `Naver` -- `Google` All clients accept the same parameters that are described at [Configuration](#configuration) section. First, import the clients, @@ -66,7 +64,6 @@ from datamaxi.datamaxi import Datamaxi # Trend from datamaxi.naver import Naver -from datamaxi.google import Google ``` and initialize them. @@ -77,16 +74,13 @@ maxi = Datamaxi(api_key=api_key) # Trend naver = Naver(api_key=api_key) -google = Google(api_key=api_key) ``` ## Local Development -### Setup - If you wish to work on local development please clone/fork the git repo and use `pip install -r requirements.txt` to setup the project. -### Testing +## Tests ```shell # In case packages are not installed yet @@ -95,6 +89,7 @@ pip3 install -r requirements/requirements-test.txt python3 -m pytest tests/ ``` + ## Links - [Official Website](https://datamaxiplus.com/) diff --git a/datamaxi/__version__.py b/datamaxi/__version__.py index 770bb8d..6fea77c 100644 --- a/datamaxi/__version__.py +++ b/datamaxi/__version__.py @@ -1 +1 @@ -__version__ = "0.24.0" +__version__ = "0.25.0" diff --git a/datamaxi/datamaxi/__init__.py b/datamaxi/datamaxi/__init__.py index adfc434..327a0ba 100644 --- a/datamaxi/datamaxi/__init__.py +++ b/datamaxi/datamaxi/__init__.py @@ -9,11 +9,8 @@ from datamaxi.datamaxi.cex_ticker import ( # used in documentation # noqa:F401 CexTicker, ) -from datamaxi.datamaxi.cex_orderbook import ( # used in documentation # noqa:F401 - CexOrderbook, -) -from datamaxi.datamaxi.cex_trading_fees import ( # used in documentation # noqa:F401 - CexTradingFees, +from datamaxi.datamaxi.cex_fee import ( # used in documentation # noqa:F401 + CexFee, ) from datamaxi.datamaxi.cex_wallet_status import ( # used in documentation # noqa:F401 CexWalletStatus, @@ -21,8 +18,8 @@ from datamaxi.datamaxi.cex_announcement import ( # used in documentation # noqa:F401 CexAnnouncement, ) -from datamaxi.datamaxi.cex_token_updates import ( # used in documentation # noqa:F401 - CexTokenUpdates, +from datamaxi.datamaxi.cex_token import ( # used in documentation # noqa:F401 + CexToken, ) diff --git a/datamaxi/datamaxi/cex.py b/datamaxi/datamaxi/cex.py index d5e4ef3..43818ae 100644 --- a/datamaxi/datamaxi/cex.py +++ b/datamaxi/datamaxi/cex.py @@ -1,27 +1,28 @@ from typing import Any +from datamaxi.api import API from datamaxi.datamaxi.cex_candle import CexCandle from datamaxi.datamaxi.cex_ticker import CexTicker -from datamaxi.datamaxi.cex_orderbook import CexOrderbook -from datamaxi.datamaxi.cex_trading_fees import CexTradingFees +from datamaxi.datamaxi.cex_fee import CexFee from datamaxi.datamaxi.cex_wallet_status import CexWalletStatus from datamaxi.datamaxi.cex_announcement import CexAnnouncement -from datamaxi.datamaxi.cex_token_updates import CexTokenUpdates +from datamaxi.datamaxi.cex_token import CexToken -class Cex: +class Cex(API): """Client to fetch CEX data from DataMaxi+ API.""" def __init__(self, api_key=None, **kwargs: Any): - """Initialize candle client. + """Initialize CEX client. Args: api_key (str): The DataMaxi+ API key **kwargs: Keyword arguments used by `datamaxi.api.API`. """ + super().__init__(api_key, **kwargs) + self.candle = CexCandle(api_key, **kwargs) self.ticker = CexTicker(api_key, **kwargs) - self.orderbook = CexOrderbook(api_key, **kwargs) - self.trading_fees = CexTradingFees(api_key, **kwargs) + self.fee = CexFee(api_key, **kwargs) self.wallet_status = CexWalletStatus(api_key, **kwargs) - self.announcements = CexAnnouncement(api_key, **kwargs) - self.token_updates = CexTokenUpdates(api_key, **kwargs) + self.announcement = CexAnnouncement(api_key, **kwargs) + self.token = CexToken(api_key, **kwargs) diff --git a/datamaxi/datamaxi/cex_announcement.py b/datamaxi/datamaxi/cex_announcement.py index 202938d..642dc5f 100644 --- a/datamaxi/datamaxi/cex_announcement.py +++ b/datamaxi/datamaxi/cex_announcement.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional from datamaxi.api import API -from datamaxi.lib.constants import BASE_URL +from datamaxi.lib.constants import ASC, DESC class CexAnnouncement(API): @@ -13,16 +13,14 @@ def __init__(self, api_key=None, **kwargs: Any): api_key (str): The DataMaxi+ API key **kwargs: Keyword arguments used by `datamaxi.api.API`. """ - if "base_url" not in kwargs: - kwargs["base_url"] = BASE_URL super().__init__(api_key, **kwargs) - def get( + def __call__( self, category: Optional[str] = None, page: int = 1, limit: int = 1000, - sort: str = "desc", + sort: str = DESC, ) -> Dict[str, Any]: """Get exchange announcements @@ -45,7 +43,7 @@ def get( if limit < 1: raise ValueError("limit must be greater than 0") - if sort not in ["asc", "desc"]: + if sort not in [ASC, DESC]: raise ValueError("sort must be either asc or desc") params = { @@ -60,7 +58,7 @@ def get( raise ValueError("no data found") def next_request(): - return self.get( + return self.__call__( category=category, page=page + 1, limit=limit, diff --git a/datamaxi/datamaxi/cex_candle.py b/datamaxi/datamaxi/cex_candle.py index 83c7cfa..87c0c3f 100644 --- a/datamaxi/datamaxi/cex_candle.py +++ b/datamaxi/datamaxi/cex_candle.py @@ -4,13 +4,14 @@ from datamaxi.lib.utils import check_required_parameter from datamaxi.lib.utils import check_required_parameters from datamaxi.datamaxi.utils import convert_data_to_data_frame +from datamaxi.lib.constants import SPOT, FUTURES, INTERVAL_1D, USD class CexCandle(API): """Client to fetch CEX candle data from DataMaxi+ API.""" def __init__(self, api_key=None, **kwargs: Any): - """Initialize client. + """Initialize cex candle client. Args: api_key (str): The DataMaxi+ API key @@ -18,17 +19,18 @@ def __init__(self, api_key=None, **kwargs: Any): """ super().__init__(api_key, **kwargs) - def get( + self.__module__ = __name__ + self.__qualname__ = self.__class__.__qualname__ + + def __call__( self, exchange: str, + market: str, symbol: str, - interval: str = "1d", - market: str = "spot", - page: int = 1, - limit: int = 1000, - fromDateTime: str = None, - toDateTime: str = None, - sort: str = "desc", + interval: str = INTERVAL_1D, + from_unix: str = None, + to_unix: str = None, + currency: str = USD, pandas: bool = True, ) -> Union[Tuple[Dict, Callable], Tuple[pd.DataFrame, Callable]]: """Fetch candle data @@ -39,14 +41,12 @@ def get( Args: exchange (str): Exchange name + market (str): Market type (spot/futures) symbol (str): Symbol name interval (str): Candle interval - market (str): Market type (spot/futures) - page (int): Page number - limit (int): Limit of data - fromDateTime (str): Start date and time (accepts format "2006-01-02 15:04:05" or "2006-01-02") - toDateTime (str): End date and time (accepts format "2006-01-02 15:04:05" or "2006-01-02") - sort (str): Sort order + from_unix (str): Start time in Unix timestamp + to_unix (str): End time in Unix timestamp + currency (str): Currency pandas (bool): Return data as pandas DataFrame Returns: @@ -58,63 +58,33 @@ def get( [symbol, "symbol"], [interval, "interval"], [market, "market"], + [currency, "currency"], ] ) - if market not in ["spot", "futures"]: + if market not in [SPOT, FUTURES]: raise ValueError("market must be either spot or futures") - if page < 1: - raise ValueError("page must be greater than 0") - - if limit < 1: - raise ValueError("limit must be greater than 0") - - if fromDateTime is not None and toDateTime is not None: - raise ValueError( - "fromDateTime and toDateTime cannot be set at the same time" - ) - - if sort not in ["asc", "desc"]: - raise ValueError("sort must be either asc or desc") - params = { "exchange": exchange, + "market": market, "symbol": symbol, "interval": interval, - "market": market, - "page": page, - "limit": limit, - "from": fromDateTime, - "to": toDateTime, - "sort": sort, + "from": from_unix, + "to": to_unix, + "currency": currency, } res = self.query("/api/v1/cex/candle", params) if res["data"] is None or len(res["data"]) == 0: raise ValueError("no data found") - def next_request(): - return self.get( - exchange, - symbol, - interval, - market, - page + 1, - limit, - fromDateTime, - toDateTime, - sort, - pandas, - ) - if pandas: - df = convert_data_to_data_frame(res["data"]) - return df, next_request + return convert_data_to_data_frame(res["data"]) else: - return res, next_request + return res - def exchanges(self, market: str = "spot") -> List[str]: + def exchanges(self, market: str) -> List[str]: """Fetch supported exchanges accepted by [datamaxi.CexCandle.get](./#datamaxi.datamaxi.CexCandle.get) API. @@ -131,7 +101,7 @@ def exchanges(self, market: str = "spot") -> List[str]: """ check_required_parameter(market, "market") - if market not in ["spot", "futures"]: + if market not in [SPOT, FUTURES]: raise ValueError("market must be either spot or futures") params = {"market": market} @@ -154,7 +124,7 @@ def symbols(self, exchange: str = None, market: str = None) -> List[Dict]: Returns: List of supported symbols """ - if market is not None and market not in ["spot", "futures"]: + if market is not None and market not in [SPOT, FUTURES]: raise ValueError("market must be either spot or futures") params = {} diff --git a/datamaxi/datamaxi/cex_trading_fees.py b/datamaxi/datamaxi/cex_fee.py similarity index 88% rename from datamaxi/datamaxi/cex_trading_fees.py rename to datamaxi/datamaxi/cex_fee.py index 4b8158e..b13192b 100644 --- a/datamaxi/datamaxi/cex_trading_fees.py +++ b/datamaxi/datamaxi/cex_fee.py @@ -3,7 +3,7 @@ from datamaxi.lib.utils import check_required_parameter -class CexTradingFees(API): +class CexFee(API): """Client to fetch CEX trading fee data from DataMaxi+ API.""" def __init__(self, api_key=None, **kwargs: Any): @@ -15,7 +15,7 @@ def __init__(self, api_key=None, **kwargs: Any): """ super().__init__(api_key, **kwargs) - def get( + def __call__( self, exchange: str = None, symbol: str = None, @@ -39,12 +39,12 @@ def get( if symbol: params["symbol"] = symbol - url_path = "/api/v1/trading-fees" + url_path = "/api/v1/cex/fees" return self.query(url_path, params) def exchanges(self) -> List[str]: """Fetch supported exchanges accepted by - [datamaxi.CexTradingFees.get](./#datamaxi.datamaxi.CexTradingFees.get) + [datamaxi.CexFee.get](./#datamaxi.datamaxi.CexFee.get) API. `GET /api/v1/trading-fees/exchanges` @@ -54,7 +54,7 @@ def exchanges(self) -> List[str]: Returns: List of supported exchange """ - url_path = "/api/v1/trading-fees/exchanges" + url_path = "/api/v1/cex/fees/exchanges" return self.query(url_path) def symbols(self, exchange: str) -> List[str]: @@ -78,5 +78,5 @@ def symbols(self, exchange: str) -> List[str]: "exchange": exchange, } - url_path = "/api/v1/trading-fees/symbols" + url_path = "/api/v1/cex/fees/symbols" return self.query(url_path, params) diff --git a/datamaxi/datamaxi/cex_orderbook.py b/datamaxi/datamaxi/cex_orderbook.py deleted file mode 100644 index a277b83..0000000 --- a/datamaxi/datamaxi/cex_orderbook.py +++ /dev/null @@ -1,95 +0,0 @@ -from typing import Any, List, Dict, Union -import pandas as pd -from datamaxi.api import API -from datamaxi.lib.utils import check_required_parameters -from datamaxi.lib.utils import check_required_parameter - - -class CexOrderbook(API): - """Client to fetch orderbook data from DataMaxi+ API.""" - - def __init__(self, api_key=None, **kwargs: Any): - """Initialize orderbook client. - - Args: - api_key (str): The DataMaxi+ API key - **kwargs: Keyword arguments used by `datamaxi.api.API`. - """ - super().__init__(api_key, **kwargs) - - def get( - self, - exchange: str, - symbol: str, - pandas: bool = True, - ) -> Union[Dict, pd.DataFrame]: - """Fetch orderbook data - - `GET /api/v1/orderbook` - - - - Args: - exchange (str): Exchange name - symbol (str): symbol name - pandas (bool): Return data as pandas DataFrame - - Returns: - CexOrderbook data in pandas DataFrame - """ - - check_required_parameters( - [ - [exchange, "exchange"], - [symbol, "symbol"], - ] - ) - - params = {"exchange": exchange, "symbol": symbol} - - res = self.query("/api/v1/orderbook", params) - if pandas: - df = pd.DataFrame(res) - df = df.set_index("d") - return df - - return res - - def exchanges(self) -> List[str]: - """Fetch supported exchanges accepted by - [datamaxi.CexOrderbook.get](./#datamaxi.datamaxi.CexOrderbook.get) - API. - - `GET /api/v1/orderbook/exchanges` - - - - Returns: - List of supported exchange - """ - url_path = "/api/v1/orderbook/exchanges" - return self.query(url_path) - - def symbols(self, exchange: str) -> List[str]: - """Fetch supported symbols accepted by - [datamaxi.CexOrderbook.get](./#datamaxi.datamaxi.CexOrderbook.get) - API. - - `GET /api/v1/orderbook/symbols` - - - - Args: - exchange (str): Exchange name - - Returns: - List of supported symbols - """ - check_required_parameter(exchange, "exchange") - - params = { - "exchange": exchange, - } - - url_path = "/api/v1/orderbook/symbols" - return self.query(url_path, params) diff --git a/datamaxi/datamaxi/cex_ticker.py b/datamaxi/datamaxi/cex_ticker.py index 42a8c3b..eb2762b 100644 --- a/datamaxi/datamaxi/cex_ticker.py +++ b/datamaxi/datamaxi/cex_ticker.py @@ -2,14 +2,14 @@ import pandas as pd from datamaxi.api import API from datamaxi.lib.utils import check_required_parameters -from datamaxi.lib.utils import check_required_parameter +from datamaxi.lib.constants import SPOT, FUTURES class CexTicker(API): """Client to fetch ticker data from DataMaxi+ API.""" def __init__(self, api_key=None, **kwargs: Any): - """Initialize ticker client. + """Initialize cex ticker client. Args: api_key (str): The DataMaxi+ API key @@ -21,6 +21,7 @@ def get( self, exchange: str, symbol: str, + market: str, pandas: bool = True, ) -> Union[Dict, pd.DataFrame]: """Fetch ticker data @@ -32,6 +33,7 @@ def get( Args: exchange (str): Exchange name symbol (str): Symbol name + market (str): Market type (spot/futures) pandas (bool): Return data as pandas DataFrame Returns: @@ -42,12 +44,17 @@ def get( [ [exchange, "exchange"], [symbol, "symbol"], + [market, "market"], ] ) + if market not in [SPOT, FUTURES]: + raise ValueError("market must be either spot or futures") + params = { "exchange": exchange, "symbol": symbol, + "market": market, } res = self.query("/api/v1/ticker", params) @@ -59,7 +66,10 @@ def get( else: return res - def exchanges(self) -> List[str]: + def exchanges( + self, + market: str, + ) -> List[str]: """Fetch supported exchanges accepted by [datamaxi.CexTicker.get](./#datamaxi.datamaxi.CexTicker.get) API. @@ -68,13 +78,33 @@ def exchanges(self) -> List[str]: + Args: + market (str): Market type (spot/futures) + Returns: List of supported exchange """ + check_required_parameters( + [ + [market, "market"], + ] + ) + + if market not in [SPOT, FUTURES]: + raise ValueError("market must be either spot or futures") + + params = { + "market": market, + } + url_path = "/api/v1/ticker/exchanges" - return self.query(url_path) + return self.query(url_path, params) - def symbols(self, exchange: str) -> List[str]: + def symbols( + self, + exchange: str, + market: str, + ) -> List[str]: """Fetch supported symbols accepted by [datamaxi.CexTicker.get](./#datamaxi.datamaxi.CexTicker.get) API. @@ -85,14 +115,24 @@ def symbols(self, exchange: str) -> List[str]: Args: exchange (str): Exchange name + market (str): Market type (spot/futures) Returns: List of supported symbols """ - check_required_parameter(exchange, "exchange") + check_required_parameters( + [ + [exchange, "exchange"], + [market, "market"], + ] + ) + + if market not in [SPOT, FUTURES]: + raise ValueError("market must be either spot or futures") params = { "exchange": exchange, + "market": market, } url_path = "/api/v1/ticker/symbols" diff --git a/datamaxi/datamaxi/cex_token_updates.py b/datamaxi/datamaxi/cex_token.py similarity index 85% rename from datamaxi/datamaxi/cex_token_updates.py rename to datamaxi/datamaxi/cex_token.py index b567171..08d99a6 100644 --- a/datamaxi/datamaxi/cex_token_updates.py +++ b/datamaxi/datamaxi/cex_token.py @@ -1,9 +1,9 @@ from typing import Any, Dict, Optional from datamaxi.api import API -from datamaxi.lib.constants import BASE_URL +from datamaxi.lib.constants import DESC, ASC -class CexTokenUpdates(API): +class CexToken(API): """Client to fetch token update data from DataMaxi+ API.""" def __init__(self, api_key=None, **kwargs: Any): @@ -13,16 +13,14 @@ def __init__(self, api_key=None, **kwargs: Any): api_key (str): The DataMaxi+ API key **kwargs: Keyword arguments used by `datamaxi.api.API`. """ - if "base_url" not in kwargs: - kwargs["base_url"] = BASE_URL super().__init__(api_key, **kwargs) - def get( + def updates( self, type: Optional[str] = None, page: int = 1, limit: int = 1000, - sort: str = "desc", + sort: str = DESC, ) -> Dict[str, Any]: """Get token update data @@ -45,7 +43,7 @@ def get( if limit < 1: raise ValueError("limit must be greater than 0") - if sort not in ["asc", "desc"]: + if sort not in [ASC, DESC]: raise ValueError("sort must be either asc or desc") if type is not None and type not in ["listed", "delisted"]: @@ -58,7 +56,7 @@ def get( "sort": sort, } - res = self.query("/api/v1/cex/token-updates", params) + res = self.query("/api/v1/cex/token/updates", params) if res["data"] is None: raise ValueError("no data found") diff --git a/datamaxi/datamaxi/cex_wallet_status.py b/datamaxi/datamaxi/cex_wallet_status.py index 09f9842..3999145 100644 --- a/datamaxi/datamaxi/cex_wallet_status.py +++ b/datamaxi/datamaxi/cex_wallet_status.py @@ -6,7 +6,7 @@ class CexWalletStatus(API): - """Client to fetch wallet status data from DataMaxi+ API.""" + """Client to fetch transfer status data from DataMaxi+ API.""" def __init__(self, api_key=None, **kwargs: Any): """Initialize wallet status client. @@ -17,13 +17,13 @@ def __init__(self, api_key=None, **kwargs: Any): """ super().__init__(api_key, **kwargs) - def get( + def __call__( self, exchange: str, asset: str, pandas: bool = True, ) -> Union[Dict, pd.DataFrame]: - """Fetch wallet status data + """Fetch transfer status data `GET /api/v1/wallet-status` @@ -60,7 +60,7 @@ def get( def exchanges(self) -> List[str]: """Fetch supported exchanges accepted by - [datamaxi.CexWalletStatus.get](./#datamaxi.datamaxi.CexWalletStatus.get) + [datamaxi.CexWalletStatus.__call__](./#datamaxi.datamaxi.CexWalletStatus.__call__) API. `GET /api/v1/wallet-status/exchanges` @@ -75,7 +75,7 @@ def exchanges(self) -> List[str]: def assets(self, exchange: str) -> List[str]: """Fetch supported assets accepted by - [datamaxi.CexWalletStatus.get](./#datamaxi.datamaxi.CexWalletStatus.get) + [datamaxi.CexWalletStatus.__call__](./#datamaxi.datamaxi.CexWalletStatus.__call__) API. `GET /api/v1/wallet-status/assets` diff --git a/datamaxi/datamaxi/dex.py b/datamaxi/datamaxi/dex.py index 205cfd9..51e3e53 100644 --- a/datamaxi/datamaxi/dex.py +++ b/datamaxi/datamaxi/dex.py @@ -1,8 +1,10 @@ from typing import Any, Callable, Tuple, List, Dict, Union +import logging from datamaxi.api import API import pandas as pd from datamaxi.lib.utils import check_required_parameters from datamaxi.datamaxi.utils import convert_data_to_data_frame +from datamaxi.lib.constants import ASC, DESC class Dex(API): @@ -49,6 +51,8 @@ def trade( Returns: DEX trade data in pandas DataFrame and next request function """ + logging.warning("warning: dex related endpoints are experimental") + check_required_parameters( [ [chain, "chain"], @@ -67,7 +71,7 @@ def trade( "fromDateTime and toDateTime cannot be set at the same time" ) - if sort not in ["asc", "desc"]: + if sort not in [ASC, DESC]: raise ValueError("sort must be either asc or desc") params = { @@ -138,6 +142,8 @@ def candle( Returns: DEX candle data in pandas DataFrame and next request function """ + logging.warning("warning: dex related endpoints are experimental") + check_required_parameters( [ [chain, "chain"], @@ -158,7 +164,7 @@ def candle( "fromDateTime and toDateTime cannot be set at the same time" ) - if sort not in ["asc", "desc"]: + if sort not in [ASC, DESC]: raise ValueError("sort must be either asc or desc") params = { @@ -229,6 +235,8 @@ def liquidity( Returns: DEX liquidity data in pandas DataFrame and next request function """ + logging.warning("warning: dex related endpoints are experimental") + check_required_parameters( [ [chain, "chain"], @@ -248,7 +256,7 @@ def liquidity( "fromDateTime and toDateTime cannot be set at the same time" ) - if sort not in ["asc", "desc"]: + if sort not in [ASC, DESC]: raise ValueError("sort must be either asc or desc") params = { @@ -282,7 +290,6 @@ def next_request(): if pandas: df = convert_data_to_data_frame( res["data"], - ["b", "l"], ) return df, next_request else: @@ -301,6 +308,7 @@ def chains(self) -> List[str]: Returns: List of supported chains """ + logging.warning("warning: dex related endpoints are experimental") url_path = "/api/v1/dex/chains" return self.query(url_path) @@ -318,6 +326,7 @@ def exchanges(self) -> List[str]: Returns: List of supported exchanges """ + logging.warning("warning: dex related endpoints are experimental") url_path = "/api/v1/dex/exchanges" return self.query(url_path) @@ -359,5 +368,7 @@ def intervals(self) -> List[str]: Returns: List of supported intervals """ + logging.warning("warning: dex related endpoints are experimental") + url_path = "/api/v1/dex/intervals" return self.query(url_path) diff --git a/datamaxi/datamaxi/forex.py b/datamaxi/datamaxi/forex.py index eb66708..3c3e619 100644 --- a/datamaxi/datamaxi/forex.py +++ b/datamaxi/datamaxi/forex.py @@ -16,7 +16,10 @@ def __init__(self, api_key=None, **kwargs: Any): """ super().__init__(api_key, **kwargs) - def get( + self.__module__ = __name__ + self.__qualname__ = self.__class__.__qualname__ + + def __call__( self, symbol: str, pandas: bool = True, @@ -43,9 +46,7 @@ def get( res = self.query("/api/v1/forex", params) if pandas: - df = pd.DataFrame(res) - df = df.set_index("d") - return df + return pd.DataFrame([res]) else: return res diff --git a/datamaxi/datamaxi/funding_rate.py b/datamaxi/datamaxi/funding_rate.py index edf9d9c..34c248d 100644 --- a/datamaxi/datamaxi/funding_rate.py +++ b/datamaxi/datamaxi/funding_rate.py @@ -4,6 +4,7 @@ from datamaxi.lib.utils import check_required_parameter from datamaxi.lib.utils import check_required_parameters from datamaxi.datamaxi.utils import convert_data_to_data_frame +from datamaxi.lib.constants import ASC, DESC class FundingRate(API): @@ -18,7 +19,7 @@ def __init__(self, api_key=None, **kwargs: Any): """ super().__init__(api_key, **kwargs) - def get( + def history( self, exchange: str, symbol: str, @@ -26,7 +27,7 @@ def get( limit: int = 1000, fromDateTime: str = None, toDateTime: str = None, - sort: str = "desc", + sort: str = DESC, pandas: bool = True, ) -> Union[Tuple[Dict, Callable], Tuple[pd.DataFrame, Callable]]: """Fetch historical funding rate data @@ -66,7 +67,7 @@ def get( "fromDateTime and toDateTime cannot be set at the same time" ) - if sort not in ["asc", "desc"]: + if sort not in [ASC, DESC]: raise ValueError("sort must be either asc or desc") params = { @@ -79,7 +80,7 @@ def get( "sort": sort, } - res = self.query("/api/v1/funding-rate", params) + res = self.query("/api/v1/funding-rate/history", params) if res["data"] is None or len(res["data"]) == 0: raise ValueError("no data found") @@ -101,7 +102,7 @@ def next_request(): else: return res, next_request - def getLatest( + def latest( self, sort: str = None, limit: int = None, @@ -142,7 +143,7 @@ def getLatest( res = self.query("/api/v1/funding-rate/latest", params) if pandas: - df = pd.DataFrame(res) + df = pd.DataFrame([res]) df = df.set_index("d") return df else: diff --git a/datamaxi/datamaxi/premium.py b/datamaxi/datamaxi/premium.py index 186797f..9bf8cfd 100644 --- a/datamaxi/datamaxi/premium.py +++ b/datamaxi/datamaxi/premium.py @@ -1,7 +1,6 @@ from typing import Any, List, Union import pandas as pd from datamaxi.api import API -from datamaxi.lib.utils import check_required_parameters class Premium(API): @@ -16,13 +15,21 @@ def __init__(self, api_key=None, **kwargs: Any): """ super().__init__(api_key, **kwargs) - def get( + self.__module__ = __name__ + self.__qualname__ = self.__class__.__qualname__ + + def __call__( # noqa: C901 self, + source_exchange: str = None, + target_exchange: str = None, + asset: str = None, + source_quote: str = None, + target_quote: str = None, sort: str = None, - limit: int = None, - symbol: str = None, - sourceExchange: str = None, - targetExchange: str = None, + key: str = None, + page: int = 1, + limit: int = 100, + currency: str = None, pandas: bool = True, ) -> Union[List, pd.DataFrame]: """Fetch premium data @@ -31,11 +38,16 @@ def get( Args: + source_exchange (str): Source exchange name + target_exchange (str): Target exchange name + asset (str): Asset name + source_quote (str): Source quote currency + target_quote (str): Target quote currency + currency (str): Currency applied to cross-exchange price differences sort (str): Sort data by `asc` or `desc` - limit (int): Limit number of data to return - symbol (str): Symbol name - sourceExchange (str): Source exchange name - targetExchange (str): Target exchange name + key (str): Key to sort data + page (int): Page number + limit (int): Page size pandas (bool): Return data as pandas DataFrame Returns: @@ -43,25 +55,42 @@ def get( """ params = {} + if source_exchange is not None: + params["sourceExchange"] = source_exchange + + if target_exchange is not None: + params["targetExchange"] = target_exchange + + if asset is not None: + params["asset"] = asset + + if source_quote is not None: + params["sourceQuote"] = source_quote + + if target_quote is not None: + params["targetQuote"] = target_quote + if sort is not None: params["sort"] = sort - if limit is not None: - params["limit"] = limit + if page is not None: + params["page"] = page - if symbol is not None: - params["symbol"] = symbol + if key is not None: + params["key"] = key - if sourceExchange is not None: - params["sourceExchange"] = sourceExchange + if limit is not None: + params["limit"] = limit - if targetExchange is not None: - params["targetExchange"] = targetExchange + if currency is not None: + params["currency"] = currency res = self.query("/api/v1/premium", params) + if res["data"] is None or len(res["data"]) == 0: + raise ValueError("no data found") if pandas: - df = pd.DataFrame(res) + df = pd.DataFrame(res["data"]) df = df.set_index("d") return df else: @@ -81,30 +110,3 @@ def exchanges(self) -> List[str]: """ url_path = "/api/v1/premium/exchanges" return self.query(url_path) - - def symbols(self, sourceExchange: str, targetExchange: str) -> List[str]: - """Fetch supported symbols accepted by - [datamaxi.Premium.get](./#datamaxi.datamaxi.Premium.get) - API. - - `GET /api/v1/premium/symbols` - - - - Args: - sourceExchange (str): Source exchange name - targetExchange (str): Target exchange name - - Returns: - List of supported symbols - """ - check_required_parameters( - [ - [sourceExchange, "sourceExchange"], - [targetExchange, "targetExchange"], - ] - ) - - params = {"sourceExchange": sourceExchange, "targetExchange": targetExchange} - url_path = "/api/v1/premium/symbols" - return self.query(url_path, params) diff --git a/datamaxi/google/__init__.py b/datamaxi/google/__init__.py deleted file mode 100644 index 0bb3e20..0000000 --- a/datamaxi/google/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Any, List, Union -import pandas as pd -from datamaxi.api import API -from datamaxi.lib.utils import check_required_parameter -from datamaxi.lib.utils import postprocess -from datamaxi.lib.constants import BASE_URL - - -class Google(API): - """Client to fetch Google trend data from DataMaxi+ API.""" - - def __init__(self, api_key=None, **kwargs: Any): - """Initialize the object. - - Args: - api_key (str): The DataMaxi+ API key - **kwargs: Keyword arguments used by `datamaxi.api.API`. - """ - if "base_url" not in kwargs: - kwargs["base_url"] = BASE_URL - super().__init__(api_key, **kwargs) - - def keywords(self) -> List[str]: - """Get Google trend supported keywords - - `GET /api/v1/google/keywords` - - - - Returns: - List of supported Google trend keywords - """ - url_path = "/api/v1/google/keywords" - return self.query(url_path) - - @postprocess() - def trend(self, keyword: str, pandas: bool = True) -> Union[List, pd.DataFrame]: - """Get Google trend for given keyword - - `GET /api/v1/google/trend` - - - - Args: - keyword (str): keyword to search for - pandas (bool): Return data as pandas DataFrame - - Returns: - Google trend data - """ - check_required_parameter(keyword, "keyword") - params = {"keyword": keyword} - return self.query("/api/v1/google/trend", params) diff --git a/datamaxi/lib/constants.py b/datamaxi/lib/constants.py index 150d166..76d1e55 100644 --- a/datamaxi/lib/constants.py +++ b/datamaxi/lib/constants.py @@ -1 +1,29 @@ -BASE_URL = "https://api.datamaxiplus.com" +from typing import Final + +BASE_URL: Final = "https://api.datamaxiplus.com" +SPOT: Final = "spot" +FUTURES: Final = "futures" +USD: Final = "USD" + +INTERVAL_1M: Final = "1m" +INTERVAL_5M: Final = "5m" +INTERVAL_15M: Final = "15m" +INTERVAL_30M: Final = "30m" +INTERVAL_1H: Final = "1h" +INTERVAL_4H: Final = "4h" +INTERVAL_12H: Final = "12h" +INTERVAL_1D: Final = "1d" + +SUPPORTED_INTERVALS: Final = [ + INTERVAL_1M, + INTERVAL_5M, + INTERVAL_15M, + INTERVAL_30M, + INTERVAL_1H, + INTERVAL_4H, + INTERVAL_12H, + INTERVAL_1D, +] + +ASC: Final = "asc" +DESC: Final = "desc" diff --git a/docs/google-trend.md b/docs/cex-fee.md similarity index 62% rename from docs/google-trend.md rename to docs/cex-fee.md index 37cb640..ba8b3d3 100644 --- a/docs/google-trend.md +++ b/docs/cex-fee.md @@ -1,6 +1,6 @@ -# Google Trend +# CEX Fees -::: datamaxi.google +::: datamaxi.datamaxi.CexFee options: show_submodules: true show_source: false diff --git a/docs/cex-token-updates.md b/docs/cex-token-updates.md deleted file mode 100644 index ee0c0af..0000000 --- a/docs/cex-token-updates.md +++ /dev/null @@ -1,6 +0,0 @@ -# CEX Token Updates - -::: datamaxi.datamaxi.CexTokenUpdates - options: - show_submodules: true - show_source: false diff --git a/docs/cex-orderbook.md b/docs/cex-token.md similarity index 56% rename from docs/cex-orderbook.md rename to docs/cex-token.md index 71c6891..e7f30cf 100644 --- a/docs/cex-orderbook.md +++ b/docs/cex-token.md @@ -1,6 +1,6 @@ -# CEX Orderbook +# CEX Token -::: datamaxi.datamaxi.CexOrderbook +::: datamaxi.datamaxi.CexToken options: show_submodules: true show_source: false diff --git a/docs/cex-trading-fees.md b/docs/cex-trading-fees.md deleted file mode 100644 index fd0560d..0000000 --- a/docs/cex-trading-fees.md +++ /dev/null @@ -1,6 +0,0 @@ -# CEX Trading Fees - -::: datamaxi.datamaxi.CexTradingFees - options: - show_submodules: true - show_source: false diff --git a/mkdocs.yml b/mkdocs.yml index 1dd3f20..d5061a3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -34,18 +34,15 @@ nav: - CEX: - Candle: cex-candle.md - Ticker: cex-ticker.md - - Orderbook: cex-orderbook.md - - Trading Fees: cex-trading-fees.md + - Fees: cex-fee.md - Wallet Status: cex-wallet-status.md - Announcement: cex-announcement.md - - Token Updates: cex-token-updates.md + - Token: cex-token.md - DEX: dex.md - Funding Rate: funding-rate.md - Premium: premium.md - Forex: forex.md - - Trend: - - Naver Trend: naver-trend.md - - Google Trend: google-trend.md + - Naver Trend: naver-trend.md - Telegram: telegram.md theme: diff --git a/pyproject.toml b/pyproject.toml index c5545e4..9b47c87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] name = "datamaxi" -version = "0.24.0" +version = "0.25.0" authors = [ { name="Bisonai", email="business@bisonai.com" }, ] -description = "Official Python client for DataMaxi+ API" +description = "Official Python SDK for DataMaxi+ API" readme = "README.md" requires-python = ">=3.8" classifiers = [ diff --git a/tests/google/test_google_keywords.py b/tests/google/test_google_keywords.py deleted file mode 100644 index e5512a9..0000000 --- a/tests/google/test_google_keywords.py +++ /dev/null @@ -1,24 +0,0 @@ -import responses - -from datamaxi.google import Google as Client -from tests.util import random_str -from tests.util import mock_http_response - - -mock_item = ["a", "b", "c"] - -key = random_str() -client = Client(key) - - -@mock_http_response( - responses.GET, - "/v1/google/keywords", - mock_item, - 200, -) -def test_google_keywords(): - """Tests the API endpoint to get google trend keywords.""" - - response = client.keywords() - response.should.equal(mock_item) diff --git a/tests/google/test_google_trend.py b/tests/google/test_google_trend.py deleted file mode 100644 index b2d066b..0000000 --- a/tests/google/test_google_trend.py +++ /dev/null @@ -1,28 +0,0 @@ -import responses - -from datamaxi.google import Google as Client -from tests.util import random_str -from tests.util import mock_http_response -from urllib.parse import urlencode - - -mock_item = [["Timestamp", "Ethereum"], ["2020-01-01 00:00", "9723"]] - -key = random_str() -client = Client(key) - -req_params = {"keyword": "Ethereum"} -params = {"keyword": "Ethereum", "pandas": False} - - -@mock_http_response( - responses.GET, - "/v1/google/trend\\?" + urlencode(req_params), - mock_item, - 200, -) -def test_google_trend(): - """Tests the API endpoint to get google trend.""" - - response = client.trend(**params) - response.should.equal(mock_item) diff --git a/tests/naver/test_naver_symbols.py b/tests/naver/test_naver_symbols.py deleted file mode 100644 index 6aa9410..0000000 --- a/tests/naver/test_naver_symbols.py +++ /dev/null @@ -1,24 +0,0 @@ -import responses - -from datamaxi.naver import Naver as Client -from tests.util import random_str -from tests.util import mock_http_response - - -mock_item = ["a", "b", "c"] - -key = random_str() -client = Client(key) - - -@mock_http_response( - responses.GET, - "/v1/naver/symbols", - mock_item, - 200, -) -def test_naver_symbols(): - """Tests the API endpoint to get naver trend token symbols.""" - - response = client.symbols() - response.should.equal(mock_item) diff --git a/tests/naver/test_naver_trend.py b/tests/naver/test_naver_trend.py deleted file mode 100644 index c93c2ee..0000000 --- a/tests/naver/test_naver_trend.py +++ /dev/null @@ -1,28 +0,0 @@ -import responses - -from datamaxi.naver import Naver as Client -from tests.util import random_str -from tests.util import mock_http_response -from urllib.parse import urlencode - - -mock_item = [["Date", "Ethereum"], ["2020-01-01 00:00", "9723"]] - -key = random_str() -client = Client(key) - -req_params = {"symbol": "ETH"} -params = {"symbol": "ETH", "pandas": False} - - -@mock_http_response( - responses.GET, - "/v1/naver/trend\\?" + urlencode(req_params), - mock_item, - 200, -) -def test_naver_trend(): - """Tests the API endpoint to get naver trend.""" - - response = client.trend(**params) - response.should.equal(mock_item) diff --git a/tests/test_call.py b/tests/test_call.py new file mode 100644 index 0000000..474813c --- /dev/null +++ b/tests/test_call.py @@ -0,0 +1,87 @@ +import os +import logging +from datamaxi import Datamaxi + + +logging.basicConfig(level=logging.INFO) + +api_key = os.getenv("API_KEY") +base_url = os.getenv("BASE_URL") or "https://api.datamaxiplus.com" + +datamaxi = Datamaxi(api_key=api_key, base_url=base_url) + + +def test_cex_candle(): + datamaxi.cex.candle( + exchange="binance", + symbol="BTC-USDT", + interval="1m", + market="spot", + ) + datamaxi.cex.candle.exchanges(market="spot") + datamaxi.cex.candle.symbols(exchange="binance", market="spot") + datamaxi.cex.candle.intervals() + + +def test_cex_ticker(): + datamaxi.cex.ticker.exchanges(market="spot") + datamaxi.cex.ticker.symbols(exchange="binance", market="spot") + + +def test_cex_token_updates(): + datamaxi.cex.token.updates() + + +def test_cex_fees(): + datamaxi.cex.fee(exchange="binance", symbol="BTC-USDT") + datamaxi.cex.fee.exchanges() + datamaxi.cex.fee.symbols(exchange="binance") + + +def test_cex_announcement(): + datamaxi.cex.announcement() + + +def test_cex_wallet_status(): + datamaxi.cex.wallet_status(exchange="binance", asset="BTC") + datamaxi.cex.wallet_status.exchanges() + datamaxi.cex.wallet_status.assets(exchange="binance") + + +def test_test_funding_rate(): + datamaxi.funding_rate.history(exchange="binance", symbol="BTC-USDT") + datamaxi.funding_rate.latest(exchange="binance", symbol="BTC-USDT") + datamaxi.funding_rate.exchanges() + datamaxi.funding_rate.symbols(exchange="binance") + + +def test_dex(): + datamaxi.dex.chains() + datamaxi.dex.exchanges() + datamaxi.dex.pools(exchange="klayswap", chain="kaia_mainnet") + datamaxi.dex.intervals() + datamaxi.dex.trade( + exchange="pancakeswap", + chain="bsc_mainnet", + pool="0xb24cd29e32FaCDDf9e73831d5cD1FFcd1e535423", + ) + datamaxi.dex.liquidity( + exchange="pancakeswap", + chain="bsc_mainnet", + pool="0xb24cd29e32FaCDDf9e73831d5cD1FFcd1e535423", + ) + datamaxi.dex.trade( + exchange="pancakeswap", + chain="bsc_mainnet", + pool="0xb24cd29e32FaCDDf9e73831d5cD1FFcd1e535423", + ) + + +def test_forex(): + datamaxi.forex.symbols() + datamaxi.forex(symbol="USD-KRW") + + +def test_premium(): + datamaxi.premium() + datamaxi.premium.exchanges()