Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USA to drop universal application: implement per-wallet application #135

Open
eprbell opened this issue Oct 26, 2024 · 49 comments
Open

USA to drop universal application: implement per-wallet application #135

eprbell opened this issue Oct 26, 2024 · 49 comments

Comments

@eprbell
Copy link
Owner

eprbell commented Oct 26, 2024

Discussion on this topic started here: #134

@eprbell eprbell added the help wanted Extra attention is needed label Oct 26, 2024
@eprbell
Copy link
Owner Author

eprbell commented Nov 4, 2024

Some useful links on this topic:

@macanudo527
Copy link
Collaborator

macanudo527 commented Nov 11, 2024

This is what the great magnificent supercomputer in the sky aka CoPilot had to say about this:

To modify the project to track transactions by individual wallets and calculate the cost basis based on the purchase price for the crypto asset in each wallet, you will need to make changes to several parts of the codebase. Here is a step-by-step guide to help you implement this feature:

  1. Update the TransactionSet class to include wallet information.

  2. Modify the InputData class to handle wallet-specific transactions.

  3. Update the GainLossSet class to calculate gain/loss based on wallet-specific transactions.

  4. Modify the tax_engine.py to use the updated classes and methods.

Step 1: Update the TransactionSet Class

Add wallet information to the TransactionSet class and ensure it can handle transactions for different wallets.

Step 2: Modify the InputData Class

Update the InputData class to handle wallet-specific transactions. This involves adding methods to filter transactions by wallet.

Step 3: Update the GainLossSet Class

Modify the GainLossSet class to calculate gain/loss based on wallet-specific transactions.

Step 4: Modify tax_engine.py

Update the tax_engine.py to use the updated classes and methods.

Here is an example of how you can modify the tax_engine.py:

def _create_unfiltered_taxable_event_set(configuration: Configuration, input_data: InputData) -> Dict[str, TransactionSet]:
    wallet_taxable_event_sets: Dict[str, TransactionSet] = {}
    for wallet in input_data.wallets:
        taxable_event_set: TransactionSet = TransactionSet(configuration, "MIXED", input_data.asset, MIN_DATE, MAX_DATE)
        for transaction_set in [
            input_data.get_unfiltered_in_transaction_set(wallet),
            input_data.get_unfiltered_out_transaction_set(wallet),
            input_data.get_unfiltered_intra_transaction_set(wallet),
        ]:
            for entry in transaction_set:
                transaction = cast(AbstractTransaction, entry)
                if transaction.is_taxable():
                    taxable_event_set.add_entry(transaction)
        wallet_taxable_event_sets[wallet] = taxable_event_set
    return wallet_taxable_event_sets


def _create_unfiltered_gain_and_loss_set(
    configuration: Configuration, accounting_engine: AccountingEngine, input_data: InputData, wallet_taxable_event_sets: Dict[str, TransactionSet]
) -> Dict[str, GainLossSet]:
    wallet_gain_loss_sets: Dict[str, GainLossSet] = {}
    for wallet, taxable_event_set in wallet_taxable_event_sets.items():
        gain_loss_set: GainLossSet = GainLossSet(configuration, input_data.asset, MIN_DATE, MAX_DATE)
        new_accounting_engine: AccountingEngine = accounting_engine.__class__(accounting_engine.years_2_methods)
        taxable_event_iterator: Iterator[AbstractTransaction] = iter(cast(Iterable[AbstractTransaction], taxable_event_set))
        acquired_lot_iterator: Iterator[InTransaction] = iter(cast(Iterable[InTransaction], input_data.get_unfiltered_in_transaction_set(wallet)))

        new_accounting_engine.initialize(taxable_event_iterator, acquired_lot_iterator)

        try:
            gain_loss: GainLoss
            taxable_event: AbstractTransaction
            acquired_lot: Optional[InTransaction]
            taxable_event_amount: RP2Decimal
            acquired_lot_amount: RP2Decimal
            total_amount: RP2Decimal = ZERO

            (taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
                new_accounting_engine, None, None, ZERO, ZERO
            )

            while taxable_event:
                AbstractTransaction.type_check("taxable_event", taxable_event)
                if acquired_lot is None:
                    raise RP2RuntimeError("Parameter 'acquired_lot' is None")
                InTransaction.type_check("acquired_lot", acquired_lot)

                if taxable_event_amount == acquired_lot_amount:
                    gain_loss = GainLoss(configuration, taxable_event_amount, taxable_event, acquired_lot)
                    total_amount += taxable_event_amount
                    gain_loss_set.add_entry(gain_loss)
                    (taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
                        new_accounting_engine, None, None, ZERO, ZERO
                    )
                elif taxable_event_amount < acquired_lot_amount:
                    gain_loss = GainLoss(configuration, taxable_event_amount, taxable_event, acquired_lot)
                    total_amount += taxable_event_amount
                    gain_loss_set.add_entry(gain_loss)
                    (taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
                        new_accounting_engine, taxable_event, acquired_lot, ZERO, acquired_lot_amount - taxable_event_amount
                    )
                else:
                    gain_loss = GainLoss(configuration, acquired_lot_amount, taxable_event, acquired_lot)
                    total_amount += acquired_lot_amount
                    gain_loss_set.add_entry(gain_loss)
                    (taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
                        new_accounting_engine, taxable_event, None, taxable_event_amount - acquired_lot_amount, ZERO
                    )
        except AcquiredLotsExhaustedException:
            raise RP2ValueError("Total in-transaction crypto value < total taxable crypto value") from None
        except TaxableEventsExhaustedException:
            pass

        wallet_gain_loss_sets[wallet] = gain_loss_set

    return wallet_gain_loss_sets


def compute_tax(configuration: Configuration, accounting_engine: AccountingEngine, input_data: InputData) -> Dict[str, ComputedData]:
    Configuration.type_check("configuration", configuration)
    AccountingEngine.type_check("accounting_engine", accounting_engine)
    InputData.type_check("input_data", input_data)

    wallet_taxable_event_sets = _create_unfiltered_taxable_event_set(configuration, input_data)
    wallet_gain_loss_sets = _create_unfiltered_gain_and_loss_set(configuration, accounting_engine, input_data, wallet_taxable_event_sets)

    computed_data: Dict[str, ComputedData] = {}
    for wallet, gain_loss_set in wallet_gain_loss_sets.items():
        computed_data[wallet] = ComputedData(
            input_data.asset,
            wallet_taxable_event_sets[wallet],
            gain_loss_set,
            input_data,
            configuration.from_date,
            configuration.to_date,
        )

    return computed_data

This example assumes that the InputData class has methods like get_unfiltered_in_transaction_set(wallet), get_unfiltered_out_transaction_set(wallet), and get_unfiltered_intra_transaction_set(wallet) to retrieve transactions for a specific wallet. You will need to implement these methods and ensure that the

TransactionSet and GainLossSet classes can handle wallet-specific transactions.

By following these steps, you can modify the project to track transactions by individual wallets and calculate the cost basis based on the purchase price for the crypto asset in each wallet.

It goes on to say that AccountingEngine of course needs to be modified:

class AccountingEngine:
    def __init__(self, years_2_methods: Dict[int, str]):
        self.years_2_methods = years_2_methods
        self.wallets = {}

    def initialize(self, taxable_event_iterator: Iterator[AbstractTransaction], acquired_lot_iterator: Iterator[InTransaction], wallet: str):
        self.wallets[wallet] = {
            "taxable_event_iterator": taxable_event_iterator,
            "acquired_lot_iterator": acquired_lot_iterator,
        }

    def get_next_taxable_event_and_amount(
        self,
        wallet: str,
        taxable_event: Optional[AbstractTransaction],
        acquired_lot: Optional[InTransaction],
        taxable_event_amount: RP2Decimal,
        acquired_lot_amount: RP2Decimal,
    ) -> Tuple[AbstractTransaction, Optional[InTransaction], RP2Decimal, RP2Decimal]:
        wallet_data = self.wallets[wallet]
        taxable_event_iterator = wallet_data["taxable_event_iterator"]
        acquired_lot_iterator = wallet_data["acquired_lot_iterator"]

        # Logic to get the next taxable event and acquired lot for the specified wallet
        # ...

    def get_acquired_lot_for_taxable_event(
        self,
        wallet: str,
        taxable_event: AbstractTransaction,
        acquired_lot: Optional[InTransaction],
        taxable_event_amount: RP2Decimal,
        acquired_lot_amount: RP2Decimal,
    ) -> Tuple[AbstractTransaction, Optional[InTransaction], RP2Decimal, RP2Decimal]:
        wallet_data = self.wallets[wallet]
        acquired_lot_iterator = wallet_data["acquired_lot_iterator"]

        # Logic to get the acquired lot for the specified taxable event and wallet
        # ...

Basically, just adding wallet storage to each part of the engine. Is there something I'm missing? This is AI generated, so please go over it carefully.

@eprbell
Copy link
Owner Author

eprbell commented Nov 12, 2024

Thanks, I'll read it. I had some rough ideas on how to approach the problem:

  • do queue analysis to understand which wallet each lot ends up at after transfers;
  • do the tax analysis wallet by wallet, rather than globally (as we do now);
  • somehow normalize/unify the results in the same report.

My only problem right now is finding the time to work on it...

@macanudo527
Copy link
Collaborator

macanudo527 commented Nov 12, 2024

I think we can take it piece by piece. The first piece is to modify the initial reading in of the data to sort the different lots into different wallets. We can probably start there and build out some tests for it. I should have about the next month or so to work on and submit code. I don't think it will take that much time as long as we are pretty systematic about it.

For example, I think the first step is to create a function in tax_engine.py that sorts the transactions in to wallets. We can create the function now and write an isolated test for it and get a PR for that.

I guess this function would sort in and out transactions pretty easily, just whatever exchange they happened on. Then, intra transaction will be split into non-taxable in and out transactions in their respective wallets.

I think this handles this first step right?

  • do queue analysis to understand which wallet each lot ends up at after transfers;

Then we can cycle through the wallets probably in a multithread way to process all the transactions using the current engine. That will cover the next step:

  • do the tax analysis wallet by wallet, rather than globally (as we do now);

And finally merge all the GainLossSets for the final report. Am I missing something big?

I can probably put the code together as long as you can review it by the end of the year.

@eprbell
Copy link
Owner Author

eprbell commented Nov 13, 2024

Yes, that sounds reasonable, however I'll add a few more considerations that complicate the picture slightly:

  • Queue analysis (or transfer analysis) isn't simply about tracking lots and where they go: transferring can split a lot into parts. E.g. if I buy 1 BTC on CB and then send 0.5 BTC to a HW, I started with one lot and, after transferring, I ended up with two lots.
  • The tax engine should be able to work using either universal application or per wallet application.
  • Selection of which one to use should be left to the country plugin.
  • Additionally, some countries (like the US) support universal up to a certain year (2024 for the US), then per wallet: this should also be reflected in the country plugin.

There are additional complications such as which method to use for transfers (FIFO, LIFO, etc.). Some options:

  • Just use FIFO;
  • Same as the accounting method;
  • Let user select a method that may be different than the accounting method.

I think we should start option one or two.

I think we should write a brief high level design of this feature first: let me see if I can come up with a quick document in the weekend, to start the discussion.

@macanudo527
Copy link
Collaborator

Sorry, I guess I didn't realize until just now that this only applies to 2025, so for when we file taxes in 2026. For some reason, I thought we had to have this ready for filing taxes in 2025. I was in a panic. I guess we still have time, but if you outline what is needed I can try to have a whack at it.

@eprbell
Copy link
Owner Author

eprbell commented Nov 13, 2024

Yes, according to the Reddit thread, new rules are effective from 1/1/2025. So we have 1 year to figure it out.

@eprbell
Copy link
Owner Author

eprbell commented Nov 18, 2024

I'm making progress on the design of per-wallet application but it is still unfinished. I realized we can apply the existing accounting method layer to the new logic to pick which lot to transfer, which is nice. However we need a few additions to the existing infrastructure:

  • add add_acquired_lot() method to AbstractAcquiredLotCandidates
  • add a get_artificial_id_from_row() function to unify management of artificial transaction row id (which are negative): the new design creates one artificial InTransaction for each IntraTransaction and this new transaction needs an artificial id.

Here's the unfinished design so far. How to switch from universal to per-wallet from one year to the next is still TBD.

@macanudo527
Copy link
Collaborator

Okay, I just looked up details about Japan, and they use universal wallet and you can make use of FIFO, LIFO, etc... based on all of your holdings as a whole. So, we will have to combine universal wallet with FIFO, etc... Does your plan account for that?
Honestly, I'm okay with the current system of FIFO and universal wallet if we can't implement universal wallet and LIFO for example. But, it is something that we will probably need in the future to support all countries.

@eprbell
Copy link
Owner Author

eprbell commented Nov 20, 2024

Universal application + FIFO/LIFO/HIFO/LOFO is already supported today (you can even change from one accounting method to another year over year). See: https://github.com/eprbell/rp2/blob/main/docs/user_faq.md#can-i-change-accounting-method. Based on what you're saying it sounds like we can add more accounting methods here: https://github.com/eprbell/rp2/blob/main/src/rp2/plugin/country/jp.py#L41

The design I'm working on supports any combination of per-wallet/universal application + FIFO/LIFO/HIFO/LOFO (including changing from one combination to another year over year). This high-generality approach is proving both interesting and hard to do, so it will require some more time to iron out all the details. It's good that we can reuse the existing accounting method infrastructure for lot selection in transfers, but the problem goes deeper than I thought. When I'm closer to something presentable, we can discuss it and brainstorm a bit.

@eprbell
Copy link
Owner Author

eprbell commented Dec 1, 2024

The per-wallet application design is more or less complete. It can still be improved, but it captures most of the concepts: feel free to take a look and let me know what you think. Next I will probably do a bit of prototyping to make sure the ideas behind the design hold water.

@macanudo527
Copy link
Collaborator

@eprbell I read through it and it looks pretty sound. I'll have to give it some time and read through it again just to double check, but I think this will handle what we need. Thanks for working it out. It looks like a lot of work and you gave some good examples.

@eprbell eprbell removed the help wanted Extra attention is needed label Dec 14, 2024
@eprbell
Copy link
Owner Author

eprbell commented Dec 14, 2024

I'm making good progress on the implementation and unit testing of the transfer analysis function. Sorry, @macanudo527, I know you expressed some interest in working on this: I wanted to write a prototype to verify the design, but ended up finding more and more corner cases, and adjusting the code accordingly to deal with them. So basically what started as a prototype is becoming the real thing. I will open a PR for this though: it would be good to get your feedback before merging.

@macanudo527
Copy link
Collaborator

No worries, I can just spend time on some of the other core functions instead. Looking forward to it. I'll be out for the end of the year (Dec 23rd - Jan 7th), but after that I can look at it.

@eprbell
Copy link
Owner Author

eprbell commented Dec 16, 2024

Sounds good (we're in no rush). Have a great time during the holidays!

@eprbell
Copy link
Owner Author

eprbell commented Dec 16, 2024

US tax payers, watch this informative interview by Andreas Antonopulous on per-wallet application in the US.

@gbtorrance
Copy link

Hi. Hope you don't mind me commenting as a non-code-contributing member of the RP2 community.

I watched the Andreas Antonopulous interview. Honestly, I found it extremely difficult to follow due to him constantly interrupting the guest. I found this video by "Crypto Tax Girl" to be much more clear and easy to follow. If you have a chance to watch it, I'd be interested if you feel it lines up with your understanding of these changes.

A have a few questions if I may:

  1. Under the new per-wallet tracking with FIFO, if one transfers crypto from wallet A to wallet B, presumably the first crypto in wallet A will be transferred to wallet B. But when it gets to wallet B, where does it go in the FIFO queue? Does it automatically go to the end with a new date (i.e. the date of transfer), or does it get slotted into the queue at some point in the middle based on the original date of purchase? (Hopefully that makes sense.)
  2. There has been talk (including in both videos) about the "safe harbor" provision. I've seen two extremes for what one needs to do by the end of 2024 to comply. One is to simply declare the rules one is going to use going forward. (This is like what Crypto Tax Girl proposes.) The other is to fully document one's crypto holdings and how cost basis is allocated to each wallet. The latter approach would seem to be infeasible for RP2 users, given that the code changes are still in process, right? Is there a way -- even if somewhat manual -- to know at this point how RP2 will allocate cost basis to each wallet so that this information can be documented by the end of 2024?

Thoughts?

Thanks for all you do to make RP2 available and up-to-date! Much appreciated.

@eprbell
Copy link
Owner Author

eprbell commented Dec 19, 2024

Feedback is always welcome from anybody (code-contributing or not). Thanks for the video: I haven't watched it yet, but I will in the weekend. I think the main takeaway from Andreas' video is to follow the procedure he and his guest recommended before 12/31/24: basically consolidate everything into one wallet (if possible). The Description in the video summarizes the steps to take. This will simplify accounting going forward.

As for your questions:

  1. The current thinking is to let the user select two separate things: the transfer semantics and the accounting method. The first one is part of the new feature being developed to support per-wallet application, the second one is already available today (and doesn't change). Either of these two can be FIFO, LIFO, HIFO or LOFO. See the design document). So you could use FIFO for transfers and then HIFO as the per-wallet accounting method after transfers. However this feature is still in development and may change: take the above with a grain of salt. Again, it may be a good idea to consolidate everything before EOY as suggested by Andreas.
  2. The first feature (document each lot's cost basis) won't be supported. The second one will (see answer above).

Hope this helps.

@gbtorrance
Copy link

Thank you for the reply!

FWIW, I reviewed the design document -- twice -- but since it's very code centric, and I'm not familiar with the overall application design, I wasn't able to understand all that much about the approach being taken. (That's not a issue. It just is what it is.)

Regarding your reply, let me see if I understand:

So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?

Am I understanding that correctly?

Assuming I am, I'm still not clear what happens to a token when it is transferred to another wallet and "transfer semantics" is FIFO. Does it get assign a new "in date" in the destination wallet or does it retain its original "in date" from when it was purchased?

In my original post I asked it this way:

Under the new per-wallet tracking with FIFO, if one transfers crypto from wallet A to wallet B, presumably the first crypto in wallet A will be transferred to wallet B. But when it gets to wallet B, where does it go in the FIFO queue? Does it automatically go to the end with a new date (i.e. the date of transfer), or does it get slotted into the queue at some point in the middle based on the original date of purchase?

At the time I imagined each wallet would have a "queue" of transactions, but now I understand it's probably more like a pool of transactions that can be sorted in any way at runtime, as needed, based on whether a transfer is being done ("transfer semantics") or a sell is being done ("accounting method"). Is that correct?

That being the case, I would guess that the transferred token (and corresponding basis) would retain the original purchase date even after it is moved to the destination wallet. Here's an example (a variant of yours from the design):

  • 1/1: InTransaction of 10 BTC on Coinbase
  • 2/1: InTransaction of 5 BTC on Kraken
  • 3/1: IntraTransaction of 4 BTC from Coinbase to Kraken
  • 4/1: OutTransaction of 2 BTC from Kraken

If both "transfer semantics" and "accounting method" are FIFO, does that mean that the OutTransaction on 4/1 will use the Coinbase transaction basis from 1/1 or the Kraken transaction basis from 2/1? I would assume the former. In other words, the original "in date" associated with the 4 BTC moved from Coinbase to Kraken will be retained, and when FIFO is used for the OutTransaction, 2 of those 4 BTC will be sold, since 1/1 is the new "first in" date of the Kraken wallet (even though, technically, the 5 BTC bought on Kraken on 2/1 were "first in" prior to the transfer on 3/1 having occurred).

Does that make any sense? Hopefully. Thoughts?

As for the second question, I'm not sure what you mean by this:

  1. The first feature (document each lot's cost basis) won't be supported. The second one will (see answer above).

I understand that, per Andreas, it probably makes sense to try and consolidate wallets as much as possible. But are you going to actually make a "declaration" before 1/1/25 and either email it to yourself or use the blockchain time-stamping approach Andreas suggested to have something that can be provided to the IRS, if needed, as proof of claiming "safe harbor"? And, if so, what is that declaration going to contain?

Thanks!

@eprbell
Copy link
Owner Author

eprbell commented Dec 20, 2024

I'm replying inline, however keep in mind that what I'm saying is based on my current ideas for a design that is still in flux. So don't make tax decisions solely based on what I'm describing here, because it may change. This is why I was highlighting Andreas' solution: it makes your crypto accounting simple and clear, regardless of what happens with tax software.

So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?

Yes to both questions. The transfer semantics is what is used to populate per-wallet queues from the universal queue that is used up to the end of 2024 (it is also used when transferring from one per-wallet queue to another).

Assuming I am, I'm still not clear what happens to a token when it is transferred to another wallet and "transfer semantics" is FIFO. Does it get assign a new "in date" in the destination wallet or does it retain its original "in date" from when it was purchased?

Good question. The current idea is to create an artificial InTransaction in the "to" wallet. This artificial InTransaction has:

  • timestamp: same as the IntraTransaction;
  • crypto_in: minimum of IntraTransaction crypto_received and remaining amount in the from lot that was selected with transfer semantics;
  • spot_price: same as the from lot that was selected with transfer semantics.

Under the new per-wallet tracking with FIFO, if one transfers crypto from wallet A to wallet B, presumably the first crypto in wallet A will be transferred to wallet B. But when it gets to wallet B, where does it go in the FIFO queue? Does it automatically go to the end with a new date (i.e. the date of transfer), or does it get slotted into the queue at some point in the middle based on the original date of purchase?

I think this was already answered above. It goes into a new queue that is specific to wallet B (that's the whole idea behind per-wallet application). However you need to consider the case in which you have 1 BTC in wallet A and you transfer 0.5 BTC to wallet B. In this case you're splitting the original lot. Currently this is captured by leaving the 1 BTC in the queue of wallet A and creating an artificial transaction for 0.5 BTC in wallet B. The two transactions are linked and are updated together by the tax engine (for more on this check this).

At the time I imagined each wallet would have a "queue" of transactions, but now I understand it's probably more like a pool of transactions that can be sorted in any way at runtime, as needed, based on whether a transfer is being done ("transfer semantics") or a sell is being done ("accounting method"). Is that correct?

Not quite: see explanation above about one queue per wallet.

That being the case, I would guess that the transferred token (and corresponding basis) would retain the original purchase date even after it is moved to the destination wallet. Here's an example (a variant of yours from the design):
* 1/1: InTransaction of 10 BTC on Coinbase
* 2/1: InTransaction of 5 BTC on Kraken
* 3/1: IntraTransaction of 4 BTC from Coinbase to Kraken
* 4/1: OutTransaction of 2 BTC from Kraken
If both "transfer semantics" and "accounting method" are FIFO, does that mean that the OutTransaction on 4/1 will use the Coinbase transaction basis from 1/1 or the Kraken transaction basis from 2/1? I would assume the former. In other words, the original "in date" associated with the 4 BTC moved from Coinbase to Kraken will be retained, and when FIFO is used for the OutTransaction, 2 of those 4 BTC will be sold, since 1/1 is the new "first in" date of the Kraken wallet (even though, technically, the 5 BTC bought on Kraken on 2/1 were "first in" prior to the transfer on 3/1 having occurred).

In your example, using FIFO for everything, the 4/1 OutTransaction would use the 2/1 InTransaction. Note that the Kraken queue would also have an arificial InTransaction on 3/1 containing 4 BTC and linked to the 1/1 transaction. But in your example the artificial transaction is not exercised because the 2/1 transaction has enough funds to cover the 2 BTC of the OutTransaction.

If the OutTransaction had, say, 7 BTC instead of 2, then the code would use first the entire 2/1 lot and then 2 BTC from the artficial InTransaction (this also causes its parent transaction 1/1 to be updated as explained here).

As for the second question, I'm not sure what you mean by this:

  1. The first feature (document each lot's cost basis) won't be supported. The second one will (see answer above).

I mean that RP2 won't let the user select which lot goes into which wallet queue arbitrarily. RP2 will take an algorithmic approach: the user selects the transfer semantics and the code moves the funds around.

I understand that, per Andreas, it probably makes sense to try and consolidate wallets as much as possible. But are you going to actually make a "declaration" before 1/1/25 and either email it to yourself or use the blockchain time-stamping approach Andreas suggested to have something that can be provided to the IRS, if needed, as proof of claiming "safe harbor"? And, if so, what is that declaration going to contain?

This is probably a question for your tax advisor, but the rough idea is to move everything to one single wallet and then take snapshots of all accounts, generate an RP2 report and timestamp everything on Dec 31st. By moving to a single wallet you're essentially causing universal and per-wallet approach to match, because there is now only one wallet having one queue with all the funds.

Thanks for asking questions and engaging in conversation. It's good to brainstorm the ideas behind the design and see if they hold water.

@gbtorrance
Copy link

gbtorrance commented Dec 20, 2024

Thanks for the reply!

Agreed about the conversation and brainstorming. Even though I'm not coding this, I find it very helpful for my own understanding.

If both "transfer semantics" and "accounting method" are FIFO, does that mean that the OutTransaction on 4/1 will use the Coinbase transaction basis from 1/1 or the Kraken transaction basis from 2/1? I would assume the former. In other words, the original "in date" associated with the 4 BTC moved from Coinbase to Kraken will be retained, and when FIFO is used for the OutTransaction, 2 of those 4 BTC will be sold, since 1/1 is the new "first in" date of the Kraken wallet (even though, technically, the 5 BTC bought on Kraken on 2/1 were "first in" prior to the transfer on 3/1 having occurred).

In your example, using FIFO for everything, the 4/1 OutTransaction would use the 2/1 InTransaction. Note that the Kraken queue would also have an arificial InTransaction on 3/1 containing 4 BTC and linked to the 1/1 transaction. But in your example the artificial transaction is not exercised because the 2/1 transaction has enough funds to cover the 2 BTC of the OutTransaction.

This was a surprise to me, so I decided to post basically this exact question on the Reddit forum to see if @JustinCPA would respond, which he did. He seems to say the opposite of what you've said.

image

I feel like this could be an issue for the current design -- at least for US taxpayers, and assuming @JustinCPA is correct.

Thoughts?

@eprbell
Copy link
Owner Author

eprbell commented Dec 21, 2024

Sounds like you found a bug in the design! Thanks for getting in the weeds and asking JustinCPA: his explanation is convincing. The bug is that the artificial InTransaction was using the timestamp of the transfer instead of the timestamp of the from InTransaction. I already fixed the code so that the behavior is as explained by Justin. I will be posting some initial code for the transfer analysis algorithm in a PR soon (together with unit tests).

@gbtorrance
Copy link

gbtorrance commented Dec 21, 2024

Great! Thanks!

One thing that comes to mind: You may well be taking care of this already, so forgive me if you are, but you may want to make sure that transfers, if they are using the timestamp of the "from" InTransaction, do not inadvertently allow other invalid transactions to occur. An example is probably needed to explain:

  • 1/1: InTransaction of 10 BTC on Coinbase
  • 2/1: OutTransaction of 1 BTC from Kraken (!! INVALID !!)
  • 3/1: IntraTransaction of 3 BTC from Kraken to Binance (!! INVALID !!)
  • 4/1: IntraTransaction of 5 BTC from Coinbase to Kraken

Of course the transactions on 2/1 and 3/1 seem obviously invalid when looked at like that. But, I could imagine a scenario where the 5 BTC transferred on 4/1 from Coinbase to Kraken inherit the "from" InTransaction date of 1/1, making the Kraken OutTransaction on 2/1 and IntraTransaction on 3/1 seem valid. But obviously they're not (because the 5 BTC has not been transferred yet). Make sense?

Keep in mind I don't understand the overall app design or the design for these changes. I'm just looking at this as an outsider.

Thanks!

@gbtorrance
Copy link

So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?

Yes to both questions. The transfer semantics is what is used to populate per-wallet queues from the universal queue that is used up to the end of 2024 (it is also used when transferring from one per-wallet queue to another).

One more thought: Is it possible to use different transfer semantics to:

  • populate per-wallet queues from the universal queue, and,
  • transfer from one wallet to another in 2025 and beyond?

Don't think that I would need this, but -- if I'm understanding correctly -- others may. Take a look at this post for context. Basically, I think some may want to use HIFO for populating the wallets from the universal queue, and then FIFO going forward (as I understand that is required for "global allocation" / non-spec-ID).

image

@eprbell
Copy link
Owner Author

eprbell commented Dec 21, 2024

One thing that comes to mind: You may well be taking care of this already, so forgive me if you are, but you may want to make sure that transfers, if they are using the timestamp of the "from" InTransaction, do not inadvertently allow other invalid transactions to occur. An example is probably needed to explain:

* 1/1: `InTransaction` of 10 BTC on Coinbase

* 2/1: `OutTransaction` of 1 BTC from Kraken (!! INVALID !!)

* 3/1: `IntraTransaction` of 3 BTC from Kraken to Binance (!! INVALID !!)

* 4/1: `IntraTransaction` of 5 BTC from Coinbase to Kraken

Of course the transactions on 2/1 and 3/1 seem obviously invalid when looked at like that. But, I could imagine a scenario where the 5 BTC transferred on 4/1 from Coinbase to Kraken inherit the "from" InTransaction date of 1/1, making the Kraken OutTransaction on 2/1 and IntraTransaction on 3/1 seem valid. But obviously they're not (because the 5 BTC has not been transferred yet). Make sense?

Ah, good point. This means that my previous approach was only 50% wrong :-) because the artificial transaction needs both timestamps: one for holding period and the other for fund availability. Let me think a bit on how to best model this: we probably need a subclass of InTransaction to capture this.

Keep in mind I don't understand the overall app design or the design for these changes. I'm just looking at this as an outsider.

No worries: your feedback as a user has been very valuable. Keep it coming!

@eprbell
Copy link
Owner Author

eprbell commented Dec 22, 2024

So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?

Yes to both questions. The transfer semantics is what is used to populate per-wallet queues from the universal queue that is used up to the end of 2024 (it is also used when transferring from one per-wallet queue to another).

One more thought: Is it possible to use different transfer semantics to:

* populate per-wallet queues from the universal queue, and,

* transfer from one wallet to another in 2025 and beyond?

Don't think that I would need this, but -- if I'm understanding correctly -- others may. Take a look at this post for context. Basically, I think some may want to use HIFO for populating the wallets from the universal queue, and then FIFO going forward (as I understand that is required for "global allocation" / non-spec-ID).

image

Interesting: the current design allows for changing transfer semantics and accounting method year over year in any combination supported by the country plugin. What you're describing would be an extra one-time only transfer semantics for initial per-wallet queue population: this is not supported yet. With the existing design you could select HIFO transfer semantics in 2025 and then switch to FIFO in following years: not exactly what you're asking for but it's an approximation.

I think we should finish the basic design and implementation of per-wallet application and then we can think about something like this as a potential advanced feature.

@gbtorrance
Copy link

I think we should finish the basic design and implementation of per-wallet application and then we can think about something like this as a potential advanced feature.

Of course. Just figured I'd mention it for your awareness. (I personally don't anticipate needing it.)

With the existing design you could select HIFO transfer semantics in 2025 and then switch to FIFO in following years: not exactly what you're asking for but it's an approximation.

I suppose, though I don't think that would comply with the IRS requirements. I'm no expert, but my understanding is that it has to either be FIFO or specific identification going forward.

I totally get your point, though. "One step at a time" :-)

@eprbell
Copy link
Owner Author

eprbell commented Dec 23, 2024

With the existing design you could select HIFO transfer semantics in 2025 and then switch to FIFO in following years: not exactly what you're asking for but it's an approximation.

I suppose, though I don't think that would comply with the IRS requirements. I'm no expert, but my understanding is that it has to either be FIFO or specific identification going forward.

The tax engine isn't US-specific so it allows this general behavior: then the country plugin can impose constraints as needed on a country-specific basis.

@gbtorrance
Copy link

Just wanted to share this post. It's a newer @JustinCPA post than the one posted above. I found it quite helpful.

I'm trying to make sure I have all my proverbial "ducks in a row" before the end of the year.

From Justin's post:

Global Allocation Method
Global Allocation Method is one option for performing the migration. This method focuses on assigning a governing "rule" to your unused basis for how the allocation should be performed. In other words, a rule like "lowest cost basis to highest balance" is perfect. What does this mean? Lets look at a scenario.

You have 1 ETH in Wallet A, 5 ETH in Wallet 5, and 10 ETH in Wallet C for a total of 16 ETH. Assigning the "lowest cost basis to highest balance" global allocation rule, we would go to your spreadsheet with all your tax lots of unused basis as of 11:59pm 12/31/2024 and you would start with the lowest cost basis lots. Lot by lot, you would assign them Wallet C first, until you reached 10 ETH in that new pool, then you would take the next lowest cost basis tax lots and assign them to Wallet B until 5 ETH have been assigned to that pool. Finally, the remaining tax lots (which will be the highest cost basis) will be assigned to Wallet A.

Other examples include: "Oldest tax lots to highest balance", "Oldest tax lots to least active wallet", "Highest cost basis to lowest balance" etc.

Specific Allocation Method
This method does not focus on assigning a rule, but rather allows the taxpayer to specifically allocate each unit as they see fit. In other words, taking that spreadsheet with the pool of unused basis, a taxpayer could go line by line and assign each tax lot to the wallet or exchange they want, until they reach the proper amount of assets held in that wallet/exchange.

As I understand it, one has to use either "Global Allocation" or "Specific Allocation" to determine how the basis from Universal application is applied to individual wallets. ("Global Allocation" sounds much easier to me than "Specific Allocation", so that's what I'm planning on.)

The thing is, I understand that RP2 is intending to support different methods for the transition from Universal to Per-wallet, such as FIFO. But as I think about it more, it feels like that is insufficient. (Either that or I'm missing something -- which is very possible.)

A method such as FIFO takes care of pulling basis from the Universal queue, but how does it account for allocating that basis to 1-n wallets? Wallets do not have any inherent ordering. They just exist. A method is needed to determine the ordering of wallets, right?

Justin provides examples such as:

  • "Lowest cost basis to highest balance"
  • "Oldest tax lots to highest balance"
  • "Oldest tax lots to least active wallet"
  • "Highest cost basis to lowest balance"

Can you shed some light on how this will be supported with RP2?

My understanding is that, in order to be in compliance, it will be necessary to *individually document this before the end of 2024. At at this moment I'm not clear on what allocation method RP2 will be using. (I can't just say FIFO, because that doesn't cover wallet selection.)

Thoughts? Thanks!

@eprbell
Copy link
Owner Author

eprbell commented Dec 28, 2024

Thanks for the additional feedback.

The current RP2 per-wallet design uses a combination of transfers (IntraTransactions) plus the new concept of transfer semantics (which can be FIFO, LIFO, etc.) to determine where funds go. So the "Global Allocation Method" in the current version of the design would be something like: funds are directed to wallets based on transfers and within each transfer the cost basis is chosen using transfer semantics.

However choosing "Specific Allocation Method" might also work, because RP2 tracks lot pairings precisely, so when the time comes to file taxes the RP2 full report will describe in detail how cost basis has been allocated (my understanding is that when picking "Specific Allocation Method" you don't have to declare the exact allocation by Dec 31st, but correct me if I'm wrong).

Again, these are just thoughts I'm giving while design and implementation are still ongoing: unfortunately this change of rules didn't give us enough time to design and implement this well in advance (also none of this is tax or financial advice). I think the best bet is to follow the advice in Andreas' video, because, after consolidation, your situation becomes agnostic to what happens with tax software.

We could also implement methods like those suggested by Justin, but unfortunately there won't be even time to finish the basic implementation by Dec 31st.

@gbtorrance
Copy link

Thanks for the reply!

(my understanding is that when picking "Specific Allocation Method" you don't have to declare the exact allocation by Dec 31st, but correct me if I'm wrong).

Per Justin (in the post above):

For those taking the Specific Allocation Method, you do not need to take specific action before year end. However, you will need to perform the allocation and migration before you make any sales, transfers, or transactions in 2025.

So the "Global Allocation Method" in the current version of the design would be something like: funds are directed to wallets based on transfers and within each transfer the cost basis is chosen using transfer semantics.

Can you elaborate on "directed to wallets based on transfers"? Wouldn't that require going back to the beginning of RP2 transaction history to do the allocation? And wouldn't that result in somehow changing history for prior tax years? I guess I don't understand how this would work.

Again, these are just thoughts I'm giving while design and implementation are still ongoing: unfortunately this change of rules didn't give us enough time to design and implement this well in advance

I totally understand! And I appreciate all your effort on this. It's a tricky situation. (I'm just trying to understand as best I can to try and be in compliance as best I can by the deadline.)

@eprbell
Copy link
Owner Author

eprbell commented Dec 28, 2024

For those taking the Specific Allocation Method, you do not need to take specific action before year end. However, you will need to perform the allocation and migration before you make any sales, transfers, or transactions in 2025.

OK, that's what I thought. What I was saying is that you would let RP2 do the allocation and then use the full report as the documentation of Specific Allocation.

So the "Global Allocation Method" in the current version of the design would be something like: funds are directed to wallets based on transfers and within each transfer the cost basis is chosen using transfer semantics.

Can you elaborate on "directed to wallets based on transfers"? Wouldn't that require going back to the beginning of RP2 transaction history to do the allocation? And wouldn't that result in somehow changing history for prior tax years? I guess I don't understand how this would work.

The transfer analysis algorithm from the new design follows funds movements to understand how funds are moved around. Then it uses transfer semantics to decide how to assign cost basis.

For example, if you have:

  • 1/1: InTransaction of 10 BTC on Coinbase
  • 2/1: InTransaction of 20 BTC on Coinbase
  • 3/1: IntraTransaction of 15 BTC from Coinbase to Kraken

Assuming you selected FIFO transfer semantics, it would result in:

  • 15 BTC on Coinbase (from the 2/1 lot because the 1/1 lot has been fully exhausted)
  • 15 BTC on Kraken, of which:
    • 10 BTC on Kraken (with the 1/1 lot as cost basis)
    • 5 BTC on Kraken (with the 2/1 lot as cost basis)

If instead you selected LIFO transfer semantics, the result would be:

  • 15 BTC on Coinbase (10 from the 1/1 lot and 5 from the 2/1 lot)
  • 15 BTC on Kraken, of which:
    • 15 BTC on Kraken (with the 2/1 lot as cost basis)

In the first case the Global Allocation Method is: funds are directed to wallets based on transfers and within each transfer the cost basis is chosen using FIFO.

In the second case the Global Allocation Method is: funds are directed to wallets based on transfers and within each transfer the cost basis is chosen using LIFO.

As for the going back to the beginning of history, yes, that is required (even before the per-wallet change), however for years before 2024 RP2 will continue to use universal application and from 2025 it will switch to per-wallet.

I totally understand! And I appreciate all your effort on this. It's a tricky situation. (I'm just trying to understand as best I can to try and be in compliance as best I can by the deadline.)

You and I both :-)

@jayr0d
Copy link
Contributor

jayr0d commented Dec 29, 2024

I apologize for missing earlier discussions. As an engineer, not a tax advisor, I want to share my thoughts on the new rules regarding capital gains.

Your interpretation is correct. While edge cases can be complex, users can follow simple guidelines. To defer capital gains, avoid FIFO, especially with Bitcoin's upward trend. LIFO or HIFO are preferable, which I advocated in issue #79. Spec-ID would be too burdensome for me, so I will stick with HIFO.

Now, HIFO applies to each wallet individually, increasing the developer's burden but also requiring users to consolidate wallets before 2025 and plan for the future.

Ensure your DaLI-RP2 input files are accurate for tax reporting. Consolidating wallets is wise; consider moving older coins to one wallet and segregating using additional wallets for short and long-term capital gains.

Thanks again to eprbell and macanudo527 for your great work in the community.

@gbtorrance
Copy link

Thanks for the detailed explanation. However, I still feel unsure about the approach. (That may just be due to lack of understanding on my part.)

As for the going back to the beginning of history, yes, that is required (even before the per-wallet change), however for years before 2024 RP2 will continue to use universal application and from 2025 it will switch to per-wallet.

Let's consider a more complex example involving both universal application (for 2024) and per-wallet (for 2025). Let's assume FIFO throughout for simplicity.

  • 1/1/24: InTransaction of 10 BTC on Coinbase
  • 2/1/24: InTransaction of 20 BTC on Kraken
  • 3/1/24: OutTransaction of 5 BTC from Kraken

Because of universal application, the basis for the 3/1/24 OutTransaction will be pulled from the 1/1/24 Coinbase InTransaction due to FIFO, even though the actual BTC was sold from Kraken. So now the pool of unused basis in the universal "pool" looks like this:

  • 5 BTC purchased on Coinbase on 1/1/24
  • 20 BTC purchased on Kraken on 2/1/24

And the actual BTC on exchanges looks like this

  • 10 BTC on Coinbase
  • 15 BTC on Kraken

So now in 2025 we're on per-wallet application. Let's take two scenarios.

Scenario A:

  • 5/1/25: OutTransaction of 10 BTC from Coinbase

Scenario B:

  • 5/1/25: OutTransaction of 10 BTC from Kraken

How will these scenarios be handled?

What basis will be assigned for each 5/1/25 OutTransaction?

Will the scenarios result in the same cost basis being assigned or different?

I would argue that the cost basis for these two scenarios must be different under per-wallet application (assuming different BTC price on 1/1/24 and 2/1/24). The basis needs to essentially be pre-assigned (implicitly or explicitly) to Coinbase and Kraken before the transaction of 5/1/25 occurs. The OutTransaction on 5/1/25 cannot affect the assignment of basis, it can only be affected by the basis already assigned.

Thoughts? Thanks.

@orientalperil
Copy link

The example presented by @gbtorrance is not handled by the design. Specifically the Universal Application section is not actually an example of universal application. It is actually an example of per-wallet application.

In the example of using universal application, FIFO transfer method, and FIFO accounting method the balances of each wallet will not match the cost basis units of the lots as they are transferred. This means that if you are switching from universal application to per-wallet application from one year to the next year there must be a step in between to assign cost basis to fill in the spent cost basis that will be missing from each wallet.

@eprbell
Copy link
Owner Author

eprbell commented Dec 29, 2024

You're asking all the right questions! Your scenario made me realize that my approach (using the transfer analysis algorithm as Global Allocation Method) doesn't work: we'll have to implement the Global Allocation Methods described by Justin (initially I thought we would implement them later next year as a nice to have, but instead they are actually required and need to be part of the design from the start).

The good news is that the transfer analysis algorithm I'm implementing is still correct: it just can't be used for Global Allocation, but it can be used in steady state.

Your example made me realize the problem. If we use the transfer analysis algorithm as Global Allocation Method (like I was planning to do) we end up with not enough cost basis lots on Coinbase. This is because on 3/1/24 we end up with 5 BTC of unused basis on Coinbase but then on 5/1/25 we are selling 10BTC: in a per-wallet setup the cost basis for the 10 BTC must all come from Coinbase (and we only have 5 BTC of unused basis there, even though there are 10 BTC on the balance).

This discrepancy must be the reason they introduced the Global/Specific Allocation Methods: essentially a reset of the cost basis assignments before starting out with the new per-wallet regimen.

So the answer to your question is:

  • up to end of 2024 things are exactly as you described;
  • when switching to per-wallet in 2025, RP2 applies the user-selected Global Allocation Method (one of those described by Justin) to reset the cost bases;
  • then per-wallet application starts being used normally (using the transfer analysis algorithm described in the design document).

So the TLDR is: the existing design is fine as is, but we'll need to add the Global Allocation Methods described by Justin, which will be used once before switching from Universal Application to Per-Wallet Application.

I will need some time to add this new feature to the design and implementation, but this is a great step in the right direction.

Again, thank you so much for engaging in conversation and asking lots of good questions. This is helping both my understanding of the new rules and the robustness of the design.

@eprbell
Copy link
Owner Author

eprbell commented Dec 29, 2024

The example presented by @gbtorrance is not handled by the design. Specifically the Universal Application section is not actually an example of universal application. It is actually an example of per-wallet application.

In the example of using universal application, FIFO transfer method, and FIFO accounting method the balances of each wallet will not match the cost basis units of the lots as they are transferred. This means that if you are switching from universal application to per-wallet application from one year to the next year there must be a step in between to assign cost basis to fill in the spent cost basis that will be missing from each wallet.

Looks like @orientalperil and I came to a similar conclusion around the same time: the design needs an extra step to run the Global Allocation Method when switching from Universal Application to Per-wallet Application.

@gbtorrance
Copy link

gbtorrance commented Dec 29, 2024

Sounds great! Thanks for the feedback!

For reference, here are the Global Allocation Methods Justin referenced in his post:

Justin provides examples such as:

  • "Lowest cost basis to highest balance"
  • "Oldest tax lots to highest balance"
  • "Oldest tax lots to least active wallet"
  • "Highest cost basis to lowest balance"

(Now that I think about it, "Lowest cost basis to highest balance" and "Highest cost basis to lowest balance" seem the same to me. For the second one (final bullet) maybe he meant, "Lowest cost basis to lowest balance" or "Highest cost basis to highest balance"?)

I went back to see what Crypto Tax Girl said about this. She is recommending to her clients, "Highest Cost Allocated First" as the Global Allocation Method. On the surface this seems insufficient, as it says nothing about the wallet ordering. But in this section below she does mention allocating first to the most active wallets. But she also makes a distinction between "hosted" (CEXs) and "unhosted" (HW or SW wallets). It's a little unclear. I think the section is worth watching, though. Just a few minutes.

https://youtu.be/oaJh6k1QoPk?t=1377

Just to throw something out there, do you see any feasibility in RP2 supporting specifying a custom wallet ordering for Global Allocation? For example, the ordering specified for exchanges the .ini. file? Maybe risky, since a user may not realize that ordering there matters, and could change the ordering unwittingly. Maybe a special global_allocation_exchanges_order would be better? Just spit-balling.

Whether a custom wallet order can be specified in RP2 or it only supports completely deterministic methods like those suggested by Justin, it would be nice to have a pretty solid list of the methods you feel comfortable RP2 supporting. That way we can choose one to document for Safe Harbor before EOY.

Thanks!

@gbtorrance
Copy link

Just wanted to share this discussion I had with Justin today. I think it's relevant to this overall effort. (I think we kind of touched on it earlier in this thread, also, but it keeps coming up in my mind as a point of confusion.) IMO, basis should remain associated with the date the coin was originally purchased. (But there are others that seemingly disagree.)

@eprbell
Copy link
Owner Author

eprbell commented Dec 29, 2024

Just to throw something out there, do you see any feasibility in RP2 supporting specifying a custom wallet ordering for Global Allocation? For example, the ordering specified for exchanges the .ini. file? Maybe risky, since a user may not realize that ordering there matters, and could change the ordering unwittingly. Maybe a special global_allocation_exchanges_order would be better? Just spit-balling.

Whether a custom wallet order can be specified in RP2 or it only supports completely deterministic methods like those suggested by Justin, it would be nice to have a pretty solid list of the methods you feel comfortable RP2 supporting. That way we can choose one to document for Safe Harbor before EOY.

Thanks!

I started thinking about this today, so this is not even part of the design: consider it not set in stone. My initial thoughts are that we can probably let the user decide the Global Allocation Method in a special section of the .ini file as follows:

  • pick an accounting method (FIFO, LIFO, HIFO, etc.);
  • pick a wallet order.

Global Allocation would work more or less this way:

  • The accounting method is used to sort all the acquired lots in the universal queue;
  • The sorted universal queue is divided in as many segments as there are wallets (up to the balance of each wallet). So we end up with n subqueues of acquired lots (where n is the number of wallets), already sorted by the accounting method;
  • Transfer analysis is performed but instead of passing the per-wallet InTransactions that have been discovered so far by the transfer analysis algorithm, we pass the per-wallet subqueue from the previous bullet (and using FIFO because the subqueue is already sorted).

This should cover all the methods described by Justin (and more). Let me know if you find any holes in this logic.

@eprbell
Copy link
Owner Author

eprbell commented Dec 29, 2024

Just wanted to share this discussion I had with Justin today. I think it's relevant to this overall effort. (I think we kind of touched on it earlier in this thread, also, but it keeps coming up in my mind as a point of confusion.) IMO, basis should remain associated with the date the coin was originally purchased. (But there are others that seemingly disagree.)

I would say: let's assume the date doesn't change unless we have overwhelming evidence to the contrary.

@gbtorrance
Copy link

gbtorrance commented Dec 30, 2024

I would say: let's assume the date doesn't change unless we have overwhelming evidence to the contrary.

I think that is the safest approach. Almost certainly it will be accepted by the IRS. (The other approach, I'm not so sure.)

@gbtorrance
Copy link

This should cover all the methods described by Justin (and more). Let me know if you find any holes in this logic.

I think this makes sense.

I do have a bit of concern that the IRS may not like the custom wallet ordering. I posted a question to Justin about this, and will let you know what he says. Hopefully it's allowed. But if it is used, it does put a bit more burden on the user to make sure they clearly and correctly document their chosen Global Allocation Method, and that it is aligned with the configured wallet ordering in RP2.

Transfer analysis is performed but instead of passing the per-wallet InTransactions that have been discovered so far by the transfer analysis algorithm, we pass the per-wallet subqueue from the previous bullet (and using FIFO because the subqueue is already sorted).

Can you elaborate a bit on how wallet queues are used in RP2? It seems to me you should be able to use any Accounting Method for the Global Allocation (to determine what lots go into what wallets). But then you should also be able to choose any Accounting Method for subsequent transfer processing (which presumably includes processing of sales). Important note: For the Accounting Method for transfer processing, it is understood that U.S. taxpayers would likely have to use FIFO going forward. The flexibility to choose another method would be for non-U.S. taxpayers.

To restate the question: Can you clarify how sorted ordering of queues plays into the ability to select any Accounting Method for any tax year (for transfer processing)?


On a different subject, I was thinking that some of the Global Allocation Methods suggested by Justin can be open to some confusion and, if they are going to be handled directly by RP2 (rather than by a custom wallet ordering), it's worth making sure everyone is one the same page.

What does wallet "highest balance" mean? Highest balance of the particular coin or highest balance of all coins in the wallet? I would assume of the particular coin being processed. (It doesn't really make sense otherwise.)

What does "most active wallet" mean? Is it based on number of transactions or recency of transactions? Is it number of transactions for the particular coin or for all coins? Is it number of transactions since inception of the wallet or number of transactions in a more recent time period? (If I had to guess I'd say number of transactions since inception for the particular coin being processed.)

Just throwing that out for consideration. RP2 implementing the wallet selection deterministically does require being clear on what the Global Allocation Methods actually mean. But if RP2 just supports custom wallet selection order, then it's not really an issue. It would be up to the user.

@gbtorrance
Copy link

This should cover all the methods described by Justin (and more).

Now that I think about it, there is a potential issue. It depends on one's interpretation of the Global Allocation methods.

If, for example, one interprets wallet "highest balance" as meaning "highest balance of the particular coin", then having a single custom ordering of wallets in RP2 would not allow this method to be properly replicated. Wallet A may have the highest balance of Token X, but Wallet B may have the highest balance of Token Y. It's possible to manually order the wallets to guarantee "Lowest cost basis to highest balance" for Token X or for Token Y, but not for both. Make sense?

Sorry to always be the bearer of bad news :-(

Maybe having a deterministic wallet allocation is going to be required?

@gbtorrance
Copy link

Sorry to always be the bearer of bad news :-(

On the assumption that custom wallet ordering will not be suitable due to "highest balance of the particular coin" issue referenced above... do you think it's comfortably doable to implement deterministic ordering of wallets by per-coin balance for the Global Allocation? If so, I'd like to go ahead and create a Safe Harbor declaration as follows: "Highest Cost Basis to Highest Balance" (or maybe highest to lowest or lowest to highest; not 100% sure yet). Just want to make sure you foresee no issues with implementation.

Thoughts? Thanks!

@gbtorrance
Copy link

gbtorrance commented Dec 30, 2024

FWIW, Justin confirmed that he believes custom wallet ordering would be acceptable. It's unfortunate about the per-coin issue.

https://www.reddit.com/r/CryptoTax/comments/1hk31yd/comment/m4i60ew/
image

@gbtorrance
Copy link

Sorry for all the messages. I'm just thinking outloud/online here.

I suppose custom wallet ordering would be an acceptable approach for RP2, on the understanding that it wouldn't be able to replicated the more generic Global Allocation Methods Justin mentioned.

Whatever you think is best. Please just let me know when you can so I can make an appropriate declaration before EOY. Thanks.

@eprbell
Copy link
Owner Author

eprbell commented Dec 30, 2024

Sorry to always be the bearer of bad news :-(

On the assumption that custom wallet ordering will not be suitable due to "highest balance of the particular coin" issue referenced above... do you think it's comfortably doable to implement deterministic ordering of wallets by per-coin balance for the Global Allocation? If so, I'd like to go ahead and create a Safe Harbor declaration as follows: "Highest Cost Basis to Highest Balance" (or maybe highest to lowest or lowest to highest; not 100% sure yet). Just want to make sure you foresee no issues with implementation.

Thoughts? Thanks!

Yes, per-coin ordering of wallets should be doable. Perhaps we can have a default order that can be optionally overridden on a per per-coin basis.

@gbtorrance
Copy link

Yes, per-coin ordering of wallets should be doable.

Great! Thanks for confirming. I'll assume the ability to order wallets by holding of each coin for Global Allocation when filling out my Safe Harbor declaration.

Perhaps we can have a default order that can be optionally overridden on a per per-coin basis.

Sounds good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants
@orientalperil @jayr0d @gbtorrance @macanudo527 @eprbell and others