Esempio n. 1
0
    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)
Esempio n. 2
0
    def get_price(self) -> Price:
        tub_price = Wad(self.ds_value.read_as_int())

        return Price(buy_price=tub_price, sell_price=tub_price)
Esempio n. 3
0
    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())
Esempio n. 4
0
    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
Esempio n. 5
0
def get_collateral_price(collateral: Collateral):
    assert isinstance(collateral, Collateral)
    return Wad(Web3.toInt(collateral.pip.read()))
Esempio n. 6
0
 def sum(self) -> Wad:
     """Total balance of MKR `join`ed to this contract"""
     return Wad(self._contract.functions.Sum().call())
Esempio n. 7
0
 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))
Esempio n. 9
0
 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
Esempio n. 11
0
 def remaining_buy_amount(self) -> Wad:
     return Wad.max(
         self.buy_amount - self._exchange.get_unavailable_buy_amount(self),
         Wad(0))
Esempio n. 12
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)
Esempio n. 13
0
 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())
Esempio n. 14
0
 def get_price(self) -> Optional[Wad]:
     return Wad(self.ds_value.read_as_int())
Esempio n. 15
0
 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())
Esempio n. 16
0
 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!")
Esempio n. 17
0
 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())
Esempio n. 18
0
 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))
Esempio n. 19
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())
Esempio n. 20
0
    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)
Esempio n. 21
0
    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)
Esempio n. 22
0
    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)
Esempio n. 24
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()
Esempio n. 25
0
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)
Esempio n. 26
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())
Esempio n. 27
0
 def total_amount(orders):
     return reduce(operator.add, map(lambda order: order.remaining_sell_amount, orders), Wad(0))
Esempio n. 28
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())
Esempio n. 29
0
    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))
Esempio n. 30
0
 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)