diff --git a/README.md b/README.md
index eb9e13553d..f668e3d96f 100644
--- a/README.md
+++ b/README.md
@@ -24,13 +24,13 @@ We created hummingbot to promote **decentralized market-making**: enabling membe
| logo | id | name | ver | doc | status |
|:---:|:---:|:---:|:---:|:---:|:---:|
+| | ascend_ex | [AscendEx](https://ascendex.com/en/global-digital-asset-platform) | 1 | [API](https://ascendex.github.io/ascendex-pro-api/#ascendex-pro-api-documentation) |![GREEN](https://via.placeholder.com/15/008000/?text=+) |
| | beaxy | [Beaxy](https://beaxy.com/) | 2 | [API](https://beaxyapiv2trading.docs.apiary.io/) |![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
| | binance | [Binance](https://www.binance.com/) | 3 | [API](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md) |![GREEN](https://via.placeholder.com/15/008000/?text=+) |
| | binance_us | [Binance US](https://www.binance.com/) | 3 | [API](https://github.com/binance-us/binance-official-api-docs/blob/master/rest-api.md) |![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
| | binance_perpetual | [Binance Futures](https://www.binance.com/) | 1 | [API](https://binance-docs.github.io/apidocs/futures/en/) |![GREEN](https://via.placeholder.com/15/008000/?text=+) |
|| bittrex | [Bittrex Global](https://global.bittrex.com/) | 3 | [API](https://bittrex.github.io/api/v3) |![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
| | bitfinex | [Bitfinex](https://www.bitfinex.com/) | 2 | [API](https://docs.bitfinex.com/docs/introduction) |![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
-| | bitmax | [BitMax](https://bitmax.io/en/global-digital-asset-platform) | 1 | [API](https://bitmax-exchange.github.io/bitmax-pro-api/#bitmax-pro-api-documentation) |![GREEN](https://via.placeholder.com/15/008000/?text=+) |
| | blocktane | [Blocktane](https://blocktane.io/) | 2 | [API](https://blocktane.io/api) |![GREEN](https://via.placeholder.com/15/008000/?text=+) |
| | coinbase_pro | [Coinbase Pro](https://pro.coinbase.com/) | * | [API](https://docs.pro.coinbase.com/) |![GREEN](https://via.placeholder.com/15/008000/?text=+) |
| | crypto_com | [Crypto.com](https://crypto.com/exchange) | 2 | [API](https://exchange-docs.crypto.com/#introduction) |![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
diff --git a/assets/ascend_ex_logo.png b/assets/ascend_ex_logo.png
new file mode 100644
index 0000000000..c30f757cf4
Binary files /dev/null and b/assets/ascend_ex_logo.png differ
diff --git a/assets/bitmax_logo.png b/assets/bitmax_logo.png
deleted file mode 100644
index 362daeca21..0000000000
Binary files a/assets/bitmax_logo.png and /dev/null differ
diff --git a/hummingbot/client/command/config_command.py b/hummingbot/client/command/config_command.py
index 2e145380a5..30e803ce39 100644
--- a/hummingbot/client/command/config_command.py
+++ b/hummingbot/client/command/config_command.py
@@ -45,12 +45,7 @@
"send_error_logs",
"script_enabled",
"script_file_path",
- "manual_gas_price",
"ethereum_chain_name",
- "ethgasstation_gas_enabled",
- "ethgasstation_api_key",
- "ethgasstation_gas_level",
- "ethgasstation_refresh_time",
"gateway_enabled",
"gateway_cert_passphrase",
"gateway_api_host",
@@ -240,9 +235,12 @@ async def inventory_price_prompt(
exchange = config_map["exchange"].value
market = config_map["market"].value
base_asset, quote_asset = market.split("-")
- balances = await UserBalances.instance().balances(
- exchange, base_asset, quote_asset
- )
+ if global_config_map["paper_trade_enabled"].value:
+ balances = global_config_map["paper_trade_account_balance"].value
+ else:
+ balances = await UserBalances.instance().balances(
+ exchange, base_asset, quote_asset
+ )
if balances.get(base_asset) is None:
return
diff --git a/hummingbot/client/command/create_command.py b/hummingbot/client/command/create_command.py
index eb6d6e7487..4ca413be94 100644
--- a/hummingbot/client/command/create_command.py
+++ b/hummingbot/client/command/create_command.py
@@ -97,6 +97,9 @@ async def prompt_a_config(self, # type: HummingbotApplication
config: ConfigVar,
input_value=None,
assign_default=True):
+ if config.key == "inventory_price":
+ await self.inventory_price_prompt(self.strategy_config_map, input_value)
+ return
if input_value is None:
if assign_default:
self.app.set_text(parse_config_default_to_text(config))
diff --git a/hummingbot/client/command/start_command.py b/hummingbot/client/command/start_command.py
index bbb0690972..a8b516d9ec 100644
--- a/hummingbot/client/command/start_command.py
+++ b/hummingbot/client/command/start_command.py
@@ -22,7 +22,6 @@
from hummingbot.core.utils.kill_switch import KillSwitch
from typing import TYPE_CHECKING
from hummingbot.client.config.global_config_map import global_config_map
-from hummingbot.core.utils.eth_gas_station_lookup import EthGasStationLookup
from hummingbot.script.script_iterator import ScriptIterator
from hummingbot.connector.connector_status import get_connector_status, warning_messages
from hummingbot.client.config.config_var import ConfigVar
@@ -146,9 +145,6 @@ async def start_market_making(self, # type: HummingbotApplication
self.clock.add_iterator(self._script_iterator)
self._notify(f"Script ({script_file}) started.")
- if global_config_map["ethgasstation_gas_enabled"].value and settings.ethereum_gas_station_required():
- EthGasStationLookup.get_instance().start()
-
self.strategy_task: asyncio.Task = safe_ensure_future(self._run_clock(), loop=self.ev_loop)
self._notify(f"\n'{strategy_name}' strategy started.\n"
f"Run `status` command to query the progress.")
diff --git a/hummingbot/client/command/status_command.py b/hummingbot/client/command/status_command.py
index 3cf92785ad..1fb5e69344 100644
--- a/hummingbot/client/command/status_command.py
+++ b/hummingbot/client/command/status_command.py
@@ -18,7 +18,7 @@
)
from hummingbot.client.config.security import Security
from hummingbot.user.user_balances import UserBalances
-from hummingbot.client.settings import required_exchanges, ethereum_wallet_required, ethereum_gas_station_required
+from hummingbot.client.settings import required_exchanges, ethereum_wallet_required
from hummingbot.core.utils.async_utils import safe_ensure_future
from typing import TYPE_CHECKING
@@ -186,10 +186,6 @@ async def status_check_all(self, # type: HummingbotApplication
else:
self._notify(" - ETH wallet check: ETH wallet is not connected.")
- if ethereum_gas_station_required() and not global_config_map["ethgasstation_gas_enabled"].value:
- self._notify(f' - ETH gas station check: Manual gas price is fixed at '
- f'{global_config_map["manual_gas_price"].value}.')
-
loading_markets: List[ConnectorBase] = []
for market in self.markets.values():
if not market.ready:
diff --git a/hummingbot/client/command/stop_command.py b/hummingbot/client/command/stop_command.py
index bf5340f827..3413f2d90f 100644
--- a/hummingbot/client/command/stop_command.py
+++ b/hummingbot/client/command/stop_command.py
@@ -3,7 +3,6 @@
import threading
from typing import TYPE_CHECKING
from hummingbot.core.utils.async_utils import safe_ensure_future
-from hummingbot.core.utils.eth_gas_station_lookup import EthGasStationLookup
from hummingbot.core.rate_oracle.rate_oracle import RateOracle
if TYPE_CHECKING:
from hummingbot.client.hummingbot_application import HummingbotApplication
@@ -49,9 +48,6 @@ async def stop_loop(self, # type: HummingbotApplication
if RateOracle.get_instance().started:
RateOracle.get_instance().stop()
- if EthGasStationLookup.get_instance().started:
- EthGasStationLookup.get_instance().stop()
-
if self.markets_recorder is not None:
self.markets_recorder.stop()
diff --git a/hummingbot/client/config/global_config_map.py b/hummingbot/client/config/global_config_map.py
index 6ccf2eeda4..7a6c96fa23 100644
--- a/hummingbot/client/config/global_config_map.py
+++ b/hummingbot/client/config/global_config_map.py
@@ -298,32 +298,6 @@ def global_token_symbol_on_validated(value: str):
type_str="decimal",
validator=lambda v: validate_decimal(v, Decimal(0), inclusive=False),
default=50),
- "ethgasstation_gas_enabled":
- ConfigVar(key="ethgasstation_gas_enabled",
- prompt="Do you want to enable Ethereum gas station price lookup? >>> ",
- required_if=lambda: False,
- type_str="bool",
- validator=validate_bool,
- default=False),
- "ethgasstation_api_key":
- ConfigVar(key="ethgasstation_api_key",
- prompt="Enter API key for defipulse.com gas station API >>> ",
- required_if=lambda: global_config_map["ethgasstation_gas_enabled"].value,
- type_str="str"),
- "ethgasstation_gas_level":
- ConfigVar(key="ethgasstation_gas_level",
- prompt="Enter gas level you want to use for Ethereum transactions (fast, fastest, safeLow, average) "
- ">>> ",
- required_if=lambda: global_config_map["ethgasstation_gas_enabled"].value,
- type_str="str",
- validator=lambda s: None if s in {"fast", "fastest", "safeLow", "average"}
- else "Invalid gas level."),
- "ethgasstation_refresh_time":
- ConfigVar(key="ethgasstation_refresh_time",
- prompt="Enter refresh time for Ethereum gas price lookup (in seconds) >>> ",
- required_if=lambda: global_config_map["ethgasstation_gas_enabled"].value,
- type_str="int",
- default=120),
"gateway_api_host":
ConfigVar(key="gateway_api_host",
prompt=None,
diff --git a/hummingbot/client/hummingbot_application.py b/hummingbot/client/hummingbot_application.py
index 7d025e2d68..f777132f21 100644
--- a/hummingbot/client/hummingbot_application.py
+++ b/hummingbot/client/hummingbot_application.py
@@ -21,7 +21,6 @@
from hummingbot.client.errors import InvalidCommandError, ArgumentParserError
from hummingbot.client.config.global_config_map import global_config_map, using_wallet
from hummingbot.client.config.config_helpers import (
- get_erc20_token_addresses,
get_strategy_config_map,
get_connector_class,
get_eth_wallet_private_key,
@@ -194,10 +193,14 @@ def _initialize_market_assets(market_name: str, trading_pairs: List[str]) -> Lis
return market_trading_pairs
def _initialize_wallet(self, token_trading_pairs: List[str]):
+ # Todo: This function should be removed as it's currently not used by current working connectors
+
if not using_wallet():
return
- if not self.token_list:
- self.token_list = get_erc20_token_addresses()
+ # Commented this out for now since get_erc20_token_addresses uses blocking call
+
+ # if not self.token_list:
+ # self.token_list = get_erc20_token_addresses()
ethereum_wallet = global_config_map.get("ethereum_wallet").value
private_key = Security._private_keys[ethereum_wallet]
diff --git a/hummingbot/connector/connector/balancer/balancer_connector.py b/hummingbot/connector/connector/balancer/balancer_connector.py
index e1bc1e131d..179c6b2e8f 100644
--- a/hummingbot/connector/connector/balancer/balancer_connector.py
+++ b/hummingbot/connector/connector/balancer/balancer_connector.py
@@ -7,7 +7,6 @@
import time
import ssl
import copy
-import itertools as it
from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL
from hummingbot.core.utils import async_ttl_cache
from hummingbot.core.network_iterator import NetworkStatus
@@ -31,9 +30,9 @@
from hummingbot.connector.connector_base import ConnectorBase
from hummingbot.connector.connector.balancer.balancer_in_flight_order import BalancerInFlightOrder
from hummingbot.client.settings import GATEAWAY_CA_CERT_PATH, GATEAWAY_CLIENT_CERT_PATH, GATEAWAY_CLIENT_KEY_PATH
-from hummingbot.core.utils.eth_gas_station_lookup import get_gas_price
from hummingbot.client.config.global_config_map import global_config_map
-from hummingbot.client.config.config_helpers import get_erc20_token_addresses
+from hummingbot.core.utils.ethereum import check_transaction_exceptions, fetch_trading_pairs
+from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map
s_logger = None
s_decimal_0 = Decimal("0")
@@ -71,12 +70,9 @@ def __init__(self,
"""
super().__init__()
self._trading_pairs = trading_pairs
- tokens = set()
+ self._tokens = set()
for trading_pair in trading_pairs:
- tokens.update(set(trading_pair.split("-")))
- self._erc_20_token_list = self.token_list()
- self._token_addresses = {t: l[0] for t, l in self._erc_20_token_list.items() if t in tokens}
- self._token_decimals = {t: l[1] for t, l in self._erc_20_token_list.items() if t in tokens}
+ self._tokens.update(set(trading_pair.split("-")))
self._wallet_private_key = wallet_private_key
self._ethereum_rpc_url = ethereum_rpc_url
self._trading_required = trading_required
@@ -84,10 +80,13 @@ def __init__(self,
self._shared_client = None
self._last_poll_timestamp = 0.0
self._last_balance_poll_timestamp = time.time()
+ self._last_est_gas_cost_reported = 0
self._in_flight_orders = {}
self._allowances = {}
self._status_polling_task = None
self._auto_approve_task = None
+ self._initiate_pool_task = None
+ self._initiate_pool_status = None
self._real_time_balance_update = False
self._max_swaps = global_config_map['balancer_max_swaps'].value
self._poll_notifier = None
@@ -96,17 +95,9 @@ def __init__(self,
def name(self):
return "balancer"
- @staticmethod
- def token_list():
- return get_erc20_token_addresses()
-
@staticmethod
async def fetch_trading_pairs() -> List[str]:
- token_list = BalancerConnector.token_list()
- trading_pairs = []
- for base, quote in it.permutations(token_list.keys(), 2):
- trading_pairs.append(f"{base}-{quote}")
- return trading_pairs
+ return await fetch_trading_pairs()
@property
def limit_orders(self) -> List[LimitOrder]:
@@ -115,6 +106,26 @@ def limit_orders(self) -> List[LimitOrder]:
for in_flight_order in self._in_flight_orders.values()
]
+ async def initiate_pool(self) -> str:
+ """
+ Initiate connector and cache pools
+ """
+ try:
+ self.logger().info(f"Initializing Balancer connector and caching pools for {self._trading_pairs}.")
+ resp = await self._api_request("get", "eth/balancer/start",
+ {"pairs": json.dumps(self._trading_pairs)})
+ status = bool(str(resp["success"]))
+ if bool(str(resp["success"])):
+ self._initiate_pool_status = status
+ except asyncio.CancelledError:
+ raise
+ except Exception as e:
+ self.logger().network(
+ f"Error initializing {self._trading_pairs} swap pools",
+ exc_info=True,
+ app_warning_msg=str(e)
+ )
+
async def auto_approve(self):
"""
Automatically approves Balancer contract as a spender for token in trading pairs.
@@ -138,9 +149,7 @@ async def approve_balancer_spender(self, token_symbol: str) -> Decimal:
"""
resp = await self._api_request("post",
"eth/approve",
- {"tokenAddress": self._token_addresses[token_symbol],
- "gasPrice": str(get_gas_price()),
- "decimals": self._token_decimals[token_symbol], # if not supplied, gateway would treat it eth-like with 18 decimals
+ {"token": token_symbol,
"connector": self.name})
amount_approved = Decimal(str(resp["amount"]))
if amount_approved > 0:
@@ -155,12 +164,11 @@ async def get_allowances(self) -> Dict[str, Decimal]:
:return: A dictionary of token and its allowance (how much Balancer can spend).
"""
ret_val = {}
- resp = await self._api_request("post", "eth/allowances-2",
- {"tokenAddressList": ("".join([tok + "," for tok in self._token_addresses.values()])).rstrip(","),
- "tokenDecimalList": ("".join([str(dec) + "," for dec in self._token_decimals.values()])).rstrip(","),
+ resp = await self._api_request("post", "eth/allowances",
+ {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]",
"connector": self.name})
- for address, amount in resp["approvals"].items():
- ret_val[self.get_token(address)] = Decimal(str(amount))
+ for token, amount in resp["approvals"].items():
+ ret_val[token] = Decimal(str(amount))
return ret_val
@async_ttl_cache(ttl=5, maxsize=10)
@@ -177,15 +185,44 @@ async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal
base, quote = trading_pair.split("-")
side = "buy" if is_buy else "sell"
resp = await self._api_request("post",
- f"balancer/{side}-price",
- {"base": self._token_addresses[base],
- "quote": self._token_addresses[quote],
+ "eth/balancer/price",
+ {"base": base,
+ "quote": quote,
"amount": amount,
- "base_decimals": self._token_decimals[base],
- "quote_decimals": self._token_decimals[quote],
- "maxSwaps": self._max_swaps})
- if resp["price"] is not None:
- return Decimal(str(resp["price"]))
+ "side": side.upper()})
+ required_items = ["price", "gasLimit", "gasPrice", "gasCost"]
+ if any(item not in resp.keys() for item in required_items):
+ if "info" in resp.keys():
+ self.logger().info(f"Unable to get price. {resp['info']}")
+ else:
+ self.logger().info(f"Missing data from price result. Incomplete return result for ({resp.keys()})")
+ else:
+ gas_limit = resp["gasLimit"]
+ gas_price = resp["gasPrice"]
+ gas_cost = resp["gasCost"]
+ price = resp["price"]
+ account_standing = {
+ "allowances": self._allowances,
+ "balances": self._account_balances,
+ "base": base,
+ "quote": quote,
+ "amount": amount,
+ "side": side,
+ "gas_limit": gas_limit,
+ "gas_price": gas_price,
+ "gas_cost": gas_cost,
+ "price": price,
+ "swaps": len(resp["swaps"])
+ }
+ exceptions = check_transaction_exceptions(account_standing)
+ for index in range(len(exceptions)):
+ self.logger().info(f"Warning! [{index+1}/{len(exceptions)}] {side} order - {exceptions[index]}")
+
+ if price is not None and len(exceptions) == 0:
+ # TODO standardize quote price object to include price, fee, token, is fee part of quote.
+ fee_overrides_config_map["balancer_maker_fee_amount"].value = Decimal(str(gas_cost))
+ fee_overrides_config_map["balancer_taker_fee_amount"].value = Decimal(str(gas_cost))
+ return Decimal(str(price))
except asyncio.CancelledError:
raise
except Exception as e:
@@ -255,24 +292,28 @@ async def _create_order(self,
amount = self.quantize_order_amount(trading_pair, amount)
price = self.quantize_order_price(trading_pair, price)
base, quote = trading_pair.split("-")
- gas_price = get_gas_price()
- api_params = {"base": self._token_addresses[base],
- "quote": self._token_addresses[quote],
+ api_params = {"base": base,
+ "quote": quote,
+ "side": trade_type.name.upper(),
"amount": str(amount),
- "maxPrice": str(price),
- "maxSwaps": str(self._max_swaps),
- "gasPrice": str(gas_price),
- "base_decimals": self._token_decimals[base],
- "quote_decimals": self._token_decimals[quote],
+ "limitPrice": str(price),
}
- self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, gas_price)
try:
- order_result = await self._api_request("post", f"balancer/{trade_type.name.lower()}", api_params)
+ order_result = await self._api_request("post", "eth/balancer/trade", api_params)
hash = order_result.get("txHash")
+ gas_price = order_result.get("gasPrice")
+ gas_limit = order_result.get("gasLimit")
+ gas_cost = order_result.get("gasCost")
+ self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, gas_price)
tracked_order = self._in_flight_orders.get(order_id)
+
+ # update onchain balance
+ await self._update_balances()
+
if tracked_order is not None:
self.logger().info(f"Created {trade_type.name} order {order_id} txHash: {hash} "
- f"for {amount} {trading_pair}.")
+ f"for {amount} {trading_pair}. Estimated Gas Cost: {gas_cost} ETH "
+ f" (gas limit: {gas_limit}, gas price: {gas_price})")
tracked_order.update_exchange_order_id(hash)
tracked_order.gas_price = gas_price
if hash is not None:
@@ -340,7 +381,7 @@ async def _update_order_status(self):
for tracked_order in tracked_orders:
order_id = await tracked_order.get_exchange_order_id()
tasks.append(self._api_request("post",
- "eth/get-receipt",
+ "eth/poll",
{"txHash": order_id}))
update_results = await safe_gather(*tasks, return_exceptions=True)
for update_result in update_results:
@@ -416,7 +457,7 @@ def has_allowances(self) -> bool:
"""
Checks if all tokens have allowance (an amount approved)
"""
- return len(self._allowances.values()) == len(self._token_addresses.values()) and \
+ return len(self._allowances.values()) == len(self._tokens) and \
all(amount > s_decimal_0 for amount in self._allowances.values())
@property
@@ -429,6 +470,7 @@ def status_dict(self) -> Dict[str, bool]:
async def start_network(self):
if self._trading_required:
self._status_polling_task = safe_ensure_future(self._status_polling_loop())
+ self._initiate_pool_task = safe_ensure_future(self.initiate_pool())
self._auto_approve_task = safe_ensure_future(self.auto_approve())
async def stop_network(self):
@@ -438,6 +480,9 @@ async def stop_network(self):
if self._auto_approve_task is not None:
self._auto_approve_task.cancel()
self._auto_approve_task = None
+ if self._initiate_pool_task is not None:
+ self._initiate_pool_task.cancel()
+ self._initiate_pool_task = None
async def check_network(self) -> NetworkStatus:
try:
@@ -478,9 +523,6 @@ async def _status_polling_loop(self):
app_warning_msg="Could not fetch balances from Gateway API.")
await asyncio.sleep(0.5)
- def get_token(self, token_address: str) -> str:
- return [k for k, v in self._token_addresses.items() if v == token_address][0]
-
async def _update_balances(self, on_interval = False):
"""
Calls Eth API to update total and available balances.
@@ -492,12 +534,10 @@ async def _update_balances(self, on_interval = False):
local_asset_names = set(self._account_balances.keys())
remote_asset_names = set()
resp_json = await self._api_request("post",
- "eth/balances-2",
- {"tokenAddressList": ("".join([tok + "," for tok in self._token_addresses.values()])).rstrip(","),
- "tokenDecimalList": ("".join([str(dec) + "," for dec in self._token_decimals.values()])).rstrip(",")})
+ "eth/balances",
+ {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]"})
+
for token, bal in resp_json["balances"].items():
- if len(token) > 4:
- token = self.get_token(token)
self._account_available_balances[token] = Decimal(str(bal))
self._account_balances[token] = Decimal(str(bal))
remote_asset_names.add(token)
@@ -554,7 +594,7 @@ async def _api_request(self,
err_msg = f" Message: {parsed_response['error']}"
raise IOError(f"Error fetching data from {url}. HTTP status is {response.status}.{err_msg}")
if "error" in parsed_response:
- raise Exception(f"Error: {parsed_response['error']}")
+ raise Exception(f"Error: {parsed_response['error']} {parsed_response['message']}")
return parsed_response
diff --git a/hummingbot/connector/connector/terra/terra_connector.py b/hummingbot/connector/connector/terra/terra_connector.py
index 1836750621..0f27b3e0ed 100644
--- a/hummingbot/connector/connector/terra/terra_connector.py
+++ b/hummingbot/connector/connector/terra/terra_connector.py
@@ -107,7 +107,7 @@ async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal
base, quote = trading_pair.split("-")
side = "buy" if is_buy else "sell"
- resp = await self._api_request("post", "terra/price", {"base": base, "quote": quote, "trade_type": side,
+ resp = await self._api_request("post", "terra/price", {"base": base, "quote": quote, "side": side,
"amount": str(amount)})
txFee = resp["txFee"] / float(amount)
price_with_txfee = resp["price"] + txFee if is_buy else resp["price"] - txFee
@@ -185,9 +185,9 @@ async def _create_order(self,
base, quote = trading_pair.split("-")
api_params = {"base": base,
"quote": quote,
- "trade_type": "buy" if trade_type is TradeType.BUY else "sell",
+ "side": "buy" if trade_type is TradeType.BUY else "sell",
"amount": str(amount),
- "secret": self._terra_wallet_seeds,
+ "privateKey": self._terra_wallet_seeds,
# "maxPrice": str(price),
}
self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount)
diff --git a/hummingbot/connector/connector/uniswap/uniswap_connector.py b/hummingbot/connector/connector/uniswap/uniswap_connector.py
index 2fef254945..c8277b65b1 100644
--- a/hummingbot/connector/connector/uniswap/uniswap_connector.py
+++ b/hummingbot/connector/connector/uniswap/uniswap_connector.py
@@ -7,7 +7,6 @@
import time
import ssl
import copy
-import itertools as it
from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL
from hummingbot.core.utils import async_ttl_cache
from hummingbot.core.network_iterator import NetworkStatus
@@ -31,9 +30,9 @@
from hummingbot.connector.connector_base import ConnectorBase
from hummingbot.connector.connector.uniswap.uniswap_in_flight_order import UniswapInFlightOrder
from hummingbot.client.settings import GATEAWAY_CA_CERT_PATH, GATEAWAY_CLIENT_CERT_PATH, GATEAWAY_CLIENT_KEY_PATH
-from hummingbot.core.utils.eth_gas_station_lookup import get_gas_price
from hummingbot.client.config.global_config_map import global_config_map
-from hummingbot.client.config.config_helpers import get_erc20_token_addresses
+from hummingbot.core.utils.ethereum import check_transaction_exceptions, fetch_trading_pairs
+from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map
s_logger = None
s_decimal_0 = Decimal("0")
@@ -71,12 +70,9 @@ def __init__(self,
"""
super().__init__()
self._trading_pairs = trading_pairs
- tokens = set()
+ self._tokens = set()
for trading_pair in trading_pairs:
- tokens.update(set(trading_pair.split("-")))
- self._erc_20_token_list = self.token_list()
- self._token_addresses = {t: l[0] for t, l in self._erc_20_token_list.items() if t in tokens}
- self._token_decimals = {t: l[1] for t, l in self._erc_20_token_list.items() if t in tokens}
+ self._tokens.update(set(trading_pair.split("-")))
self._wallet_private_key = wallet_private_key
self._ethereum_rpc_url = ethereum_rpc_url
self._trading_required = trading_required
@@ -84,10 +80,13 @@ def __init__(self,
self._shared_client = None
self._last_poll_timestamp = 0.0
self._last_balance_poll_timestamp = time.time()
+ self._last_est_gas_cost_reported = 0
self._in_flight_orders = {}
self._allowances = {}
self._status_polling_task = None
self._auto_approve_task = None
+ self._initiate_pool_task = None
+ self._initiate_pool_status = None
self._real_time_balance_update = False
self._poll_notifier = None
@@ -95,17 +94,9 @@ def __init__(self,
def name(self):
return "uniswap"
- @staticmethod
- def token_list():
- return get_erc20_token_addresses()
-
@staticmethod
async def fetch_trading_pairs() -> List[str]:
- token_list = UniswapConnector.token_list()
- trading_pairs = []
- for base, quote in it.permutations(token_list.keys(), 2):
- trading_pairs.append(f"{base}-{quote}")
- return trading_pairs
+ return await fetch_trading_pairs()
@property
def limit_orders(self) -> List[LimitOrder]:
@@ -114,6 +105,26 @@ def limit_orders(self) -> List[LimitOrder]:
for in_flight_order in self._in_flight_orders.values()
]
+ async def initiate_pool(self) -> str:
+ """
+ Initiate connector and start caching paths for trading_pairs
+ """
+ try:
+ self.logger().info(f"Initializing Uniswap connector and paths for {self._trading_pairs} pairs.")
+ resp = await self._api_request("get", "eth/uniswap/start",
+ {"pairs": json.dumps(self._trading_pairs)})
+ status = bool(str(resp["success"]))
+ if bool(str(resp["success"])):
+ self._initiate_pool_status = status
+ except asyncio.CancelledError:
+ raise
+ except Exception as e:
+ self.logger().network(
+ f"Error initializing {self._trading_pairs} ",
+ exc_info=True,
+ app_warning_msg=str(e)
+ )
+
async def auto_approve(self):
"""
Automatically approves Uniswap contract as a spender for token in trading pairs.
@@ -137,9 +148,7 @@ async def approve_uniswap_spender(self, token_symbol: str) -> Decimal:
"""
resp = await self._api_request("post",
"eth/approve",
- {"tokenAddress": self._token_addresses[token_symbol],
- "gasPrice": str(get_gas_price()),
- "decimals": self._token_decimals[token_symbol], # if not supplied, gateway would treat it eth-like with 18 decimals
+ {"token": token_symbol,
"connector": self.name})
amount_approved = Decimal(str(resp["amount"]))
if amount_approved > 0:
@@ -154,12 +163,11 @@ async def get_allowances(self) -> Dict[str, Decimal]:
:return: A dictionary of token and its allowance (how much Uniswap can spend).
"""
ret_val = {}
- resp = await self._api_request("post", "eth/allowances-2",
- {"tokenAddressList": ("".join([tok + "," for tok in self._token_addresses.values()])).rstrip(","),
- "tokenDecimalList": ("".join([str(dec) + "," for dec in self._token_decimals.values()])).rstrip(","),
+ resp = await self._api_request("post", "eth/allowances",
+ {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]",
"connector": self.name})
- for address, amount in resp["approvals"].items():
- ret_val[self.get_token(address)] = Decimal(str(amount))
+ for token, amount in resp["approvals"].items():
+ ret_val[token] = Decimal(str(amount))
return ret_val
@async_ttl_cache(ttl=5, maxsize=10)
@@ -176,12 +184,43 @@ async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal
base, quote = trading_pair.split("-")
side = "buy" if is_buy else "sell"
resp = await self._api_request("post",
- f"uniswap/{side}-price",
- {"base": self._token_addresses[base],
- "quote": self._token_addresses[quote],
+ "eth/uniswap/price",
+ {"base": base,
+ "quote": quote,
+ "side": side.upper(),
"amount": amount})
- if resp["price"] is not None:
- return Decimal(str(resp["price"]))
+ required_items = ["price", "gasLimit", "gasPrice", "gasCost"]
+ if any(item not in resp.keys() for item in required_items):
+ if "info" in resp.keys():
+ self.logger().info(f"Unable to get price. {resp['info']}")
+ else:
+ self.logger().info(f"Missing data from price result. Incomplete return result for ({resp.keys()})")
+ else:
+ gas_limit = resp["gasLimit"]
+ gas_price = resp["gasPrice"]
+ gas_cost = resp["gasCost"]
+ price = resp["price"]
+ account_standing = {
+ "allowances": self._allowances,
+ "balances": self._account_balances,
+ "base": base,
+ "quote": quote,
+ "amount": amount,
+ "side": side,
+ "gas_limit": gas_limit,
+ "gas_price": gas_price,
+ "gas_cost": gas_cost,
+ "price": price
+ }
+ exceptions = check_transaction_exceptions(account_standing)
+ for index in range(len(exceptions)):
+ self.logger().info(f"Warning! [{index+1}/{len(exceptions)}] {side} order - {exceptions[index]}")
+
+ if price is not None and len(exceptions) == 0:
+ # TODO standardize quote price object to include price, fee, token, is fee part of quote.
+ fee_overrides_config_map["uniswap_maker_fee_amount"].value = Decimal(str(gas_cost))
+ fee_overrides_config_map["uniswap_taker_fee_amount"].value = Decimal(str(gas_cost))
+ return Decimal(str(price))
except asyncio.CancelledError:
raise
except Exception as e:
@@ -251,21 +290,24 @@ async def _create_order(self,
amount = self.quantize_order_amount(trading_pair, amount)
price = self.quantize_order_price(trading_pair, price)
base, quote = trading_pair.split("-")
- gas_price = get_gas_price()
- api_params = {"base": self._token_addresses[base],
- "quote": self._token_addresses[quote],
+ api_params = {"base": base,
+ "quote": quote,
+ "side": trade_type.name.upper(),
"amount": str(amount),
- "maxPrice": str(price),
- "gasPrice": str(gas_price),
+ "limitPrice": str(price),
}
- self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, gas_price)
try:
- order_result = await self._api_request("post", f"uniswap/{trade_type.name.lower()}", api_params)
+ order_result = await self._api_request("post", "eth/uniswap/trade", api_params)
hash = order_result.get("txHash")
+ gas_price = order_result.get("gasPrice")
+ gas_limit = order_result.get("gasLimit")
+ gas_cost = order_result.get("gasCost")
+ self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, gas_price)
tracked_order = self._in_flight_orders.get(order_id)
if tracked_order is not None:
self.logger().info(f"Created {trade_type.name} order {order_id} txHash: {hash} "
- f"for {amount} {trading_pair}.")
+ f"for {amount} {trading_pair}. Estimated Gas Cost: {gas_cost} ETH "
+ f" (gas limit: {gas_limit}, gas price: {gas_price})")
tracked_order.update_exchange_order_id(hash)
tracked_order.gas_price = gas_price
if hash is not None:
@@ -333,7 +375,7 @@ async def _update_order_status(self):
for tracked_order in tracked_orders:
order_id = await tracked_order.get_exchange_order_id()
tasks.append(self._api_request("post",
- "eth/get-receipt",
+ "eth/poll",
{"txHash": order_id}))
update_results = await safe_gather(*tasks, return_exceptions=True)
for update_result in update_results:
@@ -409,7 +451,7 @@ def has_allowances(self) -> bool:
"""
Checks if all tokens have allowance (an amount approved)
"""
- return len(self._allowances.values()) == len(self._token_addresses.values()) and \
+ return len(self._allowances.values()) == len(self._tokens) and \
all(amount > s_decimal_0 for amount in self._allowances.values())
@property
@@ -422,6 +464,7 @@ def status_dict(self) -> Dict[str, bool]:
async def start_network(self):
if self._trading_required:
self._status_polling_task = safe_ensure_future(self._status_polling_loop())
+ self._initiate_pool_task = safe_ensure_future(self.initiate_pool())
self._auto_approve_task = safe_ensure_future(self.auto_approve())
async def stop_network(self):
@@ -431,6 +474,9 @@ async def stop_network(self):
if self._auto_approve_task is not None:
self._auto_approve_task.cancel()
self._auto_approve_task = None
+ if self._initiate_pool_task is not None:
+ self._initiate_pool_task.cancel()
+ self._initiate_pool_task = None
async def check_network(self) -> NetworkStatus:
try:
@@ -458,7 +504,7 @@ async def _status_polling_loop(self):
self._poll_notifier = asyncio.Event()
await self._poll_notifier.wait()
await safe_gather(
- self._update_balances(),
+ self._update_balances(on_interval=True),
self._update_order_status(),
)
self._last_poll_timestamp = self.current_timestamp
@@ -471,37 +517,32 @@ async def _status_polling_loop(self):
app_warning_msg="Could not fetch balances from Gateway API.")
await asyncio.sleep(0.5)
- def get_token(self, token_address: str) -> str:
- return [k for k, v in self._token_addresses.items() if v == token_address][0]
-
- async def _update_balances(self):
+ async def _update_balances(self, on_interval = False):
"""
Calls Eth API to update total and available balances.
"""
last_tick = self._last_balance_poll_timestamp
current_tick = self.current_timestamp
- if (current_tick - last_tick) > self.UPDATE_BALANCE_INTERVAL:
+ if not on_interval or (current_tick - last_tick) > self.UPDATE_BALANCE_INTERVAL:
self._last_balance_poll_timestamp = current_tick
- local_asset_names = set(self._account_balances.keys())
- remote_asset_names = set()
- resp_json = await self._api_request("post",
- "eth/balances-2",
- {"tokenAddressList": ("".join([tok + "," for tok in self._token_addresses.values()])).rstrip(","),
- "tokenDecimalList": ("".join([str(dec) + "," for dec in self._token_decimals.values()])).rstrip(",")})
- for token, bal in resp_json["balances"].items():
- if len(token) > 4:
- token = self.get_token(token)
- self._account_available_balances[token] = Decimal(str(bal))
- self._account_balances[token] = Decimal(str(bal))
- remote_asset_names.add(token)
-
- asset_names_to_remove = local_asset_names.difference(remote_asset_names)
- for asset_name in asset_names_to_remove:
- del self._account_available_balances[asset_name]
- del self._account_balances[asset_name]
-
- self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self._in_flight_orders.items()}
- self._in_flight_orders_snapshot_timestamp = self.current_timestamp
+ local_asset_names = set(self._account_balances.keys())
+ remote_asset_names = set()
+ resp_json = await self._api_request("post",
+ "eth/balances",
+ {"tokenList": "[" + (",".join(['"' + t + '"' for t in self._tokens])) + "]"})
+
+ for token, bal in resp_json["balances"].items():
+ self._account_available_balances[token] = Decimal(str(bal))
+ self._account_balances[token] = Decimal(str(bal))
+ remote_asset_names.add(token)
+
+ asset_names_to_remove = local_asset_names.difference(remote_asset_names)
+ for asset_name in asset_names_to_remove:
+ del self._account_available_balances[asset_name]
+ del self._account_balances[asset_name]
+
+ self._in_flight_orders_snapshot = {k: copy.copy(v) for k, v in self._in_flight_orders.items()}
+ self._in_flight_orders_snapshot_timestamp = self.current_timestamp
async def _http_client(self) -> aiohttp.ClientSession:
"""
@@ -547,7 +588,7 @@ async def _api_request(self,
err_msg = f" Message: {parsed_response['error']}"
raise IOError(f"Error fetching data from {url}. HTTP status is {response.status}.{err_msg}")
if "error" in parsed_response:
- raise Exception(f"Error: {parsed_response['error']}")
+ raise Exception(f"Error: {parsed_response['error']} {parsed_response['message']}")
return parsed_response
diff --git a/hummingbot/connector/connector_status.py b/hummingbot/connector/connector_status.py
index 2f35999bb7..14a0968094 100644
--- a/hummingbot/connector/connector_status.py
+++ b/hummingbot/connector/connector_status.py
@@ -8,7 +8,7 @@
'binance_perpetual_testnet': 'green',
'binance_us': 'yellow',
'bitfinex': 'yellow',
- 'bitmax': 'green',
+ 'ascend_ex': 'green',
'bittrex': 'yellow',
'blocktane': 'green',
'celo': 'green',
diff --git a/hummingbot/connector/exchange/bitmax/__init__.py b/hummingbot/connector/exchange/ascend_ex/__init__.py
similarity index 100%
rename from hummingbot/connector/exchange/bitmax/__init__.py
rename to hummingbot/connector/exchange/ascend_ex/__init__.py
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_active_order_tracker.pxd b/hummingbot/connector/exchange/ascend_ex/ascend_ex_active_order_tracker.pxd
similarity index 90%
rename from hummingbot/connector/exchange/bitmax/bitmax_active_order_tracker.pxd
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_active_order_tracker.pxd
index fbc1eb3080..833d9864a0 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_active_order_tracker.pxd
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_active_order_tracker.pxd
@@ -1,7 +1,7 @@
# distutils: language=c++
cimport numpy as np
-cdef class BitmaxActiveOrderTracker:
+cdef class AscendExActiveOrderTracker:
cdef dict _active_bids
cdef dict _active_asks
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_active_order_tracker.pyx b/hummingbot/connector/exchange/ascend_ex/ascend_ex_active_order_tracker.pyx
similarity index 93%
rename from hummingbot/connector/exchange/bitmax/bitmax_active_order_tracker.pyx
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_active_order_tracker.pyx
index 092a97c45c..b80930b8b2 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_active_order_tracker.pyx
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_active_order_tracker.pyx
@@ -12,12 +12,12 @@ from hummingbot.core.data_type.order_book_row import OrderBookRow
_logger = None
s_empty_diff = np.ndarray(shape=(0, 4), dtype="float64")
-BitmaxOrderBookTrackingDictionary = Dict[Decimal, Dict[str, Dict[str, any]]]
+AscendExOrderBookTrackingDictionary = Dict[Decimal, Dict[str, Dict[str, any]]]
-cdef class BitmaxActiveOrderTracker:
+cdef class AscendExActiveOrderTracker:
def __init__(self,
- active_asks: BitmaxOrderBookTrackingDictionary = None,
- active_bids: BitmaxOrderBookTrackingDictionary = None):
+ active_asks: AscendExOrderBookTrackingDictionary = None,
+ active_bids: AscendExOrderBookTrackingDictionary = None):
super().__init__()
self._active_asks = active_asks or {}
self._active_bids = active_bids or {}
@@ -30,11 +30,11 @@ cdef class BitmaxActiveOrderTracker:
return _logger
@property
- def active_asks(self) -> BitmaxOrderBookTrackingDictionary:
+ def active_asks(self) -> AscendExOrderBookTrackingDictionary:
return self._active_asks
@property
- def active_bids(self) -> BitmaxOrderBookTrackingDictionary:
+ def active_bids(self) -> AscendExOrderBookTrackingDictionary:
return self._active_bids
# TODO: research this more
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_api_order_book_data_source.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py
similarity index 91%
rename from hummingbot/connector/exchange/bitmax/bitmax_api_order_book_data_source.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py
index 306b0631c4..0c64a9c1c4 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_api_order_book_data_source.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_order_book_data_source.py
@@ -13,13 +13,13 @@
from hummingbot.core.data_type.order_book_tracker_data_source import OrderBookTrackerDataSource
from hummingbot.core.utils.async_utils import safe_gather
from hummingbot.logger import HummingbotLogger
-from hummingbot.connector.exchange.bitmax.bitmax_active_order_tracker import BitmaxActiveOrderTracker
-from hummingbot.connector.exchange.bitmax.bitmax_order_book import BitmaxOrderBook
-from hummingbot.connector.exchange.bitmax.bitmax_utils import convert_from_exchange_trading_pair, convert_to_exchange_trading_pair
-from hummingbot.connector.exchange.bitmax.bitmax_constants import EXCHANGE_NAME, REST_URL, WS_URL, PONG_PAYLOAD
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_active_order_tracker import AscendExActiveOrderTracker
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_order_book import AscendExOrderBook
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_utils import convert_from_exchange_trading_pair, convert_to_exchange_trading_pair
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_constants import EXCHANGE_NAME, REST_URL, WS_URL, PONG_PAYLOAD
-class BitmaxAPIOrderBookDataSource(OrderBookTrackerDataSource):
+class AscendExAPIOrderBookDataSource(OrderBookTrackerDataSource):
MAX_RETRIES = 20
MESSAGE_TIMEOUT = 30.0
SNAPSHOT_TIMEOUT = 10.0
@@ -105,13 +105,13 @@ async def get_order_book_data(trading_pair: str) -> Dict[str, any]:
async def get_new_order_book(self, trading_pair: str) -> OrderBook:
snapshot: Dict[str, Any] = await self.get_order_book_data(trading_pair)
snapshot_timestamp: float = snapshot.get("data").get("ts")
- snapshot_msg: OrderBookMessage = BitmaxOrderBook.snapshot_message_from_exchange(
+ snapshot_msg: OrderBookMessage = AscendExOrderBook.snapshot_message_from_exchange(
snapshot.get("data"),
snapshot_timestamp,
metadata={"trading_pair": trading_pair}
)
order_book = self.order_book_create_function()
- active_order_tracker: BitmaxActiveOrderTracker = BitmaxActiveOrderTracker()
+ active_order_tracker: AscendExActiveOrderTracker = AscendExActiveOrderTracker()
bids, asks = active_order_tracker.convert_snapshot_message_to_order_book_row(snapshot_msg)
order_book.apply_snapshot(bids, asks, snapshot_msg.update_id)
return order_book
@@ -141,7 +141,7 @@ async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop, output: asynci
for trade in msg.get("data"):
trade_timestamp: int = trade.get("ts")
- trade_msg: OrderBookMessage = BitmaxOrderBook.trade_message_from_exchange(
+ trade_msg: OrderBookMessage = AscendExOrderBook.trade_message_from_exchange(
trade,
trade_timestamp,
metadata={"trading_pair": trading_pair}
@@ -181,7 +181,7 @@ async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, outp
msg_timestamp: int = msg.get("data").get("ts")
trading_pair: str = convert_from_exchange_trading_pair(msg.get("symbol"))
- order_book_message: OrderBookMessage = BitmaxOrderBook.diff_message_from_exchange(
+ order_book_message: OrderBookMessage = AscendExOrderBook.diff_message_from_exchange(
msg.get("data"),
msg_timestamp,
metadata={"trading_pair": trading_pair}
@@ -207,7 +207,7 @@ async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop,
try:
snapshot: Dict[str, any] = await self.get_order_book_data(trading_pair)
snapshot_timestamp: float = snapshot.get("data").get("ts")
- snapshot_msg: OrderBookMessage = BitmaxOrderBook.snapshot_message_from_exchange(
+ snapshot_msg: OrderBookMessage = AscendExOrderBook.snapshot_message_from_exchange(
snapshot.get("data"),
snapshot_timestamp,
metadata={"trading_pair": trading_pair}
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_api_user_stream_data_source.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py
similarity index 79%
rename from hummingbot/connector/exchange/bitmax/bitmax_api_user_stream_data_source.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py
index 10bbac6763..19571d90c8 100755
--- a/hummingbot/connector/exchange/bitmax/bitmax_api_user_stream_data_source.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_api_user_stream_data_source.py
@@ -9,11 +9,12 @@
from typing import Optional, List, AsyncIterable, Any
from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource
from hummingbot.logger import HummingbotLogger
-from hummingbot.connector.exchange.bitmax.bitmax_auth import BitmaxAuth
-from hummingbot.connector.exchange.bitmax.bitmax_constants import REST_URL, getWsUrlPriv, PONG_PAYLOAD
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_constants import REST_URL, PONG_PAYLOAD
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_utils import get_ws_url_private
-class BitmaxAPIUserStreamDataSource(UserStreamTrackerDataSource):
+class AscendExAPIUserStreamDataSource(UserStreamTrackerDataSource):
MAX_RETRIES = 20
MESSAGE_TIMEOUT = 10.0
PING_TIMEOUT = 5.0
@@ -26,8 +27,8 @@ def logger(cls) -> HummingbotLogger:
cls._logger = logging.getLogger(__name__)
return cls._logger
- def __init__(self, bitmax_auth: BitmaxAuth, trading_pairs: Optional[List[str]] = []):
- self._bitmax_auth: BitmaxAuth = bitmax_auth
+ def __init__(self, ascend_ex_auth: AscendExAuth, trading_pairs: Optional[List[str]] = []):
+ self._ascend_ex_auth: AscendExAuth = ascend_ex_auth
self._trading_pairs = trading_pairs
self._current_listen_key = None
self._listen_for_user_stream_task = None
@@ -50,18 +51,18 @@ async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: a
while True:
try:
response = await aiohttp.ClientSession().get(f"{REST_URL}/info", headers={
- **self._bitmax_auth.get_headers(),
- **self._bitmax_auth.get_auth_headers("info"),
+ **self._ascend_ex_auth.get_headers(),
+ **self._ascend_ex_auth.get_auth_headers("info"),
})
info = await response.json()
accountGroup = info.get("data").get("accountGroup")
- headers = self._bitmax_auth.get_auth_headers("stream")
+ headers = self._ascend_ex_auth.get_auth_headers("stream")
payload = {
"op": "sub",
"ch": "order:cash"
}
- async with websockets.connect(f"{getWsUrlPriv(accountGroup)}/stream", extra_headers=headers) as ws:
+ async with websockets.connect(f"{get_ws_url_private(accountGroup)}/stream", extra_headers=headers) as ws:
try:
ws: websockets.WebSocketClientProtocol = ws
await ws.send(ujson.dumps(payload))
@@ -75,12 +76,12 @@ async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: a
output.put_nowait(msg)
except Exception:
self.logger().error(
- "Unexpected error when parsing Bitmax message. ", exc_info=True
+ "Unexpected error when parsing AscendEx message. ", exc_info=True
)
raise
except Exception:
self.logger().error(
- "Unexpected error while listening to Bitmax messages. ", exc_info=True
+ "Unexpected error while listening to AscendEx messages. ", exc_info=True
)
raise
finally:
@@ -89,7 +90,7 @@ async def listen_for_user_stream(self, ev_loop: asyncio.BaseEventLoop, output: a
raise
except Exception:
self.logger().error(
- "Unexpected error with Bitmax WebSocket connection. " "Retrying after 30 seconds...", exc_info=True
+ "Unexpected error with AscendEx WebSocket connection. " "Retrying after 30 seconds...", exc_info=True
)
await asyncio.sleep(30.0)
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_auth.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_auth.py
similarity index 79%
rename from hummingbot/connector/exchange/bitmax/bitmax_auth.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_auth.py
index 646fb13a8a..23b2c78f67 100755
--- a/hummingbot/connector/exchange/bitmax/bitmax_auth.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_auth.py
@@ -1,13 +1,13 @@
import hmac
import hashlib
from typing import Dict, Any
-from hummingbot.connector.exchange.bitmax.bitmax_utils import get_ms_timestamp
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_utils import get_ms_timestamp
-class BitmaxAuth():
+class AscendExAuth():
"""
- Auth class required by bitmax API
- Learn more at https://bitmax-exchange.github.io/bitmax-pro-api/#authenticate-a-restful-request
+ Auth class required by AscendEx API
+ Learn more at https://ascendex.github.io/ascendex-pro-api/#authenticate-a-restful-request
"""
def __init__(self, api_key: str, secret_key: str):
self.api_key = api_key
@@ -39,7 +39,7 @@ def get_auth_headers(
def get_headers(self) -> Dict[str, Any]:
"""
- Generates generic headers required by bitmax
+ Generates generic headers required by AscendEx
:return: a dictionary of headers
"""
diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_constants.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_constants.py
new file mode 100644
index 0000000000..27165b3257
--- /dev/null
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_constants.py
@@ -0,0 +1,7 @@
+# A single source of truth for constant variables related to the exchange
+
+
+EXCHANGE_NAME = "ascend_ex"
+REST_URL = "https://ascendex.com/api/pro/v1"
+WS_URL = "wss://ascendex.com/0/api/pro/v1/stream"
+PONG_PAYLOAD = {"op": "pong"}
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_exchange.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py
similarity index 88%
rename from hummingbot/connector/exchange/bitmax/bitmax_exchange.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py
index bef04d3a52..a8aac4e618 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_exchange.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_exchange.py
@@ -36,25 +36,25 @@
TradeFee
)
from hummingbot.connector.exchange_base import ExchangeBase
-from hummingbot.connector.exchange.bitmax.bitmax_order_book_tracker import BitmaxOrderBookTracker
-from hummingbot.connector.exchange.bitmax.bitmax_user_stream_tracker import BitmaxUserStreamTracker
-from hummingbot.connector.exchange.bitmax.bitmax_auth import BitmaxAuth
-from hummingbot.connector.exchange.bitmax.bitmax_in_flight_order import BitmaxInFlightOrder
-from hummingbot.connector.exchange.bitmax import bitmax_utils
-from hummingbot.connector.exchange.bitmax.bitmax_constants import EXCHANGE_NAME, REST_URL, getRestUrlPriv
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_order_book_tracker import AscendExOrderBookTracker
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_user_stream_tracker import AscendExUserStreamTracker
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_in_flight_order import AscendExInFlightOrder
+from hummingbot.connector.exchange.ascend_ex import ascend_ex_utils
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_constants import EXCHANGE_NAME, REST_URL
from hummingbot.core.data_type.common import OpenOrder
ctce_logger = None
s_decimal_NaN = Decimal("nan")
-BitmaxTradingRule = namedtuple("BitmaxTradingRule", "minNotional maxNotional")
-BitmaxOrder = namedtuple("BitmaxOrder", "symbol price orderQty orderType avgPx cumFee cumFilledQty errorCode feeAsset lastExecTime orderId seqNum side status stopPrice execInst")
-BitmaxBalance = namedtuple("BitmaxBalance", "asset availableBalance totalBalance")
+AscendExTradingRule = namedtuple("AscendExTradingRule", "minNotional maxNotional")
+AscendExOrder = namedtuple("AscendExOrder", "symbol price orderQty orderType avgPx cumFee cumFilledQty errorCode feeAsset lastExecTime orderId seqNum side status stopPrice execInst")
+AscendExBalance = namedtuple("AscendExBalance", "asset availableBalance totalBalance")
-class BitmaxExchange(ExchangeBase):
+class AscendExExchange(ExchangeBase):
"""
- BitmaxExchange connects with Bitmax exchange and provides order book pricing, user account tracking and
+ AscendExExchange connects with AscendEx exchange and provides order book pricing, user account tracking and
trading functionality.
"""
API_CALL_TIMEOUT = 10.0
@@ -70,31 +70,31 @@ def logger(cls) -> HummingbotLogger:
return ctce_logger
def __init__(self,
- bitmax_api_key: str,
- bitmax_secret_key: str,
+ ascend_ex_api_key: str,
+ ascend_ex_secret_key: str,
trading_pairs: Optional[List[str]] = None,
trading_required: bool = True
):
"""
- :param bitmax_api_key: The API key to connect to private Bitmax APIs.
- :param bitmax_secret_key: The API secret.
+ :param ascend_ex_api_key: The API key to connect to private AscendEx APIs.
+ :param ascend_ex_secret_key: The API secret.
:param trading_pairs: The market trading pairs which to track order book data.
:param trading_required: Whether actual trading is needed.
"""
super().__init__()
self._trading_required = trading_required
self._trading_pairs = trading_pairs
- self._bitmax_auth = BitmaxAuth(bitmax_api_key, bitmax_secret_key)
- self._order_book_tracker = BitmaxOrderBookTracker(trading_pairs=trading_pairs)
- self._user_stream_tracker = BitmaxUserStreamTracker(self._bitmax_auth, trading_pairs)
+ self._ascend_ex_auth = AscendExAuth(ascend_ex_api_key, ascend_ex_secret_key)
+ self._order_book_tracker = AscendExOrderBookTracker(trading_pairs=trading_pairs)
+ self._user_stream_tracker = AscendExUserStreamTracker(self._ascend_ex_auth, trading_pairs)
self._ev_loop = asyncio.get_event_loop()
self._shared_client = None
self._poll_notifier = asyncio.Event()
self._last_timestamp = 0
- self._in_flight_orders = {} # Dict[client_order_id:str, BitmaxInFlightOrder]
+ self._in_flight_orders = {} # Dict[client_order_id:str, AscendExInFlightOrder]
self._order_not_found_records = {} # Dict[client_order_id:str, count:int]
self._trading_rules = {} # Dict[trading_pair:str, TradingRule]
- self._bitmax_trading_rules = {} # Dict[trading_pair:str, BitmaxTradingRule]
+ self._ascend_ex_trading_rules = {} # Dict[trading_pair:str, AscendExTradingRule]
self._status_polling_task = None
self._user_stream_event_listener_task = None
self._trading_rules_polling_task = None
@@ -115,7 +115,7 @@ def trading_rules(self) -> Dict[str, TradingRule]:
return self._trading_rules
@property
- def in_flight_orders(self) -> Dict[str, BitmaxInFlightOrder]:
+ def in_flight_orders(self) -> Dict[str, AscendExInFlightOrder]:
return self._in_flight_orders
@property
@@ -126,7 +126,7 @@ def status_dict(self) -> Dict[str, bool]:
return {
"order_books_initialized": self._order_book_tracker.ready,
"account_balance": len(self._account_balances) > 0 if self._trading_required else True,
- "trading_rule_initialized": len(self._trading_rules) > 0 and len(self._bitmax_trading_rules) > 0,
+ "trading_rule_initialized": len(self._trading_rules) > 0 and len(self._ascend_ex_trading_rules) > 0,
"user_stream_initialized":
self._user_stream_tracker.data_source.last_recv_time > 0 if self._trading_required else True,
"account_data": self._account_group is not None and self._account_uid is not None
@@ -165,7 +165,7 @@ def restore_tracking_states(self, saved_states: Dict[str, any]):
:param saved_states: The saved tracking_states.
"""
self._in_flight_orders.update({
- key: BitmaxInFlightOrder.from_json(value)
+ key: AscendExInFlightOrder.from_json(value)
for key, value in saved_states.items()
})
@@ -258,22 +258,22 @@ async def _trading_rules_polling_loop(self):
except Exception as e:
self.logger().network(f"Unexpected error while fetching trading rules. Error: {str(e)}",
exc_info=True,
- app_warning_msg="Could not fetch new trading rules from Bitmax. "
+ app_warning_msg="Could not fetch new trading rules from AscendEx. "
"Check network connection.")
await asyncio.sleep(0.5)
async def _update_trading_rules(self):
instruments_info = await self._api_request("get", path_url="products")
- [trading_rules, bitmax_trading_rules] = self._format_trading_rules(instruments_info)
+ [trading_rules, ascend_ex_trading_rules] = self._format_trading_rules(instruments_info)
self._trading_rules.clear()
self._trading_rules = trading_rules
- self._bitmax_trading_rules.clear()
- self._bitmax_trading_rules = bitmax_trading_rules
+ self._ascend_ex_trading_rules.clear()
+ self._ascend_ex_trading_rules = ascend_ex_trading_rules
def _format_trading_rules(
self,
instruments_info: Dict[str, Any]
- ) -> [Dict[str, TradingRule], Dict[str, Dict[str, BitmaxTradingRule]]]:
+ ) -> [Dict[str, TradingRule], Dict[str, Dict[str, AscendExTradingRule]]]:
"""
Converts json API response into a dictionary of trading rules.
:param instruments_info: The json API response
@@ -299,27 +299,27 @@ def _format_trading_rules(
}
"""
trading_rules = {}
- bitmax_trading_rules = {}
+ ascend_ex_trading_rules = {}
for rule in instruments_info["data"]:
try:
- trading_pair = bitmax_utils.convert_from_exchange_trading_pair(rule["symbol"])
+ trading_pair = ascend_ex_utils.convert_from_exchange_trading_pair(rule["symbol"])
trading_rules[trading_pair] = TradingRule(
trading_pair,
min_price_increment=Decimal(rule["tickSize"]),
min_base_amount_increment=Decimal(rule["lotSize"])
)
- bitmax_trading_rules[trading_pair] = BitmaxTradingRule(
+ ascend_ex_trading_rules[trading_pair] = AscendExTradingRule(
minNotional=Decimal(rule["minNotional"]),
maxNotional=Decimal(rule["maxNotional"])
)
except Exception:
self.logger().error(f"Error parsing the trading pair rule {rule}. Skipping.", exc_info=True)
- return [trading_rules, bitmax_trading_rules]
+ return [trading_rules, ascend_ex_trading_rules]
async def _update_account_data(self):
headers = {
- **self._bitmax_auth.get_headers(),
- **self._bitmax_auth.get_auth_headers("info"),
+ **self._ascend_ex_auth.get_headers(),
+ **self._ascend_ex_auth.get_auth_headers("info"),
}
url = f"{REST_URL}/info"
response = await aiohttp.ClientSession().get(url, headers=headers)
@@ -359,16 +359,16 @@ async def _api_request(self,
if (self._account_group) is None:
await self._update_account_data()
- url = f"{getRestUrlPriv(self._account_group)}/{path_url}"
+ url = f"{ascend_ex_utils.get_rest_url_private(self._account_group)}/{path_url}"
headers = {
- **self._bitmax_auth.get_headers(),
- **self._bitmax_auth.get_auth_headers(
+ **self._ascend_ex_auth.get_headers(),
+ **self._ascend_ex_auth.get_auth_headers(
path_url if force_auth_path_url is None else force_auth_path_url
),
}
else:
url = f"{REST_URL}/{path_url}"
- headers = self._bitmax_auth.get_headers()
+ headers = self._ascend_ex_auth.get_headers()
client = await self._http_client()
if method == "get":
@@ -433,7 +433,7 @@ def buy(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET,
:param price: The price (note: this is no longer optional)
:returns A new internal order id
"""
- client_order_id = bitmax_utils.gen_client_order_id(True, trading_pair)
+ client_order_id = ascend_ex_utils.gen_client_order_id(True, trading_pair)
safe_ensure_future(self._create_order(TradeType.BUY, client_order_id, trading_pair, amount, order_type, price))
return client_order_id
@@ -448,7 +448,7 @@ def sell(self, trading_pair: str, amount: Decimal, order_type=OrderType.MARKET,
:param price: The price (note: this is no longer optional)
:returns A new internal order id
"""
- client_order_id = bitmax_utils.gen_client_order_id(False, trading_pair)
+ client_order_id = ascend_ex_utils.gen_client_order_id(False, trading_pair)
safe_ensure_future(self._create_order(TradeType.SELL, client_order_id, trading_pair, amount, order_type, price))
return client_order_id
@@ -480,25 +480,25 @@ async def _create_order(self,
"""
if not order_type.is_limit_type():
raise Exception(f"Unsupported order type: {order_type}")
- bitmax_trading_rule = self._bitmax_trading_rules[trading_pair]
+ ascend_ex_trading_rule = self._ascend_ex_trading_rules[trading_pair]
amount = self.quantize_order_amount(trading_pair, amount)
price = self.quantize_order_price(trading_pair, price)
try:
- # bitmax has a unique way of determening if the order has enough "worth" to be posted
- # see https://bitmax-exchange.github.io/bitmax-pro-api/#place-order
+ # ascend_ex has a unique way of determening if the order has enough "worth" to be posted
+ # see https://ascendex.github.io/ascendex-pro-api/#place-order
notional = Decimal(price * amount)
- if notional < bitmax_trading_rule.minNotional or notional > bitmax_trading_rule.maxNotional:
- raise ValueError(f"Notional amount {notional} is not withing the range of {bitmax_trading_rule.minNotional}-{bitmax_trading_rule.maxNotional}.")
+ if notional < ascend_ex_trading_rule.minNotional or notional > ascend_ex_trading_rule.maxNotional:
+ raise ValueError(f"Notional amount {notional} is not withing the range of {ascend_ex_trading_rule.minNotional}-{ascend_ex_trading_rule.maxNotional}.")
# TODO: check balance
- [exchange_order_id, timestamp] = bitmax_utils.gen_exchange_order_id(self._account_uid, order_id)
+ [exchange_order_id, timestamp] = ascend_ex_utils.gen_exchange_order_id(self._account_uid, order_id)
api_params = {
"id": exchange_order_id,
"time": timestamp,
- "symbol": bitmax_utils.convert_to_exchange_trading_pair(trading_pair),
+ "symbol": ascend_ex_utils.convert_to_exchange_trading_pair(trading_pair),
"orderPrice": f"{price:f}",
"orderQty": f"{amount:f}",
"orderType": "limit",
@@ -517,7 +517,6 @@ async def _create_order(self,
await self._api_request("post", "cash/order", api_params, True, force_auth_path_url="order")
tracked_order = self._in_flight_orders.get(order_id)
- # tracked_order.update_exchange_order_id(exchange_order_id)
if tracked_order is not None:
self.logger().info(f"Created {order_type.name} {trade_type.name} order {order_id} for "
@@ -540,7 +539,7 @@ async def _create_order(self,
except Exception as e:
self.stop_tracking_order(order_id)
self.logger().network(
- f"Error submitting {trade_type.name} {order_type.name} order to Bitmax for "
+ f"Error submitting {trade_type.name} {order_type.name} order to AscendEx for "
f"{amount} {trading_pair} "
f"{price}.",
exc_info=True,
@@ -560,7 +559,7 @@ def start_tracking_order(self,
"""
Starts tracking an order by simply adding it into _in_flight_orders dictionary.
"""
- self._in_flight_orders[order_id] = BitmaxInFlightOrder(
+ self._in_flight_orders[order_id] = AscendExInFlightOrder(
client_order_id=order_id,
exchange_order_id=exchange_order_id,
trading_pair=trading_pair,
@@ -596,9 +595,9 @@ async def _execute_cancel(self, trading_pair: str, order_id: str) -> str:
"delete",
"cash/order",
{
- "symbol": bitmax_utils.convert_to_exchange_trading_pair(trading_pair),
+ "symbol": ascend_ex_utils.convert_to_exchange_trading_pair(trading_pair),
"orderId": ex_order_id,
- "time": bitmax_utils.get_ms_timestamp()
+ "time": ascend_ex_utils.get_ms_timestamp()
},
True,
force_auth_path_url="order"
@@ -615,7 +614,7 @@ async def _execute_cancel(self, trading_pair: str, order_id: str) -> str:
self.logger().network(
f"Failed to cancel order {order_id}: {str(e)}",
exc_info=True,
- app_warning_msg=f"Failed to cancel the order {order_id} on Bitmax. "
+ app_warning_msg=f"Failed to cancel the order {order_id} on AscendEx. "
f"Check API key and network connection."
)
@@ -639,7 +638,7 @@ async def _status_polling_loop(self):
self.logger().error(str(e), exc_info=True)
self.logger().network("Unexpected error while fetching account updates.",
exc_info=True,
- app_warning_msg="Could not fetch account updates from Bitmax. "
+ app_warning_msg="Could not fetch account updates from AscendEx. "
"Check API key and network connection.")
await asyncio.sleep(0.5)
@@ -649,7 +648,7 @@ async def _update_balances(self):
"""
response = await self._api_request("get", "cash/balance", {}, True, force_auth_path_url="balance")
balances = list(map(
- lambda balance: BitmaxBalance(
+ lambda balance: AscendExBalance(
balance["asset"],
balance["availableBalance"],
balance["totalBalance"]
@@ -687,7 +686,7 @@ async def _update_order_status(self):
continue
order_data = response.get("data")
- self._process_order_message(BitmaxOrder(
+ self._process_order_message(AscendExOrder(
order_data["symbol"],
order_data["price"],
order_data["orderQty"],
@@ -738,7 +737,7 @@ async def cancel_all(self, timeout_seconds: float):
self.logger().network(
"Failed to cancel all orders.",
exc_info=True,
- app_warning_msg="Failed to cancel all orders on Bitmax. Check API key and network connection."
+ app_warning_msg="Failed to cancel all orders on AscendEx. Check API key and network connection."
)
return cancellation_results
@@ -783,14 +782,14 @@ async def _iter_user_event_queue(self) -> AsyncIterable[Dict[str, any]]:
self.logger().network(
"Unknown error. Retrying after 1 seconds.",
exc_info=True,
- app_warning_msg="Could not fetch user events from Bitmax. Check API key and network connection."
+ app_warning_msg="Could not fetch user events from AscendEx. Check API key and network connection."
)
await asyncio.sleep(1.0)
async def _user_stream_event_listener(self):
"""
Listens to message in _user_stream_tracker.user_stream queue. The messages are put in by
- BitmaxAPIUserStreamDataSource.
+ AscendExAPIUserStreamDataSource.
"""
async for event_message in self._iter_user_event_queue():
try:
@@ -798,7 +797,7 @@ async def _user_stream_event_listener(self):
order_data = event_message.get("data")
trading_pair = order_data["s"]
base_asset, quote_asset = tuple(asset for asset in trading_pair.split("/"))
- self._process_order_message(BitmaxOrder(
+ self._process_order_message(AscendExOrder(
trading_pair,
order_data["p"],
order_data["q"],
@@ -817,12 +816,12 @@ async def _user_stream_event_listener(self):
order_data["ei"]
))
# Handles balance updates from orders.
- base_asset_balance = BitmaxBalance(
+ base_asset_balance = AscendExBalance(
base_asset,
order_data["bab"],
order_data["btb"]
)
- quote_asset_balance = BitmaxBalance(
+ quote_asset_balance = AscendExBalance(
quote_asset,
order_data["qab"],
order_data["qtb"]
@@ -831,7 +830,7 @@ async def _user_stream_event_listener(self):
elif event_message.get("m") == "balance":
# Handles balance updates from Deposits/Withdrawals, Transfers between Cash and Margin Accounts
balance_data = event_message.get("data")
- balance = BitmaxBalance(
+ balance = AscendExBalance(
balance_data["a"],
balance_data["ab"],
balance_data["tb"]
@@ -869,7 +868,7 @@ async def get_open_orders(self) -> List[OpenOrder]:
ret_val.append(
OpenOrder(
client_order_id=client_order_id,
- trading_pair=bitmax_utils.convert_from_exchange_trading_pair(order["symbol"]),
+ trading_pair=ascend_ex_utils.convert_from_exchange_trading_pair(order["symbol"]),
price=Decimal(str(order["price"])),
amount=Decimal(str(order["orderQty"])),
executed_amount=Decimal(str(order["cumFilledQty"])),
@@ -882,7 +881,7 @@ async def get_open_orders(self) -> List[OpenOrder]:
)
return ret_val
- def _process_order_message(self, order_msg: BitmaxOrder):
+ def _process_order_message(self, order_msg: AscendExOrder):
"""
Updates in-flight order and triggers cancellation or failure event if needed.
:param order_msg: The order response from either REST or web socket API (they are of the same format)
@@ -917,7 +916,7 @@ def _process_order_message(self, order_msg: BitmaxOrder):
self.stop_tracking_order(client_order_id)
elif tracked_order.is_failure:
self.logger().info(f"The market order {client_order_id} has failed according to order status API. "
- f"Reason: {bitmax_utils.get_api_reason(order_msg.errorCode)}")
+ f"Reason: {ascend_ex_utils.get_api_reason(order_msg.errorCode)}")
self.trigger_event(MarketEvent.OrderFailure,
MarketOrderFailureEvent(
self.current_timestamp,
@@ -965,7 +964,7 @@ def _process_order_message(self, order_msg: BitmaxOrder):
)
self.stop_tracking_order(client_order_id)
- def _process_balances(self, balances: List[BitmaxBalance]):
+ def _process_balances(self, balances: List[AscendExBalance]):
local_asset_names = set(self._account_balances.keys())
remote_asset_names = set()
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_in_flight_order.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_in_flight_order.py
similarity index 95%
rename from hummingbot/connector/exchange/bitmax/bitmax_in_flight_order.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_in_flight_order.py
index b455433ba0..0654f2cba9 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_in_flight_order.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_in_flight_order.py
@@ -12,7 +12,7 @@
from hummingbot.connector.in_flight_order_base import InFlightOrderBase
-class BitmaxInFlightOrder(InFlightOrderBase):
+class AscendExInFlightOrder(InFlightOrderBase):
def __init__(self,
client_order_id: str,
exchange_order_id: Optional[str],
@@ -53,7 +53,7 @@ def from_json(cls, data: Dict[str, Any]) -> InFlightOrderBase:
:param data: json data from API
:return: formatted InFlightOrder
"""
- retval = BitmaxInFlightOrder(
+ retval = AscendExInFlightOrder(
data["client_order_id"],
data["exchange_order_id"],
data["trading_pair"],
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_order_book.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book.py
similarity index 83%
rename from hummingbot/connector/exchange/bitmax/bitmax_order_book.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book.py
index be6702853d..88ebe8b0d1 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_order_book.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book.py
@@ -1,24 +1,25 @@
#!/usr/bin/env python
import logging
-import hummingbot.connector.exchange.bitmax.bitmax_constants as constants
+import hummingbot.connector.exchange.ascend_ex.ascend_ex_constants as constants
from sqlalchemy.engine import RowProxy
from typing import (
Optional,
Dict,
List, Any)
-from hummingbot.logger import HummingbotLogger
+
from hummingbot.core.data_type.order_book import OrderBook
from hummingbot.core.data_type.order_book_message import (
OrderBookMessage, OrderBookMessageType
)
-from hummingbot.connector.exchange.bitmax.bitmax_order_book_message import BitmaxOrderBookMessage
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_order_book_message import AscendExOrderBookMessage
+from hummingbot.logger import HummingbotLogger
_logger = None
-class BitmaxOrderBook(OrderBook):
+class AscendExOrderBook(OrderBook):
@classmethod
def logger(cls) -> HummingbotLogger:
global _logger
@@ -35,13 +36,13 @@ def snapshot_message_from_exchange(cls,
Convert json snapshot data into standard OrderBookMessage format
:param msg: json snapshot data from live web socket stream
:param timestamp: timestamp attached to incoming data
- :return: BitmaxOrderBookMessage
+ :return: AscendExOrderBookMessage
"""
if metadata:
msg.update(metadata)
- return BitmaxOrderBookMessage(
+ return AscendExOrderBookMessage(
message_type=OrderBookMessageType.SNAPSHOT,
content=msg,
timestamp=timestamp
@@ -53,9 +54,9 @@ def snapshot_message_from_db(cls, record: RowProxy, metadata: Optional[Dict] = N
*used for backtesting
Convert a row of snapshot data into standard OrderBookMessage format
:param record: a row of snapshot data from the database
- :return: BitmaxOrderBookMessage
+ :return: AscendExOrderBookMessage
"""
- return BitmaxOrderBookMessage(
+ return AscendExOrderBookMessage(
message_type=OrderBookMessageType.SNAPSHOT,
content=record.json,
timestamp=record.timestamp
@@ -70,13 +71,13 @@ def diff_message_from_exchange(cls,
Convert json diff data into standard OrderBookMessage format
:param msg: json diff data from live web socket stream
:param timestamp: timestamp attached to incoming data
- :return: BitmaxOrderBookMessage
+ :return: AscendExOrderBookMessage
"""
if metadata:
msg.update(metadata)
- return BitmaxOrderBookMessage(
+ return AscendExOrderBookMessage(
message_type=OrderBookMessageType.DIFF,
content=msg,
timestamp=timestamp
@@ -88,9 +89,9 @@ def diff_message_from_db(cls, record: RowProxy, metadata: Optional[Dict] = None)
*used for backtesting
Convert a row of diff data into standard OrderBookMessage format
:param record: a row of diff data from the database
- :return: BitmaxOrderBookMessage
+ :return: AscendExOrderBookMessage
"""
- return BitmaxOrderBookMessage(
+ return AscendExOrderBookMessage(
message_type=OrderBookMessageType.DIFF,
content=record.json,
timestamp=record.timestamp
@@ -104,7 +105,7 @@ def trade_message_from_exchange(cls,
"""
Convert a trade data into standard OrderBookMessage format
:param record: a trade data from the database
- :return: BitmaxOrderBookMessage
+ :return: AscendExOrderBookMessage
"""
if metadata:
@@ -117,7 +118,7 @@ def trade_message_from_exchange(cls,
"amount": msg.get("q"),
})
- return BitmaxOrderBookMessage(
+ return AscendExOrderBookMessage(
message_type=OrderBookMessageType.TRADE,
content=msg,
timestamp=timestamp
@@ -129,9 +130,9 @@ def trade_message_from_db(cls, record: RowProxy, metadata: Optional[Dict] = None
*used for backtesting
Convert a row of trade data into standard OrderBookMessage format
:param record: a row of trade data from the database
- :return: BitmaxOrderBookMessage
+ :return: AscendExOrderBookMessage
"""
- return BitmaxOrderBookMessage(
+ return AscendExOrderBookMessage(
message_type=OrderBookMessageType.TRADE,
content=record.json,
timestamp=record.timestamp
@@ -142,5 +143,5 @@ def from_snapshot(cls, snapshot: OrderBookMessage):
raise NotImplementedError(constants.EXCHANGE_NAME + " order book needs to retain individual order data.")
@classmethod
- def restore_from_snapshot_and_diffs(self, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]):
+ def restore_from_snapshot_and_diffs(cls, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]):
raise NotImplementedError(constants.EXCHANGE_NAME + " order book needs to retain individual order data.")
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_order_book_message.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_message.py
similarity index 95%
rename from hummingbot/connector/exchange/bitmax/bitmax_order_book_message.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_message.py
index a00550b66b..c5b0ef2e13 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_order_book_message.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_message.py
@@ -13,7 +13,7 @@
)
-class BitmaxOrderBookMessage(OrderBookMessage):
+class AscendExOrderBookMessage(OrderBookMessage):
def __new__(
cls,
message_type: OrderBookMessageType,
@@ -27,7 +27,7 @@ def __new__(
raise ValueError("timestamp must not be None when initializing snapshot messages.")
timestamp = content["timestamp"]
- return super(BitmaxOrderBookMessage, cls).__new__(
+ return super(AscendExOrderBookMessage, cls).__new__(
cls, message_type, content, timestamp=timestamp, *args, **kwargs
)
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_order_book_tracker.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_tracker.py
similarity index 75%
rename from hummingbot/connector/exchange/bitmax/bitmax_order_book_tracker.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_tracker.py
index 9fa26cc508..0801dd47be 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_order_book_tracker.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_tracker.py
@@ -2,20 +2,23 @@
import asyncio
import bisect
import logging
-import hummingbot.connector.exchange.bitmax.bitmax_constants as constants
import time
+
+import hummingbot.connector.exchange.ascend_ex.ascend_ex_constants as constants
+
from collections import defaultdict, deque
from typing import Optional, Dict, List, Deque
+
from hummingbot.core.data_type.order_book_message import OrderBookMessageType
-from hummingbot.logger import HummingbotLogger
from hummingbot.core.data_type.order_book_tracker import OrderBookTracker
-from hummingbot.connector.exchange.bitmax.bitmax_order_book_message import BitmaxOrderBookMessage
-from hummingbot.connector.exchange.bitmax.bitmax_active_order_tracker import BitmaxActiveOrderTracker
-from hummingbot.connector.exchange.bitmax.bitmax_api_order_book_data_source import BitmaxAPIOrderBookDataSource
-from hummingbot.connector.exchange.bitmax.bitmax_order_book import BitmaxOrderBook
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_order_book_message import AscendExOrderBookMessage
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_active_order_tracker import AscendExActiveOrderTracker
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_order_book_data_source import AscendExAPIOrderBookDataSource
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_order_book import AscendExOrderBook
+from hummingbot.logger import HummingbotLogger
-class BitmaxOrderBookTracker(OrderBookTracker):
+class AscendExOrderBookTracker(OrderBookTracker):
_logger: Optional[HummingbotLogger] = None
@classmethod
@@ -25,7 +28,7 @@ def logger(cls) -> HummingbotLogger:
return cls._logger
def __init__(self, trading_pairs: Optional[List[str]] = None,):
- super().__init__(BitmaxAPIOrderBookDataSource(trading_pairs), trading_pairs)
+ super().__init__(AscendExAPIOrderBookDataSource(trading_pairs), trading_pairs)
self._ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
self._order_book_snapshot_stream: asyncio.Queue = asyncio.Queue()
@@ -33,10 +36,10 @@ def __init__(self, trading_pairs: Optional[List[str]] = None,):
self._order_book_trade_stream: asyncio.Queue = asyncio.Queue()
self._process_msg_deque_task: Optional[asyncio.Task] = None
self._past_diffs_windows: Dict[str, Deque] = {}
- self._order_books: Dict[str, BitmaxOrderBook] = {}
- self._saved_message_queues: Dict[str, Deque[BitmaxOrderBookMessage]] = \
+ self._order_books: Dict[str, AscendExOrderBook] = {}
+ self._saved_message_queues: Dict[str, Deque[AscendExOrderBookMessage]] = \
defaultdict(lambda: deque(maxlen=1000))
- self._active_order_trackers: Dict[str, BitmaxActiveOrderTracker] = defaultdict(BitmaxActiveOrderTracker)
+ self._active_order_trackers: Dict[str, AscendExActiveOrderTracker] = defaultdict(AscendExActiveOrderTracker)
self._order_book_stream_listener_task: Optional[asyncio.Task] = None
self._order_book_trade_listener_task: Optional[asyncio.Task] = None
@@ -51,20 +54,20 @@ async def _track_single_book(self, trading_pair: str):
"""
Update an order book with changes from the latest batch of received messages
"""
- past_diffs_window: Deque[BitmaxOrderBookMessage] = deque()
+ past_diffs_window: Deque[AscendExOrderBookMessage] = deque()
self._past_diffs_windows[trading_pair] = past_diffs_window
message_queue: asyncio.Queue = self._tracking_message_queues[trading_pair]
- order_book: BitmaxOrderBook = self._order_books[trading_pair]
- active_order_tracker: BitmaxActiveOrderTracker = self._active_order_trackers[trading_pair]
+ order_book: AscendExOrderBook = self._order_books[trading_pair]
+ active_order_tracker: AscendExActiveOrderTracker = self._active_order_trackers[trading_pair]
last_message_timestamp: float = time.time()
diff_messages_accepted: int = 0
while True:
try:
- message: BitmaxOrderBookMessage = None
- saved_messages: Deque[BitmaxOrderBookMessage] = self._saved_message_queues[trading_pair]
+ message: AscendExOrderBookMessage = None
+ saved_messages: Deque[AscendExOrderBookMessage] = self._saved_message_queues[trading_pair]
# Process saved messages first if there are any
if len(saved_messages) > 0:
message = saved_messages.popleft()
@@ -87,7 +90,7 @@ async def _track_single_book(self, trading_pair: str):
diff_messages_accepted = 0
last_message_timestamp = now
elif message.type is OrderBookMessageType.SNAPSHOT:
- past_diffs: List[BitmaxOrderBookMessage] = list(past_diffs_window)
+ past_diffs: List[AscendExOrderBookMessage] = list(past_diffs_window)
# only replay diffs later than snapshot, first update active order with snapshot then replay diffs
replay_position = bisect.bisect_right(past_diffs, message)
replay_diffs = past_diffs[replay_position:]
diff --git a/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_tracker_entry.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_tracker_entry.py
new file mode 100644
index 0000000000..ebefecfebb
--- /dev/null
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_order_book_tracker_entry.py
@@ -0,0 +1,21 @@
+from hummingbot.core.data_type.order_book import OrderBook
+from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_active_order_tracker import AscendExActiveOrderTracker
+
+
+class AscendExOrderBookTrackerEntry(OrderBookTrackerEntry):
+ def __init__(
+ self, trading_pair: str, timestamp: float, order_book: OrderBook, active_order_tracker: AscendExActiveOrderTracker
+ ):
+ self._active_order_tracker = active_order_tracker
+ super(AscendExOrderBookTrackerEntry, self).__init__(trading_pair, timestamp, order_book)
+
+ def __repr__(self) -> str:
+ return (
+ f"AscendExOrderBookTrackerEntry(trading_pair='{self._trading_pair}', timestamp='{self._timestamp}', "
+ f"order_book='{self._order_book}')"
+ )
+
+ @property
+ def active_order_tracker(self) -> AscendExActiveOrderTracker:
+ return self._active_order_tracker
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_user_stream_tracker.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_user_stream_tracker.py
similarity index 69%
rename from hummingbot/connector/exchange/bitmax/bitmax_user_stream_tracker.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_user_stream_tracker.py
index 8255abdb94..8557ca44db 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_user_stream_tracker.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_user_stream_tracker.py
@@ -2,12 +2,16 @@
import asyncio
import logging
+
from typing import (
Optional,
List,
)
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_user_stream_data_source import \
+ AscendExAPIUserStreamDataSource
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_constants import EXCHANGE_NAME
from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource
-from hummingbot.logger import HummingbotLogger
from hummingbot.core.data_type.user_stream_tracker import (
UserStreamTracker
)
@@ -15,26 +19,24 @@
safe_ensure_future,
safe_gather,
)
-from hummingbot.connector.exchange.bitmax.bitmax_api_user_stream_data_source import \
- BitmaxAPIUserStreamDataSource
-from hummingbot.connector.exchange.bitmax.bitmax_auth import BitmaxAuth
-from hummingbot.connector.exchange.bitmax.bitmax_constants import EXCHANGE_NAME
+
+from hummingbot.logger import HummingbotLogger
-class BitmaxUserStreamTracker(UserStreamTracker):
- _cbpust_logger: Optional[HummingbotLogger] = None
+class AscendExUserStreamTracker(UserStreamTracker):
+ _logger: Optional[HummingbotLogger] = None
@classmethod
def logger(cls) -> HummingbotLogger:
- if cls._bust_logger is None:
- cls._bust_logger = logging.getLogger(__name__)
- return cls._bust_logger
+ if cls._logger is None:
+ cls._logger = logging.getLogger(__name__)
+ return cls._logger
def __init__(self,
- bitmax_auth: Optional[BitmaxAuth] = None,
+ ascend_ex_auth: Optional[AscendExAuth] = None,
trading_pairs: Optional[List[str]] = []):
super().__init__()
- self._bitmax_auth: BitmaxAuth = bitmax_auth
+ self._ascend_ex_auth: AscendExAuth = ascend_ex_auth
self._trading_pairs: List[str] = trading_pairs
self._ev_loop: asyncio.events.AbstractEventLoop = asyncio.get_event_loop()
self._data_source: Optional[UserStreamTrackerDataSource] = None
@@ -48,8 +50,8 @@ def data_source(self) -> UserStreamTrackerDataSource:
:return: OrderBookTrackerDataSource
"""
if not self._data_source:
- self._data_source = BitmaxAPIUserStreamDataSource(
- bitmax_auth=self._bitmax_auth,
+ self._data_source = AscendExAPIUserStreamDataSource(
+ ascend_ex_auth=self._ascend_ex_auth,
trading_pairs=self._trading_pairs
)
return self._data_source
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_utils.py b/hummingbot/connector/exchange/ascend_ex/ascend_ex_utils.py
similarity index 77%
rename from hummingbot/connector/exchange/bitmax/bitmax_utils.py
rename to hummingbot/connector/exchange/ascend_ex/ascend_ex_utils.py
index 93d33eb9d1..a8ac64400f 100644
--- a/hummingbot/connector/exchange/bitmax/bitmax_utils.py
+++ b/hummingbot/connector/exchange/ascend_ex/ascend_ex_utils.py
@@ -18,6 +18,14 @@
HBOT_BROKER_ID = "HMBot"
+def get_rest_url_private(account_id: int) -> str:
+ return f"https://ascendex.com/{account_id}/api/pro/v1"
+
+
+def get_ws_url_private(account_id: int) -> str:
+ return f"wss://ascendex.com/{account_id}/api/pro/v1"
+
+
def convert_from_exchange_trading_pair(exchange_trading_pair: str) -> str:
return exchange_trading_pair.replace("/", "-")
@@ -70,16 +78,16 @@ def gen_client_order_id(is_buy: bool, trading_pair: str) -> str:
KEYS = {
- "bitmax_api_key":
- ConfigVar(key="bitmax_api_key",
- prompt="Enter your Bitmax API key >>> ",
- required_if=using_exchange("bitmax"),
+ "ascend_ex_api_key":
+ ConfigVar(key="ascend_ex_api_key",
+ prompt="Enter your AscendEx API key >>> ",
+ required_if=using_exchange("ascend_ex"),
is_secure=True,
is_connect_key=True),
- "bitmax_secret_key":
- ConfigVar(key="bitmax_secret_key",
- prompt="Enter your Bitmax secret key >>> ",
- required_if=using_exchange("bitmax"),
+ "ascend_ex_secret_key":
+ ConfigVar(key="ascend_ex_secret_key",
+ prompt="Enter your AscendEx secret key >>> ",
+ required_if=using_exchange("ascend_ex"),
is_secure=True,
is_connect_key=True),
}
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_constants.py b/hummingbot/connector/exchange/bitmax/bitmax_constants.py
deleted file mode 100644
index 51ec061543..0000000000
--- a/hummingbot/connector/exchange/bitmax/bitmax_constants.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# A single source of truth for constant variables related to the exchange
-
-
-EXCHANGE_NAME = "bitmax"
-REST_URL = "https://bitmax.io/api/pro/v1"
-WS_URL = "wss://bitmax.io/1/api/pro/v1/stream"
-PONG_PAYLOAD = {"op": "pong"}
-
-
-def getRestUrlPriv(accountId: int) -> str:
- return f"https://bitmax.io/{accountId}/api/pro/v1"
-
-
-def getWsUrlPriv(accountId: int) -> str:
- return f"wss://bitmax.io/{accountId}/api/pro/v1"
diff --git a/hummingbot/connector/exchange/bitmax/bitmax_order_book_tracker_entry.py b/hummingbot/connector/exchange/bitmax/bitmax_order_book_tracker_entry.py
deleted file mode 100644
index a97a33088a..0000000000
--- a/hummingbot/connector/exchange/bitmax/bitmax_order_book_tracker_entry.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from hummingbot.core.data_type.order_book import OrderBook
-from hummingbot.core.data_type.order_book_tracker_entry import OrderBookTrackerEntry
-from hummingbot.connector.exchange.bitmax.bitmax_active_order_tracker import BitmaxActiveOrderTracker
-
-
-class BitmaxOrderBookTrackerEntry(OrderBookTrackerEntry):
- def __init__(
- self, trading_pair: str, timestamp: float, order_book: OrderBook, active_order_tracker: BitmaxActiveOrderTracker
- ):
- self._active_order_tracker = active_order_tracker
- super(BitmaxOrderBookTrackerEntry, self).__init__(trading_pair, timestamp, order_book)
-
- def __repr__(self) -> str:
- return (
- f"BitmaxOrderBookTrackerEntry(trading_pair='{self._trading_pair}', timestamp='{self._timestamp}', "
- f"order_book='{self._order_book}')"
- )
-
- @property
- def active_order_tracker(self) -> BitmaxActiveOrderTracker:
- return self._active_order_tracker
diff --git a/hummingbot/core/utils/estimate_fee.py b/hummingbot/core/utils/estimate_fee.py
index 78823e425e..5a52568861 100644
--- a/hummingbot/core/utils/estimate_fee.py
+++ b/hummingbot/core/utils/estimate_fee.py
@@ -2,16 +2,11 @@
from hummingbot.core.event.events import TradeFee, TradeFeeType
from hummingbot.client.config.fee_overrides_config_map import fee_overrides_config_map
from hummingbot.client.settings import CONNECTOR_SETTINGS
-from hummingbot.core.utils.eth_gas_station_lookup import get_gas_price, get_gas_limit
def estimate_fee(exchange: str, is_maker: bool) -> TradeFee:
if exchange not in CONNECTOR_SETTINGS:
raise Exception(f"Invalid connector. {exchange} does not exist in CONNECTOR_SETTINGS")
- use_gas = CONNECTOR_SETTINGS[exchange].use_eth_gas_lookup
- if use_gas:
- gas_amount = get_gas_price(in_gwei=False) * get_gas_limit(exchange)
- return TradeFee(percent=0, flat_fees=[("ETH", gas_amount)])
fee_type = CONNECTOR_SETTINGS[exchange].fee_type
fee_token = CONNECTOR_SETTINGS[exchange].fee_token
default_fees = CONNECTOR_SETTINGS[exchange].default_fees
diff --git a/hummingbot/core/utils/eth_gas_station_lookup.py b/hummingbot/core/utils/eth_gas_station_lookup.py
deleted file mode 100644
index ee56502631..0000000000
--- a/hummingbot/core/utils/eth_gas_station_lookup.py
+++ /dev/null
@@ -1,178 +0,0 @@
-import asyncio
-import requests
-import logging
-from typing import (
- Optional,
- Dict,
- Any
-)
-import aiohttp
-from enum import Enum
-from decimal import Decimal
-from hummingbot.core.network_base import (
- NetworkBase,
- NetworkStatus
-)
-from hummingbot.core.utils.async_utils import safe_ensure_future
-from hummingbot.logger import HummingbotLogger
-from hummingbot.client.config.global_config_map import global_config_map
-from hummingbot.client.settings import CONNECTOR_SETTINGS
-from hummingbot.client.settings import GATEAWAY_CA_CERT_PATH, GATEAWAY_CLIENT_CERT_PATH, GATEAWAY_CLIENT_KEY_PATH
-
-ETH_GASSTATION_API_URL = "https://data-api.defipulse.com/api/v1/egs/api/ethgasAPI.json?api-key={}"
-
-
-def get_gas_price(in_gwei: bool = True) -> Decimal:
- if not global_config_map["ethgasstation_gas_enabled"].value:
- gas_price = global_config_map["manual_gas_price"].value
- else:
- gas_price = EthGasStationLookup.get_instance().gas_price
- return gas_price if in_gwei else gas_price / Decimal("1e9")
-
-
-def get_gas_limit(connector_name: str) -> int:
- gas_limit = request_gas_limit(connector_name)
- return gas_limit
-
-
-def request_gas_limit(connector_name: str) -> int:
- host = global_config_map["gateway_api_host"].value
- port = global_config_map["gateway_api_port"].value
- balancer_max_swaps = global_config_map["balancer_max_swaps"].value
-
- base_url = ':'.join(['https://' + host, port])
- url = f"{base_url}/{connector_name}/gas-limit"
-
- ca_certs = GATEAWAY_CA_CERT_PATH
- client_certs = (GATEAWAY_CLIENT_CERT_PATH, GATEAWAY_CLIENT_KEY_PATH)
- params = {"maxSwaps": balancer_max_swaps} if connector_name == "balancer" else {}
- response = requests.post(url, data=params, verify=ca_certs, cert=client_certs)
- parsed_response = response.json()
- if response.status_code != 200:
- err_msg = ""
- if "error" in parsed_response:
- err_msg = f" Message: {parsed_response['error']}"
- raise IOError(f"Error fetching data from {url}. HTTP status is {response.status}.{err_msg}")
- if "error" in parsed_response:
- raise Exception(f"Error: {parsed_response['error']}")
- return parsed_response['gasLimit']
-
-
-class GasLevel(Enum):
- fast = "fast"
- fastest = "fastest"
- safeLow = "safeLow"
- average = "average"
-
-
-class EthGasStationLookup(NetworkBase):
- _egsl_logger: Optional[HummingbotLogger] = None
- _shared_instance: "EthGasStationLookup" = None
-
- @classmethod
- def get_instance(cls) -> "EthGasStationLookup":
- if cls._shared_instance is None:
- cls._shared_instance = EthGasStationLookup()
- return cls._shared_instance
-
- @classmethod
- def logger(cls) -> HummingbotLogger:
- if cls._egsl_logger is None:
- cls._egsl_logger = logging.getLogger(__name__)
- return cls._egsl_logger
-
- def __init__(self):
- super().__init__()
- self._gas_prices: Dict[str, Decimal] = {}
- self._gas_limits: Dict[str, Decimal] = {}
- self._balancer_max_swaps: int = global_config_map["balancer_max_swaps"].value
- self._async_task = None
-
- @property
- def api_key(self):
- return global_config_map["ethgasstation_api_key"].value
-
- @property
- def gas_level(self) -> GasLevel:
- return GasLevel[global_config_map["ethgasstation_gas_level"].value]
-
- @property
- def refresh_time(self):
- return global_config_map["ethgasstation_refresh_time"].value
-
- @property
- def gas_price(self):
- return self._gas_prices[self.gas_level]
-
- @property
- def gas_limits(self):
- return self._gas_limits
-
- @gas_limits.setter
- def gas_limits(self, gas_limits: Dict[str, int]):
- for key, value in gas_limits.items():
- self._gas_limits[key] = value
-
- @property
- def balancer_max_swaps(self):
- return self._balancer_max_swaps
-
- @balancer_max_swaps.setter
- def balancer_max_swaps(self, max_swaps: int):
- self._balancer_max_swaps = max_swaps
-
- async def gas_price_update_loop(self):
- while True:
- try:
- url = ETH_GASSTATION_API_URL.format(self.api_key)
- async with aiohttp.ClientSession() as client:
- response = await client.get(url=url)
- if response.status != 200:
- raise IOError(f"Error fetching current gas prices. "
- f"HTTP status is {response.status}.")
- resp_data: Dict[str, Any] = await response.json()
- for key, value in resp_data.items():
- if key in GasLevel.__members__:
- self._gas_prices[GasLevel[key]] = Decimal(str(value)) / Decimal("10")
- prices_str = ', '.join([k.name + ': ' + str(v) for k, v in self._gas_prices.items()])
- self.logger().info(f"Gas levels: [{prices_str}]")
- for name, con_setting in CONNECTOR_SETTINGS.items():
- if con_setting.use_eth_gas_lookup:
- self._gas_limits[name] = get_gas_limit(name)
- self.logger().info(f"{name} Gas estimate:"
- f" limit = {self._gas_limits[name]:.0f},"
- f" price = {self.gas_level.name},"
- f" estimated cost = {get_gas_price(False) * self._gas_limits[name]:.5f} ETH")
- await asyncio.sleep(self.refresh_time)
- except asyncio.CancelledError:
- raise
- except Exception:
- self.logger().network("Unexpected error running logging task.", exc_info=True)
- await asyncio.sleep(self.refresh_time)
-
- async def start_network(self):
- self._async_task = safe_ensure_future(self.gas_price_update_loop())
-
- async def stop_network(self):
- if self._async_task is not None:
- self._async_task.cancel()
- self._async_task = None
-
- async def check_network(self) -> NetworkStatus:
- try:
- url = ETH_GASSTATION_API_URL.format(self.api_key)
- async with aiohttp.ClientSession() as client:
- response = await client.get(url=url)
- if response.status != 200:
- raise Exception(f"Error connecting to {url}. HTTP status is {response.status}.")
- except asyncio.CancelledError:
- raise
- except Exception:
- return NetworkStatus.NOT_CONNECTED
- return NetworkStatus.CONNECTED
-
- def start(self):
- NetworkBase.start(self)
-
- def stop(self):
- NetworkBase.stop(self)
diff --git a/hummingbot/core/utils/ethereum.py b/hummingbot/core/utils/ethereum.py
index ff667d3a0a..e30c2b3478 100644
--- a/hummingbot/core/utils/ethereum.py
+++ b/hummingbot/core/utils/ethereum.py
@@ -3,7 +3,11 @@
from hexbytes import HexBytes
from web3 import Web3
from web3.datastructures import AttributeDict
-from typing import Dict
+from typing import Dict, List
+import aiohttp
+from hummingbot.client.config.global_config_map import global_config_map
+import itertools as it
+from hummingbot.core.utils import async_ttl_cache
def check_web3(ethereum_rpc_url: str) -> bool:
@@ -32,3 +36,55 @@ def block_values_to_hex(block: AttributeDict) -> AttributeDict:
except binascii.Error:
formatted_block[key] = value
return AttributeDict(formatted_block)
+
+
+def check_transaction_exceptions(trade_data: dict) -> dict:
+
+ exception_list = []
+
+ gas_limit = trade_data["gas_limit"]
+ # gas_price = trade_data["gas_price"]
+ gas_cost = trade_data["gas_cost"]
+ amount = trade_data["amount"]
+ side = trade_data["side"]
+ base = trade_data["base"]
+ quote = trade_data["quote"]
+ balances = trade_data["balances"]
+ allowances = trade_data["allowances"]
+ swaps_message = f"Total swaps: {trade_data['swaps']}" if "swaps" in trade_data.keys() else ''
+
+ eth_balance = balances["ETH"]
+
+ # check for sufficient gas
+ if eth_balance < gas_cost:
+ exception_list.append(f"Insufficient ETH balance to cover gas:"
+ f" Balance: {eth_balance}. Est. gas cost: {gas_cost}. {swaps_message}")
+
+ trade_token = base if side == "side" else quote
+ trade_allowance = allowances[trade_token]
+
+ # check for gas limit set to low
+ gas_limit_threshold = 21000
+ if gas_limit < gas_limit_threshold:
+ exception_list.append(f"Gas limit {gas_limit} below recommended {gas_limit_threshold} threshold.")
+
+ # check for insufficient token allowance
+ if allowances[trade_token] < amount:
+ exception_list.append(f"Insufficient {trade_token} allowance {trade_allowance}. Amount to trade: {amount}")
+
+ return exception_list
+
+
+@async_ttl_cache(ttl=30)
+async def fetch_trading_pairs() -> List[str]:
+ token_list_url = global_config_map.get("ethereum_token_list_url").value
+ tokens = set()
+ async with aiohttp.ClientSession() as client:
+ resp = await client.get(token_list_url)
+ resp_json = await resp.json()
+ for token in resp_json["tokens"]:
+ tokens.add(token["symbol"])
+ trading_pairs = []
+ for base, quote in it.permutations(tokens, 2):
+ trading_pairs.append(f"{base}-{quote}")
+ return trading_pairs
diff --git a/hummingbot/strategy/amm_arb/amm_arb.py b/hummingbot/strategy/amm_arb/amm_arb.py
index b96af808fc..381cd991cc 100644
--- a/hummingbot/strategy/amm_arb/amm_arb.py
+++ b/hummingbot/strategy/amm_arb/amm_arb.py
@@ -253,13 +253,18 @@ async def format_status(self) -> str:
market, trading_pair, base_asset, quote_asset = market_info
buy_price = await market.get_quote_price(trading_pair, True, self._order_amount)
sell_price = await market.get_quote_price(trading_pair, False, self._order_amount)
- mid_price = (buy_price + sell_price) / 2
+
+ # check for unavailable price data
+ buy_price = Decimal(str(buy_price)) if buy_price is not None else '-'
+ sell_price = Decimal(str(sell_price)) if sell_price is not None else '-'
+ mid_price = ((buy_price + sell_price) / 2) if '-' not in [buy_price, sell_price] else '-'
+
data.append([
market.display_name,
trading_pair,
- float(sell_price),
- float(buy_price),
- float(mid_price)
+ sell_price,
+ buy_price,
+ mid_price
])
markets_df = pd.DataFrame(data=data, columns=columns)
lines = []
@@ -335,7 +340,7 @@ async def quote_in_eth_rate_fetch_loop(self):
self._market_2_quote_eth_rate = await self.request_rate_in_eth(self._market_info_2.quote_asset)
self.logger().warning(f"Estimate conversion rate - "
f"{self._market_info_2.quote_asset}:ETH = {self._market_2_quote_eth_rate} ")
- await asyncio.sleep(60 * 5)
+ await asyncio.sleep(60 * 1)
except asyncio.CancelledError:
raise
except Exception as e:
@@ -348,4 +353,5 @@ async def quote_in_eth_rate_fetch_loop(self):
async def request_rate_in_eth(self, quote: str) -> int:
if self._uniswap is None:
self._uniswap = UniswapConnector([f"{quote}-WETH"], "", None)
+ await self._uniswap.initiate_pool() # initiate to cache swap pool
return await self._uniswap.get_quote_price(f"{quote}-WETH", True, 1)
diff --git a/hummingbot/strategy/pure_market_making/pure_market_making_config_map.py b/hummingbot/strategy/pure_market_making/pure_market_making_config_map.py
index 1501d16c6c..e488ec0f17 100644
--- a/hummingbot/strategy/pure_market_making/pure_market_making_config_map.py
+++ b/hummingbot/strategy/pure_market_making/pure_market_making_config_map.py
@@ -100,6 +100,11 @@ def validate_price_floor_ceiling(value: str) -> Optional[str]:
return "Value must be more than 0 or -1 to disable this feature."
+def on_validated_price_type(value: str):
+ if value == 'inventory_cost':
+ pure_market_making_config_map["inventory_price"].value = None
+
+
def exchange_on_validated(value: str):
required_exchanges.append(value)
@@ -241,6 +246,7 @@ def exchange_on_validated(value: str):
prompt="What is the price of your base asset inventory? ",
type_str="decimal",
validator=lambda v: validate_decimal(v, min_value=Decimal("0"), inclusive=True),
+ required_if=lambda: pure_market_making_config_map.get("price_type").value == "inventory_cost",
default=Decimal("1"),
),
"filled_order_delay":
@@ -308,6 +314,7 @@ def exchange_on_validated(value: str):
type_str="str",
required_if=lambda: pure_market_making_config_map.get("price_source").value != "custom_api",
default="mid_price",
+ on_validated=on_validated_price_type,
validator=lambda s: None if s in {"mid_price",
"last_price",
"last_own_trade_price",
diff --git a/hummingbot/templates/conf_fee_overrides_TEMPLATE.yml b/hummingbot/templates/conf_fee_overrides_TEMPLATE.yml
index ed08e2fa90..3aff911e85 100644
--- a/hummingbot/templates/conf_fee_overrides_TEMPLATE.yml
+++ b/hummingbot/templates/conf_fee_overrides_TEMPLATE.yml
@@ -72,9 +72,15 @@ okex_taker_fee:
balancer_maker_fee_amount:
balancer_taker_fee_amount:
+uniswap_maker_fee_amount:
+uniswap_taker_fee_amount:
+
bitmax_maker_fee:
bitmax_taker_fee:
+ascend_ex_maker_fee:
+ascend_ex_taker_fee:
+
probit_maker_fee:
probit_taker_fee:
diff --git a/hummingbot/templates/conf_global_TEMPLATE.yml b/hummingbot/templates/conf_global_TEMPLATE.yml
index cb459640eb..84946fb27f 100644
--- a/hummingbot/templates/conf_global_TEMPLATE.yml
+++ b/hummingbot/templates/conf_global_TEMPLATE.yml
@@ -69,8 +69,8 @@ okex_api_key: null
okex_secret_key: null
okex_passphrase: null
-bitmax_api_key: null
-bitmax_secret_key: null
+ascend_ex_api_key: null
+ascend_ex_secret_key: null
celo_address: null
celo_password: null
@@ -94,7 +94,7 @@ ethereum_wallet: null
ethereum_rpc_url: null
ethereum_rpc_ws_url: null
ethereum_chain_name: MAIN_NET
-ethereum_token_list_url: null
+ethereum_token_list_url: https://wispy-bird-88a7.uniswap.workers.dev/?url=http://tokens.1inch.eth.link
# Kill switch
kill_switch_enabled: null
@@ -175,14 +175,7 @@ balance_asset_limit:
# Fixed gas price (in Gwei) for Ethereum transactions
manual_gas_price:
-# To enable gas price lookup (true/false)
-ethgasstation_gas_enabled:
-# API key for defipulse.com gas station API
-ethgasstation_api_key:
-# Gas level you want to use for Ethereum transactions (fast, fastest, safeLow, average)
-ethgasstation_gas_level:
-# Refresh time for Ethereum gas price lookups (in seconds)
-ethgasstation_refresh_time:
+
# Gateway API Configurations
# default host to only use localhost
# Port need to match the final installation port for Gateway
diff --git a/installation/docker-commands/create-gateway.sh b/installation/docker-commands/create-gateway.sh
index c6db0bae8c..49fc98579c 100755
--- a/installation/docker-commands/create-gateway.sh
+++ b/installation/docker-commands/create-gateway.sh
@@ -40,7 +40,7 @@ else
else
echo "‼️ hummingbot_conf & hummingbot_certs directory missing from path $FOLDER"
prompt_hummingbot_data_path
- fi
+ fi
if [[ -f "$FOLDER/hummingbot_certs/server_cert.pem" && -f "$FOLDER/hummingbot_certs/server_key.pem" && -f "$FOLDER/hummingbot_certs/ca_cert.pem" ]]; then
echo
@@ -79,70 +79,208 @@ do
then
HUMMINGBOT_INSTANCE_ID="$(echo -e "${value}" | tr -d '[:space:]')"
fi
- # chain
- if [ "$key" == "ethereum_chain_name" ]
+ #
+done < "$GLOBAL_CONFIG"
+}
+read_global_config
+
+# prompt to setup balancer, uniswap
+prompt_ethereum_setup () {
+ read -p " Do you want to setup Balancer or Uniswap? [Y/N] (default \"Y\") >>> " PROCEED
+ if [[ "$PROCEED" == "Y" || "$PROCEED" == "y" || "$PROCEED" == "" ]]
then
- ETHEREUM_CHAIN="$(echo -e "${value}" | tr -d '[:space:]')"
- # subgraph url
- if [[ "$ETHEREUM_CHAIN" == "MAIN_NET" || "$ETHEREUM_CHAIN" == "main_net" || "$ETHEREUM_CHAIN" == "MAINNET" || "$ETHEREUM_CHAIN" == "mainnet" ]]
+ ETHEREUM_SETUP=true
+ echo
+ read -p " Enter Ethereum chain you want to use [mainnet/kovan] (default = \"mainnet\") >>> " ETHEREUM_CHAIN
+ # chain selection
+ if [ "$ETHEREUM_CHAIN" == "" ]
+ then
+ ETHEREUM_CHAIN="mainnet"
+ fi
+ if [[ "$ETHEREUM_CHAIN" != "mainnet" && "$ETHEREUM_CHAIN" != "kovan" ]]
+ then
+ echo "‼️ ERROR. Unsupported chains (mainnet/kovan). "
+ prompt_ethereum_setup
+ fi
+ # set subgraph url, exchange_proxy
+ if [[ "$ETHEREUM_CHAIN" == "mainnet" ]]
then
ETHEREUM_CHAIN="mainnet"
REACT_APP_SUBGRAPH_URL="https://api.thegraph.com/subgraphs/name/balancer-labs/balancer"
EXCHANGE_PROXY="0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21"
else
- ETHEREUM_CHAIN="kovan"
- REACT_APP_SUBGRAPH_URL="https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-kovan"
- EXCHANGE_PROXY="0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec"
+ if [[ "$ETHEREUM_CHAIN" == "kovan" ]]
+ then
+ REACT_APP_SUBGRAPH_URL="https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-kovan"
+ EXCHANGE_PROXY="0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec"
+ fi
fi
fi
- # ethereum rpc url
- if [ "$key" == "ethereum_rpc_url" ]
+}
+prompt_ethereum_setup
+
+# prompt to ethereum rpc
+prompt_ethereum_rpc_setup () {
+ if [ "$ETHEREUM_RPC_URL" == "" ]
then
- ETHEREUM_RPC_URL="$(echo -e "${value}" | tr -d '[:space:]')"
+ read -p " Enter the Ethereum RPC node URL to connect to >>> " ETHEREUM_RPC_URL
+ if [ "$ETHEREUM_RPC_URL" == "" ]
+ then
+ prompt_ethereum_rpc_setup
+ fi
+ else
+ read -p " Use the this Ethereum RPC node ($ETHEREUM_RPC_URL) setup in Hummingbot client? [Y/N] (default = \"Y\") >>> " PROCEED
+ if [[ "$PROCEED" == "Y" || "$PROCEED" == "y" || "$PROCEED" == "" ]]
+ then
+ echo
+ else
+ ETHEREUM_RPC_URL=""
+ prompt_ethereum_rpc_setup
+ fi
fi
-done < "$GLOBAL_CONFIG"
}
-read_global_config
+prompt_ethereum_rpc_setup
-# prompt to setup balancer, uniswap
-prompt_ethereum_setup () {
- read -p " Do you want to setup Balancer/Uniswap/Perpetual Finance? [Y/N] >>> " PROCEED
- if [[ "$PROCEED" == "Y" || "$PROCEED" == "y" ]]
+# prompt to setup ethereum token list
+prompt_token_list_source () {
+ echo
+ echo " Enter the token list url available at https://tokenlists.org/"
+ read -p " (default = \"https://wispy-bird-88a7.uniswap.workers.dev/?url=http://tokens.1inch.eth.link\") >>> " ETHEREUM_TOKEN_LIST_URL
+ if [ "$ETHEREUM_TOKEN_LIST_URL" == "" ]
then
+ echo
echo "ℹ️ Retrieving config from Hummingbot config file ... "
ETHEREUM_SETUP=true
+ ETHEREUM_TOKEN_LIST_URL=https://wispy-bird-88a7.uniswap.workers.dev/?url=http://tokens.1inch.eth.link
+ fi
+}
+prompt_token_list_source
+
+# prompt to setup eth gas level
+prompt_eth_gasstation_gas_level () {
+ echo
+ read -p " Enter gas level you want to use for Ethereum transactions (fast, fastest, safeLow, average) (default = \"fast\") >>> " ETH_GAS_STATION_GAS_LEVEL
+ if [ "$ETH_GAS_STATION_GAS_LEVEL" == "" ]
+ then
+ ETH_GAS_STATION_GAS_LEVEL=fast
+ else
+ if [[ "$ETH_GAS_STATION_GAS_LEVEL" != "fast" && "$ETH_GAS_STATION_GAS_LEVEL" != "fastest" && "$ETH_GAS_STATION_GAS_LEVEL" != "safeLow" && "$ETH_GAS_STATION_GAS_LEVEL" != "safelow" && "$ETH_GAS_STATION_GAS_LEVEL" != "average" ]]
+ then
+ prompt_eth_gasstation_gas_level
+ fi
+ fi
+}
+
+# prompt to setup eth gas station
+prompt_eth_gasstation_setup () {
+ echo
+ read -p " Enable dynamic Ethereum gas price lookup? [Y/N] (default = \"Y\") >>> " PROCEED
+ if [[ "$PROCEED" == "Y" || "$PROCEED" == "y" || "$PROCEED" == "" ]]
+ then
+ ENABLE_ETH_GAS_STATION=true
+ read -p " Enter API key for Eth Gas Station (https://ethgasstation.info/) >>> " ETH_GAS_STATION_API_KEY
+ if [ "$ETH_GAS_STATION_API_KEY" == "" ]
+ then
+ prompt_eth_gasstation_setup
+ else
+ # set gas level
+ prompt_eth_gasstation_gas_level
+
+ # set refresh interval
+ read -p " Enter refresh time for Ethereum gas price lookup (in seconds) (default = \"120\") >>> " ETH_GAS_STATION_REFRESH_TIME
+ if [ "$ETH_GAS_STATION_REFRESH_TIME" == "" ]
+ then
+ ETH_GAS_STATION_REFRESH_TIME=120
+ fi
+ fi
+ else
+ if [[ "$PROCEED" == "N" || "$PROCEED" == "n" ]]
+ then
+ ENABLE_ETH_GAS_STATION=false
+ # set manual gas price
+ read -p " Enter fixed gas price (in Gwei) you want to use for Ethereum transactions (default = \"100\") >>> " MANUAL_GAS_PRICE
+ if [ "$MANUAL_GAS_PRICE" == "" ]
+ then
+ MANUAL_GAS_PRICE=100
+ fi
+ else
+ prompt_eth_gasstation_setup
+ fi
+ fi
+ echo
+}
+prompt_eth_gasstation_setup
+
+prompt_balancer_setup () {
+ # Ask the user for the Balancer specific settings
+ echo "ℹ️ Balancer setting "
+ read -p " Enter the maximum Balancer swap pool (default = \"4\") >>> " BALANCER_MAX_SWAPS
+ if [ "$BALANCER_MAX_SWAPS" == "" ]
+ then
+ BALANCER_MAX_SWAPS="4"
echo
fi
}
-prompt_ethereum_setup
-# Ask the user for ethereum network
-prompt_terra_network () {
-read -p " Enter Terra chain you want to use [mainnet/testnet] (default = \"mainnet\") >>> " TERRA
-# chain selection
-if [ "$TERRA" == "" ]
-then
- TERRA="mainnet"
-fi
-if [[ "$TERRA" != "mainnet" && "$TERRA" != "testnet" ]]
-then
- echo "‼️ ERROR. Unsupported chains (mainnet/testnet). "
- prompt_terra_network
-fi
-# setup chain params
-if [[ "$TERRA" == "mainnet" ]]
-then
- TERRA_LCD_URL="https://lcd.terra.dev"
- TERRA_CHAIN="columbus-4"
-elif [ "$TERRA" == "testnet" ]
+prompt_uniswap_setup () {
+ # Ask the user for the Uniswap specific settings
+ echo "ℹ️ Uniswap setting "
+ read -p " Enter the allowed slippage for swap transactions (default = \"1.5\") >>> " UNISWAP_SLIPPAGE
+ if [ "$UNISWAP_SLIPPAGE" == "" ]
+ then
+ UNISWAP_SLIPPAGE="1.5"
+ echo
+ fi
+}
+
+if [[ "$ETHEREUM_SETUP" == true ]]
then
- TERRA_LCD_URL="https://tequila-lcd.terra.dev"
- TERRA_CHAIN="tequila-0004"
+ prompt_balancer_setup
+ prompt_uniswap_setup
fi
+
+prompt_xdai_setup () {
+ # Ask the user for the Uniswap specific settings
+ echo "ℹ️ XDAI setting "
+ read -p " Enter preferred XDAI rpc provider (default = \"https://rpc.xdaichain.com\") >>> " XDAI_PROVIDER
+ if [ "$XDAI_PROVIDER" == "" ]
+ then
+ XDAI_PROVIDER="https://rpc.xdaichain.com"
+ echo
+ fi
}
+prompt_xdai_setup
+
+# Ask the user for ethereum network
+prompt_terra_network () {
+ echo
+ read -p " Enter Terra chain you want to use [mainnet/testnet] (default = \"mainnet\") >>> " TERRA
+ # chain selection
+ if [ "$TERRA" == "" ]
+ then
+ TERRA="mainnet"
+ fi
+ if [[ "$TERRA" != "mainnet" && "$TERRA" != "testnet" ]]
+ then
+ echo "‼️ ERROR. Unsupported chains (mainnet/testnet). "
+ prompt_terra_network
+ fi
+ # setup chain params
+ if [[ "$TERRA" == "mainnet" ]]
+ then
+ TERRA_LCD_URL="https://lcd.terra.dev"
+ TERRA_CHAIN="columbus-4"
+ elif [ "$TERRA" == "testnet" ]
+ then
+ TERRA_LCD_URL="https://tequila-lcd.terra.dev"
+ TERRA_CHAIN="tequila-0004"
+ fi
+}
+
prompt_terra_setup () {
- read -p " Do you want to setup Terra? [Y/N] >>> " PROCEED
- if [[ "$PROCEED" == "Y" || "$PROCEED" == "y" ]]
+ echo
+ read -p " Do you want to setup Terra? [Y/N] (default \"Y\") >>> " PROCEED
+ if [[ "$PROCEED" == "Y" || "$PROCEED" == "y" || "$PROCEED" == "" ]]
then
TERRA_SETUP=true
prompt_terra_network
@@ -202,9 +340,17 @@ echo
printf "%30s %5s\n" "Hummingbot Instance ID:" "$HUMMINGBOT_INSTANCE_ID"
printf "%30s %5s\n" "Ethereum Chain:" "$ETHEREUM_CHAIN"
printf "%30s %5s\n" "Ethereum RPC URL:" "$ETHEREUM_RPC_URL"
+printf "%30s %5s\n" "Ethereum Token List URL:" "$ETHEREUM_TOKEN_LIST_URL"
+printf "%30s %5s\n" "Manual Gas Price:" "$MANUAL_GAS_PRICE"
+printf "%30s %5s\n" "Enable Eth Gas Station:" "$ENABLE_ETH_GAS_STATION"
+printf "%30s %5s\n" "Eth Gas Station API:" "$ETH_GAS_STATION_API_KEY"
+printf "%30s %5s\n" "Eth Gas Station Level:" "$ETH_GAS_STATION_GAS_LEVEL"
+printf "%30s %5s\n" "Eth Gas Station Refresh Interval:" "$ETH_GAS_STATION_REFRESH_TIME"
printf "%30s %5s\n" "Balancer Subgraph:" "$REACT_APP_SUBGRAPH_URL"
printf "%30s %5s\n" "Balancer Exchange Proxy:" "$EXCHANGE_PROXY"
+printf "%30s %5s\n" "Balancer Max Swaps:" "$BALANCER_MAX_SWAPS"
printf "%30s %5s\n" "Uniswap Router:" "$UNISWAP_ROUTER"
+printf "%30s %5s\n" "Uniswap Allowed Slippage:" "$UNISWAP_SLIPPAGE"
printf "%30s %5s\n" "Terra Chain:" "$TERRA"
printf "%30s %5s\n" "Gateway Log Path:" "$LOG_PATH"
printf "%30s %5s\n" "Gateway Cert Path:" "$CERT_PATH"
@@ -220,13 +366,46 @@ echo "NODE_ENV=prod" >> $ENV_FILE
echo "PORT=$PORT" >> $ENV_FILE
echo "" >> $ENV_FILE
echo "HUMMINGBOT_INSTANCE_ID=$HUMMINGBOT_INSTANCE_ID" >> $ENV_FILE
+
+# ethereum config
+echo "" >> $ENV_FILE
+echo "# Ethereum Settings" >> $ENV_FILE
echo "ETHEREUM_CHAIN=$ETHEREUM_CHAIN" >> $ENV_FILE
echo "ETHEREUM_RPC_URL=$ETHEREUM_RPC_URL" >> $ENV_FILE
+echo "ETHEREUM_TOKEN_LIST_URL=$ETHEREUM_TOKEN_LIST_URL" >> $ENV_FILE
+echo "" >> $ENV_FILE
+echo "ENABLE_ETH_GAS_STATION=$ENABLE_ETH_GAS_STATION" >> $ENV_FILE
+echo "ETH_GAS_STATION_API_KEY=$ETH_GAS_STATION_API_KEY" >> $ENV_FILE
+echo "ETH_GAS_STATION_GAS_LEVEL=$ETH_GAS_STATION_GAS_LEVEL" >> $ENV_FILE
+echo "ETH_GAS_STATION_REFRESH_TIME=$ETH_GAS_STATION_REFRESH_TIME" >> $ENV_FILE
+echo "MANUAL_GAS_PRICE=$MANUAL_GAS_PRICE" >> $ENV_FILE
+
+# balancer config
+echo "" >> $ENV_FILE
+echo "# Balancer Settings" >> $ENV_FILE
echo "REACT_APP_SUBGRAPH_URL=$REACT_APP_SUBGRAPH_URL" >> $ENV_FILE # must used "REACT_APP_SUBGRAPH_URL" for balancer-sor
echo "EXCHANGE_PROXY=$EXCHANGE_PROXY" >> $ENV_FILE
+echo "BALANCER_MAX_SWAPS=$BALANCER_MAX_SWAPS" >> $ENV_FILE
+
+# uniswap config
+echo "" >> $ENV_FILE
+echo "# Uniswap Settings" >> $ENV_FILE
echo "UNISWAP_ROUTER=$UNISWAP_ROUTER" >> $ENV_FILE
+echo "UNISWAP_ALLOWED_SLIPPAGE=$UNISWAP_SLIPPAGE" >> $ENV_FILE
+echo "UNISWAP_NO_RESERVE_CHECK_INTERVAL=300000" >> $ENV_FILE
+echo "UNISWAP_PAIRS_CACHE_TIME=1000" >> $ENV_FILE
+
+# terra config
+echo "" >> $ENV_FILE
+echo "# Terra Settings" >> $ENV_FILE
echo "TERRA_LCD_URL=$TERRA_LCD_URL" >> $ENV_FILE
echo "TERRA_CHAIN=$TERRA_CHAIN" >> $ENV_FILE
+
+# perpeptual finance config
+echo "" >> $ENV_FILE
+echo "# Perpeptual Settings" >> $ENV_FILE
+echo "XDAI_PROVIDER=$XDAI_PROVIDER" >> $ENV_FILE
+
echo "" >> $ENV_FILE
prompt_proceed () {
@@ -234,7 +413,12 @@ prompt_proceed () {
read -p " Do you want to proceed with installation? [Y/N] >>> " PROCEED
if [ "$PROCEED" == "" ]
then
- PROCEED="Y"
+ prompt_proceed
+ else
+ if [[ "$PROCEED" != "Y" && "$PROCEED" != "y" ]]
+ then
+ PROCEED="N"
+ fi
fi
}
diff --git a/setup.py b/setup.py
index 083859ad4c..6ad90e8f60 100755
--- a/setup.py
+++ b/setup.py
@@ -39,6 +39,7 @@ def main():
"hummingbot.connector.connector.balancer",
"hummingbot.connector.connector.terra",
"hummingbot.connector.exchange",
+ "hummingbot.connector.exchange.ascend_ex",
"hummingbot.connector.exchange.binance",
"hummingbot.connector.exchange.bitfinex",
"hummingbot.connector.exchange.bittrex",
@@ -56,7 +57,6 @@ def main():
"hummingbot.connector.exchange.dolomite",
"hummingbot.connector.exchange.eterbase",
"hummingbot.connector.exchange.beaxy",
- "hummingbot.connector.exchange.bitmax",
"hummingbot.connector.exchange.hitbtc",
"hummingbot.connector.derivative",
"hummingbot.connector.derivative.binance_perpetual",
diff --git a/test/connector/connector/balancer/test_balancer_connector.py b/test/connector/connector/balancer/test_balancer_connector.py
index 151d0e403e..66ad53df97 100644
--- a/test/connector/connector/balancer/test_balancer_connector.py
+++ b/test/connector/connector/balancer/test_balancer_connector.py
@@ -28,8 +28,7 @@
global_config_map['gateway_api_host'].value = "localhost"
global_config_map['gateway_api_port'].value = 5000
-global_config_map['ethgasstation_gas_enabled'].value = False
-global_config_map['manual_gas_price'].value = 50
+global_config_map['ethereum_token_list_url'].value = "https://defi.cmc.eth.link"
global_config_map.get("ethereum_chain_name").value = "kovan"
trading_pair = "WETH-DAI"
@@ -96,6 +95,14 @@ def setUp(self):
for event_tag in self.events:
self.connector.add_listener(event_tag, self.event_logger)
+ def test_fetch_trading_pairs(self):
+ asyncio.get_event_loop().run_until_complete(self._test_fetch_trading_pairs())
+
+ async def _test_fetch_trading_pairs(self):
+ pairs = await BalancerConnector.fetch_trading_pairs()
+ print(pairs)
+ self.assertGreater(len(pairs), 0)
+
def test_update_balances(self):
all_bals = self.connector.get_all_balances()
for token, bal in all_bals.items():
diff --git a/test/connector/exchange/bitmax/.gitignore b/test/connector/exchange/ascend_ex/.gitignore
similarity index 100%
rename from test/connector/exchange/bitmax/.gitignore
rename to test/connector/exchange/ascend_ex/.gitignore
diff --git a/test/connector/exchange/bitmax/__init__.py b/test/connector/exchange/ascend_ex/__init__.py
similarity index 100%
rename from test/connector/exchange/bitmax/__init__.py
rename to test/connector/exchange/ascend_ex/__init__.py
diff --git a/test/connector/exchange/bitmax/test_bitmax_auth.py b/test/connector/exchange/ascend_ex/test_ascend_ex_auth.py
similarity index 72%
rename from test/connector/exchange/bitmax/test_bitmax_auth.py
rename to test/connector/exchange/ascend_ex/test_ascend_ex_auth.py
index 46a68bfae7..59d866ad87 100644
--- a/test/connector/exchange/bitmax/test_bitmax_auth.py
+++ b/test/connector/exchange/ascend_ex/test_ascend_ex_auth.py
@@ -9,21 +9,22 @@
import logging
from os.path import join, realpath
from typing import Dict, Any
-from hummingbot.connector.exchange.bitmax.bitmax_auth import BitmaxAuth
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth
from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL
-from hummingbot.connector.exchange.bitmax.bitmax_constants import REST_URL, getWsUrlPriv
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_constants import REST_URL
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_util import get_rest_url_private
sys.path.insert(0, realpath(join(__file__, "../../../../../")))
logging.basicConfig(level=METRICS_LOG_LEVEL)
-class TestAuth(unittest.TestCase):
+class TestAscendExAuth(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
- api_key = conf.bitmax_api_key
- secret_key = conf.bitmax_secret_key
- cls.auth = BitmaxAuth(api_key, secret_key)
+ api_key = conf.ascend_ex_api_key
+ secret_key = conf.ascend_ex_secret_key
+ cls.auth = AscendExAuth(api_key, secret_key)
async def rest_auth(self) -> Dict[Any, Any]:
headers = {
@@ -37,7 +38,7 @@ async def ws_auth(self) -> Dict[Any, Any]:
info = await self.rest_auth()
accountGroup = info.get("data").get("accountGroup")
headers = self.auth.get_auth_headers("stream")
- ws = await websockets.connect(f"{getWsUrlPriv(accountGroup)}/stream", extra_headers=headers)
+ ws = await websockets.connect(f"{get_rest_url_private(accountGroup)}/stream", extra_headers=headers)
raw_msg = await asyncio.wait_for(ws.recv(), 5000)
msg = ujson.loads(raw_msg)
diff --git a/test/connector/exchange/bitmax/test_bitmax_exchange.py b/test/connector/exchange/ascend_ex/test_ascend_ex_exchange.py
similarity index 97%
rename from test/connector/exchange/bitmax/test_bitmax_exchange.py
rename to test/connector/exchange/ascend_ex/test_ascend_ex_exchange.py
index ca642fe385..e3c928520d 100644
--- a/test/connector/exchange/bitmax/test_bitmax_exchange.py
+++ b/test/connector/exchange/ascend_ex/test_ascend_ex_exchange.py
@@ -1,15 +1,17 @@
from os.path import join, realpath
import sys; sys.path.insert(0, realpath(join(__file__, "../../../../../")))
+
import asyncio
-import logging
-from decimal import Decimal
-import unittest
+import conf
import contextlib
-import time
+import logging
+import math
import os
+import time
+import unittest
+
+from decimal import Decimal
from typing import List
-import conf
-import math
from hummingbot.core.clock import Clock, ClockMode
from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL
@@ -33,15 +35,15 @@
from hummingbot.model.order import Order
from hummingbot.model.trade_fill import TradeFill
from hummingbot.connector.markets_recorder import MarketsRecorder
-from hummingbot.connector.exchange.bitmax.bitmax_exchange import BitmaxExchange
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_exchange import AscendExExchange
logging.basicConfig(level=METRICS_LOG_LEVEL)
-API_KEY = conf.bitmax_api_key
-API_SECRET = conf.bitmax_secret_key
+API_KEY = conf.ascend_ex_api_key
+API_SECRET = conf.ascend_ex_secret_key
-class BitmaxExchangeUnitTest(unittest.TestCase):
+class AscendExExchangeUnitTest(unittest.TestCase):
events: List[MarketEvent] = [
MarketEvent.BuyOrderCompleted,
MarketEvent.SellOrderCompleted,
@@ -52,7 +54,7 @@ class BitmaxExchangeUnitTest(unittest.TestCase):
MarketEvent.OrderCancelled,
MarketEvent.OrderFailure
]
- connector: BitmaxExchange
+ connector: AscendExExchange
event_logger: EventLogger
trading_pair = "BTC-USDT"
base_token, quote_token = trading_pair.split("-")
@@ -65,13 +67,13 @@ def setUpClass(cls):
cls.ev_loop = asyncio.get_event_loop()
cls.clock: Clock = Clock(ClockMode.REALTIME)
- cls.connector: BitmaxExchange = BitmaxExchange(
- bitmax_api_key=API_KEY,
- bitmax_secret_key=API_SECRET,
+ cls.connector: AscendExExchange = AscendExExchange(
+ ascend_ex_api_key=API_KEY,
+ ascend_ex_secret_key=API_SECRET,
trading_pairs=[cls.trading_pair],
trading_required=True
)
- print("Initializing Bitmax market... this will take about a minute.")
+ print("Initializing AscendEx exchange... this will take about a minute.")
cls.clock.add_iterator(cls.connector)
cls.stack: contextlib.ExitStack = contextlib.ExitStack()
cls._clock = cls.stack.enter_context(cls.clock)
@@ -336,7 +338,7 @@ def test_orders_saving_and_restoration(self):
self.clock.remove_iterator(self.connector)
for event_tag in self.events:
self.connector.remove_listener(event_tag, self.event_logger)
- new_connector = BitmaxExchange(API_KEY, API_SECRET, [self.trading_pair], True)
+ new_connector = AscendExExchange(API_KEY, API_SECRET, [self.trading_pair], True)
for event_tag in self.events:
new_connector.add_listener(event_tag, self.event_logger)
recorder.stop()
diff --git a/test/connector/exchange/bitmax/test_bitmax_order_book_tracker.py b/test/connector/exchange/ascend_ex/test_ascend_ex_order_book_tracker.py
similarity index 88%
rename from test/connector/exchange/bitmax/test_bitmax_order_book_tracker.py
rename to test/connector/exchange/ascend_ex/test_ascend_ex_order_book_tracker.py
index 331860fbba..ed19775c0d 100755
--- a/test/connector/exchange/bitmax/test_bitmax_order_book_tracker.py
+++ b/test/connector/exchange/ascend_ex/test_ascend_ex_order_book_tracker.py
@@ -9,8 +9,8 @@
from typing import Dict, Optional, List
from hummingbot.core.event.event_logger import EventLogger
from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent, TradeType
-from hummingbot.connector.exchange.bitmax.bitmax_order_book_tracker import BitmaxOrderBookTracker
-from hummingbot.connector.exchange.bitmax.bitmax_api_order_book_data_source import BitmaxAPIOrderBookDataSource
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_order_book_tracker import AscendExOrderBookTracker
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_api_order_book_data_source import AscendExAPIOrderBookDataSource
from hummingbot.core.data_type.order_book import OrderBook
from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL
@@ -19,8 +19,8 @@
logging.basicConfig(level=METRICS_LOG_LEVEL)
-class BitmaxOrderBookTrackerUnitTest(unittest.TestCase):
- order_book_tracker: Optional[BitmaxOrderBookTracker] = None
+class AscendExOrderBookTrackerUnitTest(unittest.TestCase):
+ order_book_tracker: Optional[AscendExOrderBookTracker] = None
events: List[OrderBookEvent] = [
OrderBookEvent.TradeEvent
]
@@ -32,7 +32,7 @@ class BitmaxOrderBookTrackerUnitTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
- cls.order_book_tracker: BitmaxOrderBookTracker = BitmaxOrderBookTracker(cls.trading_pairs)
+ cls.order_book_tracker: AscendExOrderBookTracker = AscendExOrderBookTracker(cls.trading_pairs)
cls.order_book_tracker.start()
cls.ev_loop.run_until_complete(cls.wait_til_tracker_ready())
@@ -96,7 +96,7 @@ def test_tracker_integrity(self):
def test_api_get_last_traded_prices(self):
prices = self.ev_loop.run_until_complete(
- BitmaxAPIOrderBookDataSource.get_last_traded_prices(["BTC-USDT", "LTC-BTC"]))
+ AscendExAPIOrderBookDataSource.get_last_traded_prices(["BTC-USDT", "LTC-BTC"]))
for key, value in prices.items():
print(f"{key} last_trade_price: {value}")
self.assertGreater(prices["BTC-USDT"], 1000)
diff --git a/test/connector/exchange/bitmax/test_bitmax_user_stream_tracker.py b/test/connector/exchange/ascend_ex/test_ascend_ex_user_stream_tracker.py
similarity index 58%
rename from test/connector/exchange/bitmax/test_bitmax_user_stream_tracker.py
rename to test/connector/exchange/ascend_ex/test_ascend_ex_user_stream_tracker.py
index 1b4cb5c84b..ce107d09f2 100644
--- a/test/connector/exchange/bitmax/test_bitmax_user_stream_tracker.py
+++ b/test/connector/exchange/ascend_ex/test_ascend_ex_user_stream_tracker.py
@@ -6,8 +6,8 @@
import conf
from os.path import join, realpath
-from hummingbot.connector.exchange.bitmax.bitmax_user_stream_tracker import BitmaxUserStreamTracker
-from hummingbot.connector.exchange.bitmax.bitmax_auth import BitmaxAuth
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_user_stream_tracker import AscendExUserStreamTracker
+from hummingbot.connector.exchange.ascend_ex.ascend_ex_auth import AscendExAuth
from hummingbot.core.utils.async_utils import safe_ensure_future
from hummingbot.logger.struct_logger import METRICS_LOG_LEVEL
@@ -16,17 +16,17 @@
logging.basicConfig(level=METRICS_LOG_LEVEL)
-class BitmaxUserStreamTrackerUnitTest(unittest.TestCase):
- api_key = conf.bitmax_api_key
- api_secret = conf.bitmax_secret_key
+class AscendExUserStreamTrackerUnitTest(unittest.TestCase):
+ api_key = conf.ascend_ex_api_key
+ api_secret = conf.ascend_ex_secret_key
@classmethod
def setUpClass(cls):
cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()
- cls.bitmax_auth = BitmaxAuth(cls.api_key, cls.api_secret)
+ cls.ascend_ex_auth = AscendExAuth(cls.api_key, cls.api_secret)
cls.trading_pairs = ["BTC-USDT"]
- cls.user_stream_tracker: BitmaxUserStreamTracker = BitmaxUserStreamTracker(
- bitmax_auth=cls.bitmax_auth, trading_pairs=cls.trading_pairs)
+ cls.user_stream_tracker: AscendExUserStreamTracker = AscendExUserStreamTracker(
+ ascend_ex_auth=cls.ascend_ex_auth, trading_pairs=cls.trading_pairs)
cls.user_stream_tracker_task: asyncio.Task = safe_ensure_future(cls.user_stream_tracker.start())
def test_user_stream(self):