Skip to content

Commit

Permalink
Merge pull request #6535 from yourtrading-ai/feat/xrpl-connector-v2
Browse files Browse the repository at this point in the history
Feat/XRPL connector v2
  • Loading branch information
rapcmia committed Dec 1, 2023
2 parents 0ca026a + 60523b7 commit 0b26b58
Show file tree
Hide file tree
Showing 11 changed files with 925 additions and 3 deletions.
2 changes: 1 addition & 1 deletion hummingbot/client/command/start_command.py
Expand Up @@ -241,7 +241,7 @@ async def start_market_making(self, # type: HummingbotApplication
self.markets_recorder.restore_market_states(self.strategy_file_name, market)
if len(market.limit_orders) > 0:
self.notify(f"Canceling dangling limit orders on {market.name}...")
await market.cancel_all(5.0)
await market.cancel_all(10.0)
if self.strategy:
self.clock.add_iterator(self.strategy)
try:
Expand Down
2 changes: 1 addition & 1 deletion hummingbot/client/hummingbot_application.py
Expand Up @@ -49,7 +49,7 @@


class HummingbotApplication(*commands):
KILL_TIMEOUT = 10.0
KILL_TIMEOUT = 20.0
APP_WARNING_EXPIRY_DURATION = 3600.0
APP_WARNING_STATUS_LIMIT = 6

Expand Down
Empty file.

Large diffs are not rendered by default.

@@ -0,0 +1,42 @@
from bidict import bidict

from hummingbot.core.data_type.common import TradeType
from hummingbot.core.data_type.in_flight_order import OrderState

CONNECTOR_NAME = "xrpl"

MAX_ID_HEX_DIGITS = 16
MAX_ID_BIT_COUNT = MAX_ID_HEX_DIGITS * 4

BASE_PATH_URL = {
"mainnet": "https://xrplcluster.com/",
"testnet": "https://s.altnet.rippletest.net:51234/",
"devnet": "https://s.devnet.rippletest.net:51234/",
"amm-devnet": "https://amm.devnet.rippletest.net:51234/"
}

WS_PATH_URL = {
"mainnet": "wss://xrplcluster.com/",
"testnet": "wss://s.altnet.rippletest.net/",
"devnet": "wss://s.devnet.rippletest.net:51233/",
"amm-devnet": "wss://amm.devnet.rippletest.net:51233/ "
}

ORDER_SIDE_MAP = bidict(
{
"BUY": TradeType.BUY,
"SELL": TradeType.SELL
}
)

XRPL_TO_HB_STATUS_MAP = {
"OPEN": OrderState.OPEN,
"PENDING_OPEN": OrderState.PENDING_CREATE,
"PENDING_CANCEL": OrderState.PENDING_CANCEL,
"OFFER_EXPIRED_OR_UNFUNDED": OrderState.CANCELED,
"UNKNOWN": OrderState.FAILED,
"FAILED": OrderState.FAILED,
"PARTIALLY_FILLED": OrderState.PARTIALLY_FILLED,
"FILLED": OrderState.FILLED,
"CANCELED": OrderState.CANCELED,
}
2 changes: 1 addition & 1 deletion hummingbot/connector/gateway/gateway_in_flight_order.py
Expand Up @@ -9,7 +9,7 @@
from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate

GET_GATEWAY_EX_ORDER_ID_TIMEOUT = 30 # seconds
GET_GATEWAY_TX_HASH = 1 # seconds
GET_GATEWAY_TX_HASH = 10 # seconds

s_decimal_0 = Decimal("0")

Expand Down
1 change: 1 addition & 0 deletions hummingbot/core/utils/gateway_config_utils.py
Expand Up @@ -16,6 +16,7 @@
"injective": "INJ",
"xdc": "XDC",
"tezos": "XTZ",
"xrpl": "XRP",
"kujira": "KUJI"
}

Expand Down
1 change: 1 addition & 0 deletions setup/environment.yml
Expand Up @@ -65,3 +65,4 @@ dependencies:
- yarl==1.*
- git+https://github.com/CoinAlpha/python-signalr-client.git
- git+https://github.com/konichuvak/dydx-v3-python.git@web3
- xrpl-py
Empty file.

Large diffs are not rendered by default.

@@ -0,0 +1,182 @@
import asyncio
from decimal import Decimal
from typing import List, Optional, Tuple
from unittest.mock import AsyncMock, patch


class XrplClientMock:
def __init__(
self, initial_timestamp: float, wallet_address: str, base: str, quote: str,
):
self.initial_timestamp = initial_timestamp
self.base = base
self.base_coin_issuer = "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx"
self.base_decimals = 15
self.quote = quote
self.quote_coin_issuer = "rh8LssQyeBdEXk7Zv86HxHrx8k2R2DBUrx"
self.quote_decimals = 8
self.market_id = f'{base}-{quote}'
self.wallet_address = wallet_address

self.gateway_instance_mock_patch = patch(
target=(
"hummingbot.connector.gateway.clob_spot.data_sources.xrpl.xrpl_api_data_source"
".GatewayHttpClient"
),
autospec=True,
)

self.gateway_instance_mock: Optional[AsyncMock] = None

self.place_order_called_event = asyncio.Event()
self.cancel_order_called_event = asyncio.Event()
self.update_market_called_event = asyncio.Event()
self.update_ticker_called_event = asyncio.Event()
self.update_balances_called_event = asyncio.Event()
self.orderbook_snapshot_called_event = asyncio.Event()
self.transaction_status_update_called_event = asyncio.Event()

def start(self):
self.gateway_instance_mock = self.gateway_instance_mock_patch.start()
self.gateway_instance_mock.get_instance.return_value = self.gateway_instance_mock

def stop(self):
self.gateway_instance_mock_patch.stop()

def run_until_place_order_called(self, timeout: float = 1):
asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fut=self.place_order_called_event.wait(), timeout=timeout)
)

def run_until_cancel_order_called(self, timeout: float = 1):
asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fut=self.cancel_order_called_event.wait(), timeout=timeout)
)

def run_until_update_market_called(self, timeout: float = 1):
asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fut=self.update_market_called_event.wait(), timeout=timeout)
)

def run_until_transaction_status_update_called(self, timeout: float = 1):
asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fut=self.transaction_status_update_called_event.wait(), timeout=timeout)
)

def run_until_update_ticker_called(self, timeout: float = 1):
asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fut=self.update_ticker_called_event.wait(), timeout=timeout)
)

def run_until_orderbook_snapshot_called(self, timeout: float = 1):
asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fut=self.orderbook_snapshot_called_event.wait(), timeout=timeout)
)

def run_until_update_balances_called(self, timeout: float = 1):
asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fut=self.update_balances_called_event.wait(), timeout=timeout)
)

def configure_place_order_response(
self,
timestamp: int,
transaction_hash: str,
exchange_order_id: str,
):
def place_and_return(*_, **__):
self.place_order_called_event.set()
return {
"network": "xrpl",
"timestamp": timestamp,
"latency": 2,
"txHash": transaction_hash,
}

def transaction_update_and_return(*_, **__):
self.transaction_status_update_called_event.set()
return {
"sequence": exchange_order_id
}

self.gateway_instance_mock.clob_place_order.side_effect = place_and_return
self.gateway_instance_mock.get_transaction_status.side_effect = transaction_update_and_return

def configure_cancel_order_response(self, timestamp: int, transaction_hash: str):
def cancel_and_return(*_, **__):
self.cancel_order_called_event.set()
return {
"network": "xrpl",
"timestamp": timestamp,
"latency": 2,
"txHash": transaction_hash,
}

self.gateway_instance_mock.clob_cancel_order.side_effect = cancel_and_return

def configure_trading_rules_response(self, minimum_order_size: str, base_transfer_rate: str,
quote_transfer_rate: str):
def update_market_and_return(*_, **__):
self.update_market_called_event.set()
return {
"markets": [
{"marketId": self.market_id,
"minimumOrderSize": minimum_order_size,
"smallestTickSize": str(min(self.base_decimals, self.quote_decimals)),
"baseTickSize": self.base_decimals,
"quoteTickSize": self.quote_decimals,
"baseTransferRate": base_transfer_rate,
"quoteTransferRate": quote_transfer_rate,
"baseIssuer": self.base_coin_issuer,
"quoteIssuer": self.quote_coin_issuer,
"baseCurrency": self.base,
"quoteCurrency": self.quote, }
],

}

self.gateway_instance_mock.get_clob_markets.side_effect = update_market_and_return

def configure_last_traded_price_response(self, price: str, trading_pair: str):
def update_market_and_return(*_, **__):
self.update_ticker_called_event.set()
return {
"markets": [
{
"marketId": trading_pair,
"midprice": price
}
]
}

self.gateway_instance_mock.get_clob_ticker.side_effect = update_market_and_return

def configure_orderbook_snapshot(self, timestamp: float, bids: List[Tuple[float, float]],
asks: List[Tuple[float, float]]):
def update_orderbook_and_return(*_, **__):
self.orderbook_snapshot_called_event.set()
transformed_bids = [{"price": price, "quantity": quantity} for price, quantity in bids]
transformed_asks = [{"price": price, "quantity": quantity} for price, quantity in asks]

return {
"timestamp": timestamp,
"buys": transformed_bids,
"sells": transformed_asks
}

self.gateway_instance_mock.get_clob_orderbook_snapshot.side_effect = update_orderbook_and_return

def configure_get_account_balances_response(self, base: str, quote: str,
base_balance: Decimal,
quote_balance: Decimal):
def update_balances_and_return(*_, **__):
self.update_balances_called_event.set()

return {
"balances": {
base: base_balance,
quote: quote_balance
}
}

self.gateway_instance_mock.get_balances.side_effect = update_balances_and_return

0 comments on commit 0b26b58

Please sign in to comment.