Documented Exchange.py

This commit is contained in:
Rob 2024-08-03 17:35:40 -03:00
parent 4130e0ca9a
commit 917ccedbaf
1 changed files with 271 additions and 1 deletions

View File

@ -11,9 +11,21 @@ logger = logging.getLogger(__name__)
class Exchange:
"""
A class to interact with a cryptocurrency exchange using the CCXT library.
"""
_market_cache = {}
def __init__(self, name: str, api_keys: Dict[str, str], exchange_id: str):
def __init__(self, name: str, api_keys: Dict[str, str] | None, exchange_id: str):
"""
Initializes the Exchange object.
Parameters:
name (str): The name of this exchange instance.
api_keys (Dict[str, str]): Dictionary containing 'key' and 'secret' for API authentication.
exchange_id (str): The ID of the exchange as recognized by CCXT. Example('binance')
"""
self.name = name
self.api_key = api_keys['key'] if api_keys else None
self.api_key_secret = api_keys['secret'] if api_keys else None
@ -26,6 +38,12 @@ class Exchange:
self.symbols_n_precision = {}
def _connect_exchange(self) -> ccxt.Exchange:
"""
Connects to the exchange using the CCXT library.
Returns:
ccxt.Exchange: An instance of the CCXT exchange class.
"""
exchange_class = getattr(ccxt, self.exchange_id)
if not exchange_class:
logger.error(f"Exchange {self.exchange_id} is not supported by CCXT.")
@ -47,10 +65,29 @@ class Exchange:
@staticmethod
def datetime_to_unix_millis(dt: datetime) -> int:
"""
Converts a datetime object to Unix milliseconds.
Parameters:
dt (datetime): The datetime object to convert.
Returns:
int: The Unix timestamp in milliseconds.
"""
return int(dt.timestamp() * 1000)
def _fetch_historical_klines(self, symbol: str, interval: str,
start_dt: datetime, end_dt: datetime = None) -> pd.DataFrame:
"""
Fetches historical OHLCV data for a given symbol and interval.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
interval (str): The interval for the OHLCV data (e.g., '1d').
start_dt (datetime): The start date for fetching data.
end_dt (datetime, optional): The end date for fetching data. Defaults to the current UTC time.
Returns:
pd.DataFrame: A DataFrame containing the OHLCV data.
"""
if end_dt is None:
end_dt = datetime.utcnow()
@ -92,6 +129,15 @@ class Exchange:
return pd.DataFrame(columns=['open_time', 'open', 'high', 'low', 'close', 'volume'])
def _fetch_price(self, symbol: str) -> float:
"""
Fetches the current price for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
Returns:
float: The current price.
"""
try:
ticker = self.client.fetch_ticker(symbol)
return float(ticker['last'])
@ -100,6 +146,15 @@ class Exchange:
return 0.0
def _fetch_min_qty(self, symbol: str) -> float:
"""
Fetches the minimum quantity for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
Returns:
float: The minimum quantity.
"""
try:
market_data = self.exchange_info[symbol]
return float(market_data['limits']['amount']['min'])
@ -108,6 +163,15 @@ class Exchange:
return 0.0
def _fetch_min_notional_qty(self, symbol: str) -> float:
"""
Fetches the minimum notional quantity for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
Returns:
float: The minimum notional quantity.
"""
try:
market_data = self.exchange_info[symbol]
return float(market_data['limits']['cost']['min'])
@ -116,6 +180,16 @@ class Exchange:
return 0.0
def _fetch_order(self, symbol: str, order_id: str) -> object:
"""
Fetches an order by its ID for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
order_id (str): The ID of the order.
Returns:
object: The order details.
"""
try:
return self.client.fetch_order(order_id, symbol)
except ccxt.BaseError as e:
@ -123,6 +197,12 @@ class Exchange:
return None
def _set_symbols(self) -> List[str]:
"""
Sets the list of available symbols on the exchange.
Returns:
List[str]: A list of trading symbols.
"""
try:
markets = self.client.fetch_markets()
symbols = [market['symbol'] for market in markets if market['active']]
@ -132,6 +212,12 @@ class Exchange:
return []
def _set_balances(self) -> List[Dict[str, Union[str, float]]]:
"""
Sets the balances of the account.
Returns:
List[Dict[str, Union[str, float]]]: A list of balances with asset, balance, and PnL.
"""
if self.api_key and self.api_key_secret:
try:
account_info = self.client.fetch_balance()
@ -148,6 +234,12 @@ class Exchange:
return [{'asset': 'N/A', 'balance': 0, 'pnl': 0}]
def _set_exchange_info(self) -> dict:
"""
Sets the exchange information and caches it.
Returns:
dict: The exchange information.
"""
if self.exchange_id in Exchange._market_cache:
return Exchange._market_cache[self.exchange_id]
@ -160,21 +252,60 @@ class Exchange:
return {}
def get_client(self) -> object:
"""
Returns the CCXT client instance.
Returns:
object: The CCXT client.
"""
return self.client
def get_avail_intervals(self) -> Tuple[str, ...]:
"""
Returns the available time intervals for OHLCV data.
Returns:
Tuple[str, ...]: A tuple of available intervals.
"""
return self.intervals
def get_exchange_info(self) -> dict:
"""
Returns the exchange information.
Returns:
dict: The exchange information.
"""
return self.exchange_info
def get_symbols(self) -> List[str]:
"""
Returns the list of available symbols.
Returns:
List[str]: A list of trading symbols.
"""
return self.symbols
def get_balances(self) -> List[Dict[str, Union[str, float]]]:
"""
Returns the balances of the account.
Returns:
List[Dict[str, Union[str, float]]]: A list of balances with asset, balance, and PnL.
"""
return self.balances
def get_symbol_precision_rule(self, symbol: str) -> int:
"""
Returns the precision rule for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
Returns:
int: The precision rule.
"""
r_value = self.symbols_n_precision.get(symbol)
if r_value is None:
self._set_precision_rule(symbol)
@ -183,36 +314,125 @@ class Exchange:
def get_historical_klines(self, symbol: str, interval: str,
start_dt: datetime, end_dt: datetime = None) -> pd.DataFrame:
"""
Returns historical OHLCV data for a given symbol and interval.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
interval (str): The interval for the OHLCV data (e.g., '1d').
start_dt (datetime): The start date for fetching data.
end_dt (datetime, optional): The end date for fetching data. Defaults to the current UTC time.
Returns:
pd.DataFrame: A DataFrame containing the OHLCV data.
"""
return self._fetch_historical_klines(symbol=symbol, interval=interval,
start_dt=start_dt, end_dt=end_dt)
def get_price(self, symbol: str) -> float:
"""
Returns the current price for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
Returns:
float: The current price.
"""
return self._fetch_price(symbol)
def get_min_qty(self, symbol: str) -> float:
"""
Returns the minimum quantity for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
Returns:
float: The minimum quantity.
"""
return self._fetch_min_qty(symbol)
def get_min_notional_qty(self, symbol: str) -> float:
"""
Returns the minimum notional quantity for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
Returns:
float: The minimum notional quantity.
"""
return self._fetch_min_notional_qty(symbol)
def get_order(self, symbol: str, order_id: str) -> object:
"""
Returns an order by its ID for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
order_id (str): The ID of the order.
Returns:
object: The order details.
"""
return self._fetch_order(symbol, order_id)
def place_order(self, symbol: str, side: str, type: str, timeInForce: str,
quantity: float, price: float = None) -> Tuple[str, object]:
"""
Places an order on the exchange.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
side (str): The side of the order ('buy' or 'sell').
type (str): The type of the order ('limit' or 'market').
timeInForce (str): The time-in-force policy ('GTC', 'IOC', etc.).
quantity (float): The quantity of the order.
price (float, optional): The price of the order for limit orders.
Returns:
Tuple[str, object]: A tuple containing the result ('Success' or 'Failure') and the order details or None.
"""
result, msg = self._place_order(symbol=symbol, side=side, type=type,
timeInForce=timeInForce, quantity=quantity, price=price)
return result, msg
def _set_avail_intervals(self) -> Tuple[str, ...]:
"""
Sets the available intervals for OHLCV data.
Returns:
Tuple[str, ...]: A tuple of available intervals.
"""
return tuple(self.client.timeframes.keys())
def _set_precision_rule(self, symbol: str) -> None:
"""
Sets the precision rule for a given symbol.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
"""
market_data = self.exchange_info[symbol]
precision = market_data['precision']['amount']
self.symbols_n_precision[symbol] = precision
def _place_order(self, symbol: str, side: str, type: str, timeInForce: str, quantity: float, price: float = None) -> Tuple[str, object]:
"""
Places an order on the exchange.
Parameters:
symbol (str): The trading symbol (e.g., 'BTC/USDT').
side (str): The side of the order ('buy' or 'sell').
type (str): The type of the order ('limit' or 'market').
timeInForce (str): The time-in-force policy ('GTC', 'IOC', etc.).
quantity (float): The quantity of the order.
price (float, optional): The price of the order for limit orders.
Returns:
Tuple[str, object]: A tuple containing the result ('Success' or 'Failure') and the order details or None.
"""
def format_arg(value: float) -> float:
precision = self.symbols_n_precision.get(symbol, 8)
return float(f"{value:.{precision}f}")
@ -242,6 +462,12 @@ class Exchange:
return 'Failure', None
def get_active_trades(self) -> List[Dict[str, Union[str, float]]]:
"""
Returns a list of active trades.
Returns:
List[Dict[str, Union[str, float]]]: A list of active trades with symbol, side, quantity, and price.
"""
if self.api_key and self.api_key_secret:
try:
positions = self.client.fetch_positions()
@ -262,6 +488,12 @@ class Exchange:
return []
def get_open_orders(self) -> List[Dict[str, Union[str, float]]]:
"""
Returns a list of open orders.
Returns:
List[Dict[str, Union[str, float]]]: A list of open orders with symbol, side, quantity, and price.
"""
if self.api_key and self.api_key_secret:
try:
open_orders = self.client.fetch_open_orders()
@ -280,3 +512,41 @@ class Exchange:
return []
else:
return []
# Usage Examples
# Example 1: Initializing the Exchange class
api_keys = {
'key': 'your_api_key',
'secret': 'your_api_secret'
}
exchange = Exchange(name='Binance', api_keys=api_keys, exchange_id='binance')
# Example 2: Fetching historical data
start_date = datetime(2022, 1, 1)
end_date = datetime(2022, 6, 1)
historical_data = exchange.get_historical_klines(symbol='BTC/USDT', interval='1d',
start_dt=start_date, end_dt=end_date)
print(historical_data)
# Example 3: Fetching the current price of a symbol
current_price = exchange.get_price(symbol='BTC/USDT')
print(f"Current price of BTC/USDT: {current_price}")
# Example 4: Placing a limit buy order
order_result, order_details = exchange.place_order(symbol='BTC/USDT', side='buy', type='limit',
timeInForce='GTC', quantity=0.001, price=30000)
print(order_result, order_details)
# Example 5: Getting account balances
balances = exchange.get_balances()
print(balances)
# Example 6: Fetching open orders
open_orders = exchange.get_open_orders()
print(open_orders)
# Example 7: Fetching active trades
active_trades = exchange.get_active_trades()
print(active_trades)