def test_healthy_cdp(self, web3, mcd, our_address): collateral = mcd.collaterals['ETH-B'] ilk = collateral.ilk TestVat.ensure_clean_urn(mcd, collateral, our_address) initial_dai = mcd.vat.dai(our_address) wrap_eth(mcd, our_address, Wad.from_number(9)) # Ensure our collateral enters the urn collateral_balance_before = collateral.gem.balance_of(our_address) collateral.approve(our_address) assert collateral.adapter.join(our_address, Wad.from_number(9)).transact() assert collateral.gem.balance_of( our_address) == collateral_balance_before - Wad.from_number(9) # Add collateral without generating Dai frob(mcd, collateral, our_address, dink=Wad.from_number(3), dart=Wad(0)) print( f"After adding collateral: {mcd.vat.urn(ilk, our_address)}" ) assert mcd.vat.urn(ilk, our_address).ink == Wad.from_number(3) assert mcd.vat.urn(ilk, our_address).art == Wad(0) assert mcd.vat.gem(ilk, our_address) == Wad.from_number(9) - mcd.vat.urn( ilk, our_address).ink assert mcd.vat.dai(our_address) == initial_dai # Generate some Dai frob(mcd, collateral, our_address, dink=Wad(0), dart=Wad.from_number(153)) print( f"After generating dai: {mcd.vat.urn(ilk, our_address)}" ) assert mcd.vat.urn(ilk, our_address).ink == Wad.from_number(3) assert mcd.vat.urn(ilk, our_address).art == Wad.from_number(153) assert mcd.vat.dai(our_address) == initial_dai + Rad.from_number(153) # Add collateral and generate some more Dai frob(mcd, collateral, our_address, dink=Wad.from_number(6), dart=Wad.from_number(180)) print( f"After adding collateral and dai: {mcd.vat.urn(ilk, our_address)}" ) assert mcd.vat.urn(ilk, our_address).ink == Wad.from_number(9) assert mcd.vat.gem(ilk, our_address) == Wad(0) assert mcd.vat.urn(ilk, our_address).art == Wad.from_number(333) assert mcd.vat.dai(our_address) == initial_dai + Rad.from_number(333) # Mint and withdraw our Dai dai_balance_before = mcd.dai.balance_of(our_address) mcd.approve_dai(our_address) assert isinstance(mcd.dai_adapter, DaiJoin) assert mcd.dai_adapter.exit(our_address, Wad.from_number(333)).transact() assert mcd.dai.balance_of( our_address) == dai_balance_before + Wad.from_number(333) assert mcd.vat.dai(our_address) == initial_dai assert mcd.vat.debt() >= initial_dai + Rad.from_number(333) # Repay (and burn) our Dai assert mcd.dai_adapter.join(our_address, Wad.from_number(333)).transact() assert mcd.dai.balance_of(our_address) == Wad(0) assert mcd.vat.dai(our_address) == initial_dai + Rad.from_number(333) # Withdraw our collateral frob(mcd, collateral, our_address, dink=Wad(0), dart=Wad.from_number(-333)) frob(mcd, collateral, our_address, dink=Wad.from_number(-9), dart=Wad(0)) assert mcd.vat.gem(ilk, our_address) == Wad.from_number(9) assert collateral.adapter.exit(our_address, Wad.from_number(9)).transact() collateral_balance_after = collateral.gem.balance_of(our_address) assert collateral_balance_before == collateral_balance_after # Cleanup cleanup_urn(mcd, collateral, our_address)
def get_price(self) -> Price: tub_price = Wad(self.ds_value.read_as_int()) return Price(buy_price=tub_price, sell_price=tub_price)
def gem(self, ilk: Ilk, urn: Address) -> Wad: assert isinstance(ilk, Ilk) assert isinstance(urn, Address) return Wad(self._contract.functions.gem(ilk.toBytes(), urn.address).call())
ilk = mcd.vat.ilk(collateral.ilk.name) dink = Wad.from_number(1) dart = Wad( Rad(dai) / Rad(ilk.rate)) wrap_eth(mcd, our_address, dink) assert collateral.gem.balance_of(our_address) >= dink assert collateral.gem.approve(collateral.adapter.address).transact(from_address=our_address) assert collateral.adapter.join(our_address, dink).transact(from_address=our_address) frob(mcd, collateral, our_address, dink=dink, dart=dart) # Exit to Dai Token and make some checks assert mcd.vat.hope(mcd.dai_adapter.address).transact(from_address=our_address) assert mcd.dai_adapter.exit(our_address, dai).transact(from_address=our_address) assert mcd.dai.balance_of(our_address) == dai + startingAmount pytest.global_dai = Wad(0) class TestDsrManager: def test_getters(self, mcd: DssDeployment): assert isinstance(mcd.dsr_manager.pot(), Pot) assert mcd.dsr_manager.pot().address.address == mcd.pot.address.address assert isinstance(mcd.dsr_manager.dai(), DSToken) assert mcd.dsr_manager.dai().address.address == mcd.dai.address.address assert isinstance(mcd.dsr_manager.dai_adapter(), DaiJoin) assert mcd.dsr_manager.dai_adapter().address.address == mcd.dai_adapter.address.address def test_join(self, mcd: DssDeployment, our_address: Address): # Mint 58 Dai and lock it in the Pot contract through DsrManager
def get_collateral_price(collateral: Collateral): assert isinstance(collateral, Collateral) return Wad(Web3.toInt(collateral.pip.read()))
def sum(self) -> Wad: """Total balance of MKR `join`ed to this contract""" return Wad(self._contract.functions.Sum().call())
def min(self) -> Wad: """Minimum amount of MKR required to call `fire`""" return Wad(self._contract.functions.min().call())
def __init__(self, args, **kwargs): """Pass in arguements assign necessary variables/objects and instantiate other Classes""" parser = argparse.ArgumentParser("simple-arbitrage-keeper") parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", type=int, default=8545, help="JSON-RPC port (default: `8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help= "Ethereum address from which to send transactions; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--eth-key", type=str, nargs='*', required=True, help= "Ethereum private key(s) to use (e.g. 'key_file=/path/to/keystore.json,pass_file=/path/to/passphrase.txt')" ) parser.add_argument( "--uniswap-entry-exchange", type=str, required=True, help= "Ethereum address of the Uniswap Exchange contract for the entry token market; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--uniswap-arb-exchange", type=str, required=True, help= "Ethereum address of the Uniswap Exchange contract for the arb token market; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--oasis-address", type=str, required=True, help= "Ethereum address of the OasisDEX contract; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--oasis-api-endpoint", type=str, required=True, help= "Endpoint of of the Oasis V2 REST API (e.g. 'https://kovan-api.oasisdex.com' )" ) parser.add_argument( "--relayer-per-page", type=int, default=100, help= "Number of orders to fetch per one page from the 0x Relayer API (default: 100)" ) parser.add_argument( "--tx-manager", type=str, required=True, help= "Ethereum address of the TxManager contract to use for multi-step arbitrage; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--gas-price", type=int, default=0, help= "Gas price in Wei (default: node default), (e.g. 1000000000 for 1 GWei)" ) parser.add_argument( "--entry-token", type=str, required=True, help= "The token address that the bot starts and ends with in every transaction; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--arb-token", type=str, required=True, help= "The token address that arbitraged between both exchanges; checksummed (e.g. '0x12AebC')" ) parser.add_argument( "--arb-token-name", type=str, required=True, help= "The token name that arbitraged between both exchanges (e.g. 'SAI', 'WETH', 'REP')" ) parser.add_argument( "--min-profit", type=int, required=True, help= "Ether amount of minimum profit (in base token) from one arbitrage operation (e.g. 1 for 1 Sai min profit)" ) parser.add_argument( "--max-engagement", type=int, required=True, help= "Ether amount of maximum engagement (in base token) in one arbitrage operation (e.g. 100 for 100 Sai max engagement)" ) parser.add_argument( "--max-errors", type=int, default=100, help= "Maximum number of allowed errors before the keeper terminates (default: 100)" ) parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3( HTTPProvider( endpoint_uri= f"https://{self.arguments.rpc_host}:{self.arguments.rpc_port}", request_kwargs={"timeout": self.arguments.rpc_timeout})) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) self.our_address = Address(self.arguments.eth_from) self.sai = ERC20Token( web3=self.web3, address=Address( '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359')) # Mainnet Sai self.dai = ERC20Token( web3=self.web3, address=Address( '0x6b175474e89094c44da98b954eedeac495271d0f')) # Mainnet Dai self.ksai = ERC20Token( web3=self.web3, address=Address( '0xC4375B7De8af5a38a93548eb8453a498222C4fF2')) #Kovan Sai self.kdai = ERC20Token( web3=self.web3, address=Address( '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa')) #Kovan Dai self.entry_token = ERC20Token(web3=self.web3, address=Address( self.arguments.entry_token)) self.arb_token = ERC20Token(web3=self.web3, address=Address(self.arguments.arb_token)) self.arb_token.name = self.arguments.arb_token_name \ if self.arguments.arb_token_name != 'WETH' else 'ETH' self.uniswap_entry_exchange = UniswapWrapper(self.web3, self.entry_token.address, Address(self.arguments.uniswap_entry_exchange)) \ if self.arguments.uniswap_entry_exchange is not None else None self.uniswap_arb_exchange = UniswapWrapper(self.web3, self.arb_token.address, Address(self.arguments.uniswap_arb_exchange)) \ if self.arguments.uniswap_arb_exchange is not None else None self.oasis_api_endpoint = OasisAPI(api_server=self.arguments.oasis_api_endpoint, entry_token_name=self.token_name(self.entry_token.address), arb_token_name=self.arb_token.name) \ if self.arguments.oasis_api_endpoint is not None else None self.oasis = MatchingMarket(web3=self.web3, address=Address( self.arguments.oasis_address)) self.min_profit = Wad(int(self.arguments.min_profit * 10**18)) self.max_engagement = Wad(int(self.arguments.max_engagement * 10**18)) self.max_errors = self.arguments.max_errors self.errors = 0 if self.arguments.tx_manager: self.tx_manager = TxManager(web3=self.web3, address=Address( self.arguments.tx_manager)) if self.tx_manager.owner() != self.our_address: raise Exception( f"The TxManager has to be owned by the address the keeper is operating from." ) else: self.tx_manager = None logging.basicConfig( format='%(asctime)-15s %(levelname)-8s %(message)s', level=(logging.DEBUG if self.arguments.debug else logging.INFO))
def get_dai_vat_balance(self) -> Wad: return Wad(self.mcd.vat.dai(self.keeper_address))
def __init__(self, receipt): self.raw_receipt = receipt self.transaction_hash = receipt['transactionHash'] self.gas_used = receipt['gasUsed'] self.transfers = [] self.result = None receipt_logs = receipt['logs'] if (receipt_logs is not None) and (len(receipt_logs) > 0): self.successful = True for receipt_log in receipt_logs: if len(receipt_log['topics']) > 0: # $ seth keccak $(seth --from-ascii "Transfer(address,address,uint256)") # 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef if receipt_log['topics'][0] == HexBytes( '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' ): from pymaker.token import ERC20Token transfer_abi = [ abi for abi in ERC20Token.abi if abi.get('name') == 'Transfer' ][0] event_data = get_event_data(transfer_abi, receipt_log) self.transfers.append( Transfer( token_address=Address(event_data['address']), from_address=Address( event_data['args']['from']), to_address=Address(event_data['args']['to']), value=Wad(event_data['args']['value']))) # $ seth keccak $(seth --from-ascii "Mint(address,uint256)") # 0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885 if receipt_log['topics'][0] == HexBytes( '0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885' ): from pymaker.token import DSToken transfer_abi = [ abi for abi in DSToken.abi if abi.get('name') == 'Mint' ][0] event_data = get_event_data(transfer_abi, receipt_log) self.transfers.append( Transfer( token_address=Address(event_data['address']), from_address=Address( '0x0000000000000000000000000000000000000000' ), to_address=Address(event_data['args']['guy']), value=Wad(event_data['args']['wad']))) # $ seth keccak $(seth --from-ascii "Burn(address,uint256)") # 0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5 if receipt_log['topics'][0] == HexBytes( '0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5' ): from pymaker.token import DSToken transfer_abi = [ abi for abi in DSToken.abi if abi.get('name') == 'Burn' ][0] event_data = get_event_data(transfer_abi, receipt_log) self.transfers.append( Transfer( token_address=Address(event_data['address']), from_address=Address( event_data['args']['guy']), to_address=Address( '0x0000000000000000000000000000000000000000' ), value=Wad(event_data['args']['wad']))) else: self.successful = False
def remaining_buy_amount(self) -> Wad: return Wad.max( self.buy_amount - self._exchange.get_unavailable_buy_amount(self), Wad(0))
parser.add_argument('cdps', metavar='CDP', type=int, nargs='+', help='CDP id(s) to check') args = parser.parse_args() web3 = Web3(HTTPProvider(endpoint_uri="https://mainnet.infura.io/metamask")) tub = Tub(web3=web3, address=Address('0x448a5065aebb8e423f0896e6c5d525c040f59af3')) minimum_ratio=Ray.from_number(args.warnratio / 100) requirements_satisfied=True for cup_id in args.cdps: cup = tub.cups(cup_id) pro = tub.ink(cup_id)*tub.tag() tab = tub.tab(cup_id) if not args.quiet: print(f'CDP #{cup_id}') print(f' Owner {cup.lad}') print(f' Deposited {float(cup.ink):.8} PETH') print(f' Debt {float(tab):.8} DAI') if tab > Wad(0): current_ratio = Ray(pro / tab) if not args.quiet: print(f' Current Ratio {float(current_ratio):.2%}') is_undercollateralized = (current_ratio < minimum_ratio) if is_undercollateralized: print(f'CDP #{cup_id} is {float(current_ratio):.2%} which is less than {float(minimum_ratio):.2%}') requirements_satisfied=False if not requirements_satisfied: sys.exit(1)
def get_price(self) -> Optional[Wad]: price = self.price_feed.get_price() if price is None: return None else: return price / Wad(self.vox.par())
def get_price(self) -> Optional[Wad]: return Wad(self.ds_value.read_as_int())
def bag(self, address: Address) -> Wad: """Amount of Dai `pack`ed for retrieving collateral in return""" assert isinstance(address, Address) return Wad(self._contract.functions.bag(address.address).call())
def approval_function(token: ERC20Token, spender_address: Address, spender_name: str): if token.allowance_of(Address(token.web3.eth.defaultAccount), spender_address) < Wad(2 ** 128 - 1): logger = logging.getLogger() logger.info(f"Approving {spender_name} ({spender_address}) to access our {token.address} directly") if not token.approve(spender_address).transact(**kwargs): raise RuntimeError("Approval failed!")
def out(self, ilk: Ilk, address: Address) -> Wad: assert isinstance(ilk, Ilk) assert isinstance(address, Address) return Wad(self._contract.functions.out(ilk.toBytes(), address.address).call())
def test_ilk(self, mcd): assert mcd.vat.ilk('XXX') == Ilk('XXX', rate=Ray(0), ink=Wad(0), art=Wad(0), spot=Ray(0), line=Rad(0), dust=Rad(0))
def sum_of(self, address: Address) -> Wad: """MKR `join`ed to this contract by a specific account""" assert isinstance(address, Address) return Wad(self._contract.functions.sum(address.address).call())
def test_past_frob_and_urns(self, mcd, our_address, other_address): # given collateral0 = mcd.collaterals['ETH-B'] ilk0 = collateral0.ilk collateral1 = mcd.collaterals['ETH-C'] ilk1 = collateral1.ilk # when wrap_eth(mcd, our_address, Wad(18)) wrap_eth(mcd, other_address, Wad(18)) collateral0.approve(our_address) assert collateral0.adapter.join(our_address, Wad(9)).transact() assert mcd.vat.frob(ilk0, our_address, Wad(3), Wad(0)).transact() collateral1.approve(other_address) assert collateral1.adapter.join(other_address, Wad(9)).transact(from_address=other_address) assert mcd.vat.frob(ilk1, other_address, Wad(9), Wad(0)).transact(from_address=other_address) assert mcd.vat.frob(ilk1, other_address, Wad(-3), Wad(0)).transact(from_address=other_address) assert mcd.vat.frob(ilk1, our_address, Wad(3), Wad(0), collateral_owner=other_address, dai_recipient=other_address).transact( from_address=other_address) # then frobs = mcd.vat.past_frobs(10) assert len(frobs) == 4 assert frobs[0].ilk == ilk0.name assert frobs[0].urn == our_address assert frobs[0].dink == Wad(3) assert frobs[0].dart == Wad(0) assert frobs[1].ilk == ilk1.name assert frobs[1].urn == other_address assert frobs[1].dink == Wad(9) assert frobs[1].dart == Wad(0) assert frobs[2].ilk == ilk1.name assert frobs[2].urn == other_address assert frobs[2].dink == Wad(-3) assert frobs[2].dart == Wad(0) assert frobs[3].urn == our_address assert frobs[3].collateral_owner == other_address assert frobs[3].dink == Wad(3) assert frobs[3].dart == Wad(0) assert len(mcd.vat.past_frobs(6, ilk0)) == 1 assert len(mcd.vat.past_frobs(6, ilk1)) == 3 assert len(mcd.vat.past_frobs(6, mcd.collaterals['ZRX-A'].ilk)) == 0 urns0 = mcd.vat.urns(ilk=ilk0) assert len(urns0[ilk0.name]) == 1 urns1 = mcd.vat.urns(ilk=ilk1) assert len(urns1[ilk1.name]) == 2 urns_all = mcd.vat.urns() print(urns_all) assert len(urns_all) >= 2 assert len(urns_all[ilk0.name]) == 1 assert len(urns_all[ilk1.name]) == 2 # teardown cleanup_urn(mcd, collateral0, our_address) cleanup_urn(mcd, collateral1, other_address)
def test_past_frob(self, mcd, our_address, other_address): # given collateral0 = mcd.collaterals['ETH-B'] ilk0 = collateral0.ilk collateral1 = mcd.collaterals['ETH-C'] ilk1 = collateral1.ilk try: # when wrap_eth(mcd, our_address, Wad(18)) wrap_eth(mcd, other_address, Wad(18)) collateral0.approve(our_address) assert collateral0.adapter.join(our_address, Wad(9)).transact() assert mcd.vat.frob(ilk0, our_address, Wad(3), Wad(0)).transact() collateral1.approve(other_address) assert collateral1.adapter.join(other_address, Wad(9)).transact(from_address=other_address) assert mcd.vat.frob(ilk1, other_address, Wad(9), Wad(0)).transact(from_address=other_address) assert mcd.vat.frob(ilk1, other_address, Wad(-3), Wad(0)).transact(from_address=other_address) assert mcd.vat.frob(ilk1, our_address, Wad(3), Wad(0), collateral_owner=other_address, dai_recipient=other_address).transact( from_address=other_address) # then current_block = mcd.web3.eth.blockNumber from_block = current_block - 6 frobs = mcd.vat.past_frobs(from_block) assert len(frobs) == 4 assert frobs[0].ilk == ilk0.name assert frobs[0].urn == our_address assert frobs[0].dink == Wad(3) assert frobs[0].dart == Wad(0) assert frobs[1].ilk == ilk1.name assert frobs[1].urn == other_address assert frobs[1].dink == Wad(9) assert frobs[1].dart == Wad(0) assert frobs[2].ilk == ilk1.name assert frobs[2].urn == other_address assert frobs[2].dink == Wad(-3) assert frobs[2].dart == Wad(0) assert frobs[3].urn == our_address assert frobs[3].collateral_owner == other_address assert frobs[3].dink == Wad(3) assert frobs[3].dart == Wad(0) assert len(mcd.vat.past_frobs(from_block, ilk=ilk0)) == 1 assert len(mcd.vat.past_frobs(from_block, ilk=ilk1)) == 3 assert len(mcd.vat.past_frobs(from_block, ilk=mcd.collaterals['USDC-A'].ilk)) == 0 finally: # teardown cleanup_urn(mcd, collateral0, our_address) cleanup_urn(mcd, collateral1, other_address)
def _allocate_to_pair(self, total_available): # total number of instruments across which the total_available balance is distributed # total_available is denominated in quote units total_number_of_instruments = 1 # there are 2 partitions for allocation per instrument # the dai amount is divided in 2, one for the buy side and another for the sell side number_of_partitions_for_allocation = Wad.from_number( total_number_of_instruments * 2) # buffer_adjustment_factor is a small intentional buffer to avoid allocating the maximum possible. # the allocated amount is a little smaller than the maximum possible allocation # and that is determined by the buffer_adjustment_factor buffer_adjustment_factor = Wad.from_number(1.05) base = self.arguments.pair.upper()[:3] quote = self.arguments.pair.upper()[3:] target_price = self.price_feed.get_price() product = self.leverj_api.get_product(self.pair()) minimum_order_quantity = self.leverj_api.get_minimum_order_quantity( self.pair()) minimum_quantity_wad = Wad.from_number(minimum_order_quantity) if ((base == product['baseSymbol']) and (quote == product['quoteSymbol'])): if ((target_price is None) or (target_price.buy_price is None) or (target_price.sell_price is None)): base_allocation = Wad(0) quote_allocation = Wad(0) self.logger.debug( f'target_price not available to calculate allocations') else: average_price = (target_price.buy_price + target_price.sell_price) / Wad.from_number(2) # at 1x average_price * minimum_quantity_wad is the minimum_required_balance # multiplying this minimum_required_balance by 2 to avoid sending very small orders to the exchange minimum_required_balance = average_price * minimum_quantity_wad * Wad.from_number( 2) # conversion_divisor is the divisor that determines how many chunks should Dai be distributed into. # It considers the price of the base to convert into base denomination. conversion_divisor = average_price * number_of_partitions_for_allocation * buffer_adjustment_factor open_position_for_base = self.leverj_api.get_position_in_wad( base) total_available_wad = Wad.from_number( Decimal(total_available) / Decimal(Decimal(10)**Decimal(18))) base_allocation = total_available_wad / conversion_divisor quote_allocation = total_available_wad / number_of_partitions_for_allocation self.logger.debug( f'open_position_for_base: {open_position_for_base}') # bids are made basis quote_allocation and asks basis base_allocation # if open position is net long then quote_allocation is adjusted. # if open position is net too long then target_price is adjusted to reduce price of the asks/offers if (open_position_for_base.value > 0): open_position_for_base_in_quote = open_position_for_base * average_price net_adjusted_quote_value = quote_allocation.value - abs( open_position_for_base_in_quote.value) self.logger.debug( f'net_adjusted_quote_value: {net_adjusted_quote_value}' ) quote_allocation = Wad( net_adjusted_quote_value ) if net_adjusted_quote_value > minimum_required_balance.value else Wad( 0) # if open position is within 1 Wad range or more than quote allocations then target price is leaned down by 0.1 percent if Wad(net_adjusted_quote_value) < Wad(1): self.target_price_lean = Wad.from_number(0.999) else: self.target_price_lean = Wad(0) elif (open_position_for_base.value < 0): # if open position is net short then base_allocation is adjusted # if open position is net too short then target_price is adjusted to increase price of the bids net_adjusted_base_value = base_allocation.value - abs( open_position_for_base.value) minimum_required_balance_in_base = minimum_required_balance / average_price self.logger.debug( f'net_adjusted_base_value: {net_adjusted_base_value}') base_allocation = Wad( net_adjusted_base_value ) if net_adjusted_base_value > minimum_required_balance_in_base.value else Wad( 0) # if open position is within 1 Wad range or more than base allocations then target price is leaned up by 0.1 percent if Wad(net_adjusted_base_value) < Wad(1): self.target_price_lean = Wad.from_number(1.001) else: self.target_price_lean = Wad(0) else: base_allocation = Wad(0) quote_allocation = Wad(0) allocation = {base: base_allocation, quote: quote_allocation} self.logger.debug(f'allocation: {allocation}') return allocation
def our_available_balance(self, our_balances: dict, token: str) -> Wad: token_balances = list(filter(lambda coin: coin['coinType'].upper() == token, our_balances)) if token_balances: return Wad.from_number(self.round_down(token_balances[0]['balance'], self.amount_precision)) else: return Wad(0)
def __init__(self, args: list): parser = argparse.ArgumentParser(prog='leverj-market-maker-keeper') parser.add_argument( "--leverj-api-server", type=str, default="https://test.leverj.io", help= "Address of the leverj API server (default: 'https://test.leverj.io')" ) parser.add_argument("--account-id", type=str, default="", help="Address of leverj api account id") parser.add_argument("--api-key", type=str, default="", help="Address of leverj api key") parser.add_argument("--api-secret", type=str, default="", help="Address of leverj api secret") parser.add_argument( "--leverj-timeout", type=float, default=9.5, help= "Timeout for accessing the Leverj API (in seconds, default: 9.5)") parser.add_argument("--rpc-host", type=str, default="localhost", help="JSON-RPC host (default: `localhost')") parser.add_argument("--rpc-port", type=int, default=8545, help="JSON-RPC port (default: `8545')") parser.add_argument("--rpc-timeout", type=int, default=10, help="JSON-RPC timeout (in seconds, default: 10)") parser.add_argument( "--eth-from", type=str, required=True, help="Ethereum account from which to watch our trades") parser.add_argument( "--eth-key", type=str, nargs='*', help= "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')" ) parser.add_argument("--config", type=str, required=True, help="Bands configuration file") parser.add_argument("--price-feed", type=str, required=True, help="Source of price feed") parser.add_argument( "--price-feed-expiry", type=int, default=120, help="Maximum age of the price feed (in seconds, default: 120)") parser.add_argument("--spread-feed", type=str, help="Source of spread feed") parser.add_argument( "--spread-feed-expiry", type=int, default=3600, help="Maximum age of the spread feed (in seconds, default: 3600)") parser.add_argument("--control-feed", type=str, help="Source of control feed") parser.add_argument( "--control-feed-expiry", type=int, default=86400, help="Maximum age of the control feed (in seconds, default: 86400)" ) parser.add_argument("--order-history", type=str, help="Endpoint to report active orders to") parser.add_argument( "--order-history-every", type=int, default=30, help= "Frequency of reporting active orders (in seconds, default: 30)") parser.add_argument( "--refresh-frequency", type=int, default=3, help="Order book refresh frequency (in seconds, default: 3)") parser.add_argument( "--pair", type=str, required=True, help="Token pair (sell/buy) on which the keeper will operate") parser.add_argument("--leverage", type=float, default=1.0, help="Leverage chosen for futures orders") parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) self.web3 = Web3( HTTPProvider( endpoint_uri= f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}", request_kwargs={"timeout": self.arguments.rpc_timeout})) self.web3.eth.defaultAccount = self.arguments.eth_from register_keys(self.web3, self.arguments.eth_key) setup_logging(self.arguments) self.bands_config = ReloadableConfig(self.arguments.config) self.price_feed = PriceFeedFactory().create_price_feed(self.arguments) self.spread_feed = create_spread_feed(self.arguments) self.control_feed = create_control_feed(self.arguments) self.order_history_reporter = create_order_history_reporter( self.arguments) self.target_price_lean = Wad(0) self.leverage = self.arguments.leverage self.history = History() self.leverj_api = LeverjFuturesAPI( web3=self.web3, api_server=self.arguments.leverj_api_server, account_id=self.arguments.account_id, api_key=self.arguments.api_key, api_secret=self.arguments.api_secret, timeout=self.arguments.leverj_timeout) self.order_book_manager = OrderBookManager( refresh_frequency=self.arguments.refresh_frequency) self.order_book_manager.get_orders_with( lambda: self.leverj_api.get_orders(self.pair())) self.order_book_manager.get_balances_with( lambda: self.leverj_api.get_balances()) self.order_book_manager.cancel_orders_with( lambda order: self.leverj_api.cancel_order(order.order_id)) self.order_book_manager.enable_history_reporting( self.order_history_reporter, self.our_buy_orders, self.our_sell_orders) self.order_book_manager.start()
def liquidate_urn(mcd, c: Collateral, address: Address, bidder: Address, c_dai: Collateral = None): assert isinstance(c, Collateral) assert isinstance(address, Address) assert isinstance(bidder, Address) if c_dai is None: c_dai = c # Ensure the CDP isn't safe urn = mcd.vat.urn(c.ilk, address) if is_cdp_safe(c.ilk, urn): assert urn.ink > Wad(0) safe_price = urn.art / Wad(mcd.spotter.mat(c.ilk)) / urn.ink print( f"current_price={float(get_collateral_price(c))}, safe_price={float(safe_price)}" ) set_collateral_price(mcd, c, safe_price / Wad.from_number(2)) c.ilk = mcd.vat.ilk(c.ilk.name) assert not is_cdp_safe(c.ilk, urn) if c.clipper: c.clipper.approve(mcd.vat.address, approval_function=hope_directly(from_address=bidder)) # Bark to kick the auction assert mcd.dog.bark(c.ilk, urn).transact() kick = c.clipper.kicks() (needs_redo, auction_price, lot, tab) = c.clipper.status(kick) purchase_dai(Wad(tab) + Wad(1), address) assert mcd.dai_adapter.join(address, Wad(tab) + Wad(1)).transact(from_address=address) assert mcd.vat.dai(address) >= tab bid_price = tab / Rad(lot) while auction_price > bid_price: time_travel_by(mcd.web3, 1) (needs_redo, auction_price, lot, tab) = c.clipper.status(kick) print( f"taking lot {lot} on auction {kick} at {bid_price} with {mcd.vat.dai(bidder)} Dai remaining" ) assert c.clipper.take(kick, lot, bid_price).transact(from_address=address) elif c.flipper: c.flipper.approve(mcd.vat.address, approval_function=hope_directly(from_address=bidder)) # Determine how many bites will be required dunk: Rad = mcd.cat.dunk(c.ilk) box: Rad = mcd.cat.box() urn = mcd.vat.urn(c.ilk, address) bites_required = math.ceil(urn.art / Wad(dunk)) print( f"art={float(urn.art)} and dunk={float(dunk)} so {bites_required} bites are required" ) first_kick = c.flipper.kicks() + 1 while mcd.cat.can_bite(c.ilk, urn): box_kick = c.flipper.kicks() + 1 while mcd.cat.can_bite(c.ilk, urn): # Bite and bid on each auction next_kick = c.flipper.kicks() + 1 print( f"biting {next_kick} ({next_kick - first_kick + 1} of {bites_required})" ) kick = bite(mcd, c, urn) auction = c.flipper.bids(kick) reserve_dai(mcd, c_dai, bidder, Wad(auction.tab) + Wad(1)) print( f"bidding tab {auction.tab} on auction {kick} for {auction.lot} with {mcd.vat.dai(bidder)} Dai remaining" ) assert c.flipper.tend( kick, auction.lot, auction.tab).transact(from_address=bidder) urn = mcd.vat.urn(c.ilk, address) time_travel_by(mcd.web3, c.flipper.ttl() + 3) for kick in range(box_kick, c.flipper.kicks() + 1): print( f"dealing {kick} ({kick - first_kick + 1} of {bites_required})" ) assert c.flipper.deal(kick).transact() set_collateral_price(mcd, c, Wad.from_number(200)) repay_urn(mcd, c, address) assert urn.art == Wad(0) assert urn.ink == Wad(0)
def gap(self, ilk: Ilk) -> Wad: """Collateral shortfall (difference of debt and collateral""" assert isinstance(ilk, Ilk) return Wad(self._contract.functions.gap(ilk.toBytes()).call())
def total_amount(orders): return reduce(operator.add, map(lambda order: order.remaining_sell_amount, orders), Wad(0))
def art(self, ilk: Ilk) -> Wad: """Total debt for the collateral""" assert isinstance(ilk, Ilk) return Wad(self._contract.functions.Art(ilk.toBytes()).call())
def urn(self, ilk: Ilk, address: Address) -> Urn: assert isinstance(ilk, Ilk) assert isinstance(address, Address) (ink, art) = self._contract.functions.urns(ilk.toBytes(), address.address).call() return Urn(address, ilk, Wad(ink), Wad(art))
def test_balance_of(self): assert self.token.balance_of(self.our_address) == Wad(1000000) assert self.token.balance_of(self.second_address) == Wad(0)