<commit_before># This file is part of Maker Keeper Framework.
#
# Copyright (C) 2017 reverendus
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import argparse
import operator
from functools import reduce
from typing import List

import sys

from keeper.api import Address
from keeper.api.approval import directly
from keeper.api.feed import DSValue
from keeper.api.numeric import Wad

from keeper.api.etherdelta import EtherDelta, Order, EtherDeltaApi
from keeper.sai import SaiKeeper


class SaiMakerEtherDelta(SaiKeeper):
    """SAI keeper to act as a market maker on EtherDelta, on the ETH/SAI pair.

    Due to limitations of EtherDelta, the development of this keeper has been
    discontinued. It works most of the time, but due to the fact that EtherDelta
    was a bit unpredictable in terms of placing orders at the time this keeper
    was developed, we abandoned it and decided to stick to SaiMakerOtc for now.
    """
    def __init__(self, args: list, **kwargs):
        super().__init__(args, **kwargs)
        self.order_age = self.arguments.order_age
        self.max_eth_amount = Wad.from_number(self.arguments.max_eth_amount)
        self.min_eth_amount = Wad.from_number(self.arguments.min_eth_amount)
        self.max_sai_amount = Wad.from_number(self.arguments.max_sai_amount)
        self.min_sai_amount = Wad.from_number(self.arguments.min_sai_amount)
        self.eth_reserve = Wad.from_number(self.arguments.eth_reserve)
        self.min_margin = self.arguments.min_margin
        self.avg_margin = self.arguments.avg_margin
        self.max_margin = self.arguments.max_margin

        # We need to have the API server in order for this keeper to run.
        # Unfortunately it means it can be run only on Mainnet as there is no test server.
        if "apiServer" not in self.config.get_config()["etherDelta"]:
            raise Exception("Off-chain EtherDelta orders not supported on this chain")

        self.etherdelta_address = Address(self.config.get_config()["etherDelta"]["contract"])
        self.etherdelta_api_server = self.config.get_config()["etherDelta"]["apiServer"]
        self.etherdelta = EtherDelta(web3=self.web3, address=self.etherdelta_address)
        self.etherdelta_api = EtherDeltaApi(api_server=self.etherdelta_api_server, logger=self.logger)

    def args(self, parser: argparse.ArgumentParser):
        parser.add_argument("--order-age", help="Age of created orders (in blocks)", type=int, required=True)
        parser.add_argument("--min-margin", help="Minimum margin allowed", type=float, required=True)
        parser.add_argument("--avg-margin", help="Target margin, used on new order creation", type=float, required=True)
        parser.add_argument("--max-margin", help="Maximum margin allowed", type=float, required=True)
        parser.add_argument("--eth-reserve", help="Minimum amount of ETH to keep in order to cover gas", type=float, required=True)
        parser.add_argument("--max-eth-amount", help="Maximum value of open ETH sell orders", type=float, required=True)
        parser.add_argument("--min-eth-amount", help="Minimum value of open ETH sell orders", type=float, required=True)
        parser.add_argument("--max-sai-amount", help="Maximum value of open SAI sell orders", type=float, required=True)
        parser.add_argument("--min-sai-amount", help="Minimum value of open SAI sell orders", type=float, required=True)

    def startup(self):
        self.approve()
        # self.etherdelta.place_order_offchain(token_get=self.sai.address, amount_get=Wad.from_number(100),
        #                                      token_give=EtherDelta.ETH_TOKEN, amount_give=Wad.from_number(0.1),
        #                                      expires=self.web3.eth.blockNumber+self.order_age)
        # self.on_block(self.synchronize_orders)
        self.every(60*60, self.print_balances)

    def shutdown(self):
        self.cancel_all_orders()
        self.withdraw_everything()

    def print_balances(self):
        sai_owned = self.sai.balance_of(self.our_address)
        sai_deposited = self.etherdelta.balance_of_token(self.sai.address, self.our_address)
        eth_owned = self.eth_balance(self.our_address)
        eth_deposited = self.etherdelta.balance_of(self.our_address)

        self.logger.info(f"Keeper balances are {sai_owned} + {sai_deposited} SAI, {eth_owned} + {eth_deposited} ETH")

    def approve(self):
        """Approve EtherDelta to access our SAI, so we can deposit it with the exchange"""
        self.etherdelta.approve([self.sai], directly())

    def our_orders(self):
        onchain_orders = self.etherdelta.active_onchain_orders()
        return list(filter(lambda order: order.user == self.our_address, onchain_orders))

    def our_buy_orders(self):
        return list(filter(lambda order: order.token_get == self.sai.address and
                                         order.token_give == EtherDelta.ETH_TOKEN, self.our_orders()))

    def our_sell_orders(self):
        return list(filter(lambda order: order.token_get == EtherDelta.ETH_TOKEN and
                                         order.token_give == self.sai.address, self.our_orders()))

    def synchronize_orders(self):
        """Update our positions in the order book to reflect settings."""<commit_msg>`--withdraw-on-shutdown` flag added<commit_after># This file is part of Maker Keeper Framework.
#
# Copyright (C) 2017 reverendus
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import argparse
import operator
from functools import reduce
from typing import List

import sys

from keeper.api import Address
from keeper.api.approval import directly
from keeper.api.feed import DSValue
from keeper.api.numeric import Wad

from keeper.api.etherdelta import EtherDelta, Order, EtherDeltaApi
from keeper.sai import SaiKeeper


class SaiMakerEtherDelta(SaiKeeper):
    """SAI keeper to act as a market maker on EtherDelta, on the ETH/SAI pair.

    Due to limitations of EtherDelta, the development of this keeper has been
    discontinued. It works most of the time, but due to the fact that EtherDelta
    was a bit unpredictable in terms of placing orders at the time this keeper
    was developed, we abandoned it and decided to stick to SaiMakerOtc for now.
    """
    def __init__(self, args: list, **kwargs):
        super().__init__(args, **kwargs)
        self.order_age = self.arguments.order_age
        self.max_eth_amount = Wad.from_number(self.arguments.max_eth_amount)
        self.min_eth_amount = Wad.from_number(self.arguments.min_eth_amount)
        self.max_sai_amount = Wad.from_number(self.arguments.max_sai_amount)
        self.min_sai_amount = Wad.from_number(self.arguments.min_sai_amount)
        self.eth_reserve = Wad.from_number(self.arguments.eth_reserve)
        self.min_margin = self.arguments.min_margin
        self.avg_margin = self.arguments.avg_margin
        self.max_margin = self.arguments.max_margin

        # We need to have the API server in order for this keeper to run.
        # Unfortunately it means it can be run only on Mainnet as there is no test server.
        if "apiServer" not in self.config.get_config()["etherDelta"]:
            raise Exception("Off-chain EtherDelta orders not supported on this chain")

        self.etherdelta_address = Address(self.config.get_config()["etherDelta"]["contract"])
        self.etherdelta_api_server = self.config.get_config()["etherDelta"]["apiServer"]
        self.etherdelta = EtherDelta(web3=self.web3, address=self.etherdelta_address)
        self.etherdelta_api = EtherDeltaApi(api_server=self.etherdelta_api_server, logger=self.logger)

    def args(self, parser: argparse.ArgumentParser):
        parser.add_argument("--order-age", help="Age of created orders (in blocks)", type=int, required=True)
        parser.add_argument("--min-margin", help="Minimum margin allowed", type=float, required=True)
        parser.add_argument("--avg-margin", help="Target margin, used on new order creation", type=float, required=True)
        parser.add_argument("--max-margin", help="Maximum margin allowed", type=float, required=True)
        parser.add_argument("--eth-reserve", help="Minimum amount of ETH to keep in order to cover gas", type=float, required=True)
        parser.add_argument("--max-eth-amount", help="Maximum value of open ETH sell orders", type=float, required=True)
        parser.add_argument("--min-eth-amount", help="Minimum value of open ETH sell orders", type=float, required=True)
        parser.add_argument("--max-sai-amount", help="Maximum value of open SAI sell orders", type=float, required=True)
        parser.add_argument("--min-sai-amount", help="Minimum value of open SAI sell orders", type=float, required=True)

        parser.add_argument('--withdraw-on-shutdown', dest='withdraw_on_shutdown', action='store_true',
                            help="Whether should withdraw all tokens from EtherDelta on keeper shutdown")
        parser.set_defaults(withdraw_on_shutdown=False)

    def startup(self):
        self.approve()
        # self.etherdelta.place_order_offchain(token_get=self.sai.address, amount_get=Wad.from_number(100),
        #                                      token_give=EtherDelta.ETH_TOKEN, amount_give=Wad.from_number(0.1),
        #                                      expires=self.web3.eth.blockNumber+self.order_age)
        # self.on_block(self.synchronize_orders)
        self.every(60*60, self.print_balances)

    def shutdown(self):
        self.cancel_all_orders()
        if self.arguments.withdraw_on_shutdown:
            self.withdraw_everything()

    def print_balances(self):
        sai_owned = self.sai.balance_of(self.our_address)
        sai_deposited = self.etherdelta.balance_of_token(self.sai.address, self.our_address)
        eth_owned = self.eth_balance(self.our_address)
        eth_deposited = self.etherdelta.balance_of(self.our_address)

        self.logger.info(f"Keeper balances are {sai_owned} + {sai_deposited} SAI, {eth_owned} + {eth_deposited} ETH")

    def approve(self):
        """Approve EtherDelta to access our SAI, so we can deposit it with the exchange"""
        self.etherdelta.approve([self.sai], directly())

    def our_orders(self):
        onchain_orders = self.etherdelta.active_onchain_orders()
        return list(filter(lambda order: order.user == self.our_address, onchain_orders))

    def our_buy_orders(self):
        return list(filter(lambda order: order.token_get == self.sai.address and
                                         order.token_give == EtherDelta.ETH_TOKEN, self.our_orders()))

    def our_sell_orders(self):
        return list(filter(lambda order: order.token_get == EtherDelta.ETH_TOKEN and
                                         order.token_give == self.sai.address, self.our_orders()))

    def synchronize_orders(self):
        """Update our positions in the order book to reflect settings."""
{'fraction_non_alphanumeric': 0.09491637142621735, 'fraction_numerical': 0.002801351240009887, 'mean_word_length': 3.423469387755102, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7224008', 'n_tokens_mistral': 3588, 'n_tokens_neox': 3437, 'n_words': 995}