Beispiel #1
0
 def test_get_sell(self):
   client = Client(api_key, api_secret)
   sell = client.get_sell('foo', 'bar')
   self.assertIsInstance(sell, Sell)
   self.assertEqual(sell, mock_item)
Beispiel #2
0
 def test_get_sell(self):
   client = Client(api_key, api_secret)
   sell = client.get_sell('foo', 'bar')
   self.assertIsInstance(sell, Sell)
   self.assertEqual(sell, mock_item)
Beispiel #3
0
            #Write a little market to the file to signify this fact
            open('/root/MyPythonCode/pricehist.txt',
                 'a+').write("***PROFIT MARKER*** " +
                             str(datetime.datetime.now()) + '\n')

            #Example - DO NOT USE
            #sell = client.sell('2bbf394c-193b-5b2a-9155-3b4732659ede',amount="10",currency="BTC", payment_method="83562370-3e5c-51db-87da-752af5ab9559")
            #DO THE SELL!
            sell = client.sell(
                acc_id, total=float(price_change), currency="GBP"
            )  #Is this a float or a string??? Not sure check this out later
            #GET THE SELL ID!
            sell_id = str(sell.id)
            print("DEBUG: sell_id " + str(sell_id))

            sell_info = client.get_sell(acc_id, sell_id)

            #***FOR SOME REASON THIS DOESNT WORK BUT TRANSACTIONS COMPLETE QUICKLY ANYWAY***
            #while not str(sell_info.status) == "completed" or "cancelled":
            #time.sleep(3)
            #print ("DEBUG: Waiting for 5 seconds and check again, Status is  " + str(sell_info.status) )
            #NOTE: Status is either created, completed, canceled. loop whilst not completed!!!! We need to wait to the transaction is either cancelled or even better completed

            if str(sell_info.status) != "completed":
                print("DEBUG : " + str(sell_info.status))
                time.sleep(35)

            #Once you have confirmed sale of the difference, There will be a new starting price which we need to write to the file so it is picked up next time
            time.sleep(
                3
            )  #Wait a token 3 seconds for price to update before reading it
class CoinbaseInterface(PlatformInterface):
    """
    Class implementing all the methods allowing to calculate the differents
    overall values of accounts at given times. It allows also to enumerate
    all the transactions that can be impacted by taxes
    """

    def __init__(self, api_key: str, api_secret: str, price_finder: List[PriceFinder]) -> None:
        # Call the upper class initialization
        super().__init__(price_finder)

        # Create the Coinbase authenticated client that we will use
        self.api_client = Client(api_key, api_secret)

        # Load all accounts and transactions
        fcrypt_log.info("[COINBASE][INITIALIZATION] Loading all accounts...")
        self._load_all_accounts()
        fcrypt_log.info("[COINBASE][INITIALIZATION] Loading all transactions...")
        self._load_all_transactions()

    @staticmethod
    def _extract_account_id(path: str) -> str:
        """
        Function allowing to extract an account UUID from a "resource_path" given
        for each transaction done on Coinbase.
        :param path: "resource_path" to extract the account id from
        :type path: str
        :returns: str -- The account id
        """
        items = path.split("/")

        # Check that the path look like we want
        if items[2] != "accounts":
            raise ValueError("It looks like the resource path is not like we want...")

        # Return the account id
        return items[3]

    def _load_all_accounts(self):
        """
        This function allows to load every account that the user has on Coinbase
        These accounts will be used to calculate the taxes, 'in fine'.
        Only the accounts with an real UUID are taken into account
        """
        accounts_list = []
        other_pages = True
        last_id = ""
        while other_pages:
            # Get account according to pagination
            if last_id == "":
                api_answer = self.api_client.get_accounts()
            else:
                api_answer = self.api_client.get_accounts(starting_after=last_id)

            accounts_list = accounts_list + api_answer['data']
            if ((api_answer.pagination is not None) and (api_answer.pagination["next_uri"] is not None) and
                    (api_answer.pagination["next_uri"] != "")):
                # Get the pagination object
                pagination_obj = api_answer.pagination
                # Extract 'starting_after' key from next_uri
                parsed = urlparse.urlparse(pagination_obj["next_uri"])
                last_id = parse_qs(parsed.query)['starting_after'][0]
            else:
                other_pages = False

        # Save all used accounts
        for account in accounts_list:

            # If the ID is a valid UUID, save the account and print it in DEBUG logs
            match = re.search(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", account['id'])
            if match:
                # Get the various values
                id = account['id']
                name = account['name']
                crypto_balance = account['balance']['amount']
                crypto_currency = account['currency']
                native_balance = account['native_balance']['amount']
                native_currency = account['native_balance']['currency']

                # Debug print
                fcrypt_log.debug(
                    f"Adding account: {id} ==> {name}: {crypto_balance} {crypto_currency}" +
                    f" ({native_balance} {native_currency})")

                # Add the account in our list
                self.accounts.append(account)

    def _load_all_transactions(self):
        """
        This function allows to load every transaction that the user has done on Coinbase
        These transactions, in fine, will allow us to go back in time in the account, to know
        what was on each account, at a given time
        """
        # Get all transactions
        for account in self.accounts:
            transactions_list = []
            other_pages = True
            last_id = ""

            while other_pages:
                # Get account according to pagination
                if last_id == "":
                    # Get the transactions for this account
                    tmp_transactions = self.api_client.get_transactions(account['id'])
                else:
                    # Get the transactions for this account
                    tmp_transactions = self.api_client.get_transactions(account['id'], starting_after=last_id)

                transactions_list = transactions_list + tmp_transactions['data']
                if ((tmp_transactions.pagination is not None) and
                    (tmp_transactions.pagination["next_uri"] is not None) and
                        (tmp_transactions.pagination["next_uri"] != "")):
                    # Get the pagination object
                    pagination_obj = tmp_transactions.pagination
                    # Extract 'starting_after' key from next_uri
                    parsed = urlparse.urlparse(pagination_obj["next_uri"])
                    last_id = parse_qs(parsed.query)['starting_after'][0]
                else:
                    other_pages = False

            # Print the transactions
            for transaction in transactions_list:
                # print(transaction)
                transaction_type = transaction['type']
                amount = transaction['amount']['amount']
                currency = transaction['amount']['currency']
                date = transaction['updated_at']
                if not str.startswith(amount, "-"):
                    amount = "+" + amount
                account = self._extract_account_id(transaction['resource_path'])
                fcrypt_log.debug(f"[TRANSACTION][{transaction_type}] {date}: {amount} {currency} ==> {account}")

            self.transactions.extend(transactions_list)

    def get_wallet_balance_at(self, currency: str, time: datetime.datetime) -> Decimal:
        """
        This function allows to get the balance of a wallet at a given time.

        :param currency: Currency we want the value for
        :type currency: str
        :param time: Time where the value is wanted
        :type time: datetime.datetime
        :returns: Decimal -- The wallet balance at the given time
        """
        # Firstly, get the corresponding account ID
        account_id = ""
        for account in self.accounts:
            if ('currency' in account) and (account['currency'] == currency):
                account_id = account['id']
                current_balance = Decimal(account['balance']['amount'])

        if account_id == "":
            raise ValueError("No account found for this currency")

        # Then apply every transaction in reverse if the datetime of this transaction
        # is posterior to the wanted datetime
        for transaction in self.transactions:
            # Extract account ID
            tmp_account_id = self._extract_account_id(transaction['resource_path'])
            # Check if the account ids correspond
            if (tmp_account_id == account_id) and transaction['status'] == 'completed':
                # Extract the datetime
                operation_datetime = isoparse(transaction['updated_at'])
                # If datetime posterior or equal to the time given by user, reverse it
                if operation_datetime >= time:
                    trans_amount = Decimal(transaction['amount']['amount'])
                    tmp_balance = current_balance - trans_amount
                    fcrypt_log.debug(f"[REVERSED TRANSACTION] {trans_amount} {currency} ==> {account_id}")
                    fcrypt_log.debug(
                        f"[REVERSED TRANSACTION] Operation {current_balance}-{trans_amount} = {tmp_balance}")
                    current_balance = tmp_balance

        return current_balance

    def get_wallet_value_at(self, crypto_currency: str, fiat_currency: str, time: datetime.datetime) -> Decimal:
        """
        This function allows to get the value of a wallet at a given time.

        :param crypto_currency: Crypto currency of the wallet
        :type crypto_currency: str
        :param fiat_currency: Fiat currency for the result (EUR, USD, etc.)
        :type fiat_currency: str
        :param time: Time where the value is wanted
        :type time: datetime.datetime
        :returns: Decimal -- The wallet value at the given time
        """
        # Firstly, get the wallet balance at the given time
        balance = self.get_wallet_balance_at(crypto_currency, time)

        time_str = str(time)
        normal_balance = str(balance.normalize())

        # Print debug
        fcrypt_log.debug(f"[WALLET] Balance at {time_str}: {normal_balance} {crypto_currency}")

        if balance != 0:

            # Now get the equivalent value in fiat
            rate_currency = crypto_currency + "-" + fiat_currency
            rate_value = self._find_rate_value_from_finders(rate_currency, time)

            if rate_value == Decimal(0):
                # Print error
                fcrypt_log.warning(
                    f"[WALLET] NO RATE FOUND FOR NOT NULL BALANCE !!! Currency: {crypto_currency} \
 - Fiat: {fiat_currency}")
                # Return 0
                wallet_value = Decimal(0)
            else:
                # Calculate the wallet value
                wallet_value = rate_value * balance

                # Print info
                fcrypt_log.debug(
                    f"[WALLET] Value of {crypto_currency} wallet at {time_str}: {wallet_value} {fiat_currency}")

        else:
            wallet_value = Decimal(0)

        return wallet_value

    def get_all_wallets_value(self, currency: str, time: datetime.datetime) -> Decimal:
        """
        This function allows to get the value of all the crypto-wallets of
        a user at a given time

        :param currency: Fiat currency we want for the value (ISO 4217)
        :type currency: str
        :param time: Time where the value is wanted
        :type time: datetime.datetime
        :returns: Decimal -- The overall value at the given time
        """
        # Initialize overall value of the wallet
        overall_value = Decimal(0)

        # For each crypto account (except fiat currency), calculate the wallet value
        for account in self.accounts:
            if account['currency'] != currency:
                wallet_value = self.get_wallet_value_at(account['currency'], currency, time)
                # Add the wallet value to the overall value
                overall_value += wallet_value

        return overall_value

    def all_sell_transactions_generator(self, currency: str, end_time: datetime.datetime) -> Generator:
        """
        This function returns a generator that can be used in a for loop to get
        every "sell" transactions done between "start_time" and "end_time"

        :param currency: Fiat currency we want for the value (ISO 4217)
        :type currency: str
        :param start_time: Begin of the tax period
        :type start_time: datetime.datetime
        :param end_time: End of the tax period
        :type end_time: datetime.datetime
        :returns: Generator -- Generator to get each transaction object \
        """
        # Get the correct ID for this currency in order to ignore it
        account_to_ignore_id = self._find_account_for_currency(currency)

        if account_to_ignore_id == "":
            raise ValueError(f"Account not found with the given currency: {currency}")

        # Now that we have the right account
        for transaction in self.transactions:
            # Extract the account ID from the resource path
            account = self._extract_account_id(transaction['resource_path'])

            # Check that this is the right account and that is a match
            if (account != account_to_ignore_id) and (transaction["type"] == "sell"):
                # Get the amount of the transaction
                amount = transaction["native_amount"]["amount"]
                local_currency = transaction["native_amount"]["currency"]

                if local_currency != currency:
                    error_msg = f"The local currency found \"{local_currency}\" does not match \
                                  the specified currency \"{currency}\"!"
                    raise ValueError(error_msg)

                # Check the time when this sell appeared
                transaction_time = isoparse(transaction['created_at'])

                if (transaction_time < end_time):
                    # This is something we want, find the corresponding fee

                    # Request the full sell transaction object
                    current_sell = self.api_client.get_sell(account, transaction["sell"]["id"])

                    # Get the fee amount from the full sell object
                    fee_amount = current_sell["fees"][0]["amount"]["amount"]

                    # Declare the dictionnary to return
                    tmp_dict = {
                        "date": transaction_time,
                        "currency": local_currency,
                        "amount": amount,
                        "fee": fee_amount
                    }

                    yield tmp_dict

    def all_buy_transactions_generator(self, currency: str, end_time: datetime.datetime) -> Generator:
        """
        This function returns a generator that can be used in a for loop to get
        every "buy" transactions done before "end_time"

        :param currency: Fiat currency we want for the value (ISO 4217)
        :type currency: str
        :param end_time: End of the tax period
        :type end_time: datetime.datetime
        :returns: Generator -- Generator to get each transaction object
        """
        # Get the correct ID for this currency in order to ignore it
        account_to_ignore_id = self._find_account_for_currency(currency)

        if account_to_ignore_id == "":
            raise ValueError(f"Account not found with the given currency: {currency}")

        # Now that we have the right account
        for transaction in self.transactions:
            # Extract the account ID from the resource path
            account = self._extract_account_id(transaction['resource_path'])

            # Check that this is the right account and that is a match
            if (account != account_to_ignore_id) and (transaction["type"] == "buy"):
                # Get the amount of the transaction
                amount = transaction["native_amount"]["amount"]
                local_currency = transaction["native_amount"]["currency"]

                if local_currency != currency:
                    error_msg = f"The local currency found \"{local_currency}\" does not match \
                                 the specified currency \"{currency}\"!"
                    raise ValueError(error_msg)

                # Check the time when this sell appeared
                transaction_time = isoparse(transaction['created_at'])

                if (transaction_time < end_time):
                    # This is something we want, find the corresponding fee

                    # Request the full sell transaction object
                    current_buy = self.api_client.get_buy(account, transaction["buy"]["id"])

                    # Get the fee amount from the full sell object
                    fee_amount = current_buy["fees"][0]["amount"]["amount"]

                    # Declare the dictionnary to return
                    tmp_dict = {
                        "date": transaction_time,
                        "currency": local_currency,
                        "amount": amount,
                        "fee": fee_amount
                    }

                    yield tmp_dict