eprbell / rp2

Privacy-focused, free, open-source cryptocurrency tax calculator for multiple countries: it handles multiple coins/exchanges and computes long/short-term capital gains, cost bases, in/out lot relationships/fractioning, and account balances. It supports FIFO, LIFO, HIFO and it outputs in form 8949 format. It has a programmable plugin architecture
https://pypi.org/project/rp2/
Apache License 2.0
286 stars 45 forks source link

DeFi Brainstorming #4

Open westofpluto opened 2 years ago

westofpluto commented 2 years ago

This is a really interesting and useful project, nice work! But unless I am not understanding something correctly, there is a problem here: this code must be used from the very first transaction across all years of trading crypto. What if I already have a detailed snapshot of my crypto portfolio at the start of 2021, including coins/tokens, dates of purchase and cost basis for coins/tokens purchased on that date. I would like to use just my transactions from 2021 and that portfolio snapshot to compute my taxes for 2021. Is there any way to do this? If not, where would I start in the code to update the code to handle this?

eprbell commented 2 years ago

Glad you find it useful, thanks! In the case you're describing you can still use RP2: just leave out the transactions/lots you have already sold in previous years.

E.g. suppose your first year of trading BTC was 2020 and you have a situation like this: a) 2020-2-5: buy 1 BTC b) 2020-5-5: buy 2 BTC c) 2020: 8-1: sell 1.5 BTC d) 2021: more transactions... Let's also assume you were using FIFO and in 2021 you didn't use RP2. This means you sold all of lot a) and 0.5 BTC from lot b).

So if you want to start using RP2 this year for your 2021 taxes, you would just leave out what you already sold and enter the following in the input spreadsheet: b) 2020-5-5: buy 1.5 BTC d) 2021: more transactions...

This is because lot a), part of lot b) and lot c) are already accounted for in the pre-RP2 system you were using.

Of course you still need to keep all the documentation for previous years as well as for the current year. Also keep in mind that you will need to keep the same accounting method you were using previously: if you want to switch accounting method (e.g. from FIFO to LIFO) you will need to speak to a tax professional. If RP2 is helpful to you, please consider giving the project a star.

eprbell commented 2 years ago

BTW in the example above you can use the Notes column to remind yourself why lot b) only has 1.5 BTC instead of 2, in case you look at the data years in the future.

westofpluto commented 2 years ago

Aha! Yes this makes sense. If I have an Excel spreadsheet of my portfolio at start of 2021, with current lots of each coin/token and dates and cost basis when they were bought, you are completely right - I can just use these as prior year "transactions" and all will be well.

I have another couple of questions:

Finally a suggestion: if it isn't in there already, it would be really cool to let the user select a date/tim/timestamp and have the code show profit/loss per coin (a portfolio and PNL snapshot) up to that point of time.

Thanks again!

westofpluto commented 2 years ago

It also looks like a SWAP between coins has to be specified as two transaction. For example, if I am on Pancakeswap and swap BNB for CAKE, then I have to specify two transaction: one for selling BNB to USD and a second for buying CAKE with the USD. However, the transaction records I get regarding this transaction (have to download using my wallet address on bscscan.com) only show a single transaction: swapping BNB for CAKE. You might want to add an additional category of transactions (SWAP) to account for this.

eprbell commented 2 years ago

Good questions. What I tried to do in this first version of RP2 is to identify the basic building blocks that can capture the meaning of crypto transactions types from a tax perspective. This means that there may be higher-level transaction types that can be expressed as a combination of primitive transaction types: an example is swapping coins (as you say, it's expressible with a buy + a sell transaction). The design principle has been one of minimalism: I tried to resist the temptation of adding types that were not absolutely necessary, at least in this initial pre-1.0 phase.

With that said, there are two things to consider when refining the design: 1) is the current list of primitive transaction types incomplete? I.e. are real-world crypto transactions that are not expressible with the existing list? 2) are some high level transactions are frequent/useful enough to deserve their own transaction type, even though it's not primitive?

Point number 1) is high-priority: if we're missing a primitive type, then it means that certain crypto transaction cannot be captured in RP2 and this needs to be addressed ASAP.

Point number 2) is nice to have but lower priority: before committing to higher-level types I would like for RP2 to mature a bit more and gain more users.

With this introduction out of the way, let's get to your questions/comments.

Anyway, hope this helps, thanks for the great feedback and questions.

westofpluto commented 2 years ago

Hi again, and thanks for the thoughtful comments!

COST transaction: I think a COST is a special type of SELL. You would have to set the "spot price" for the SELL to zero, which looks a little strange. Or you would have to set the crypto fee and fiat fee to be the total cost. It looks a little clunky. It would be great to be more clear and have a COST label so it would be obvious. And perhaps the code could be modified so that if it is a COST type, then it gets handled as a cost without requiring any funky looking input data.

DEFI transactions: One thing that is problematic in your code is that it appears to assume that any buy or sell transaction will be paid either in USD or in whatever currency is bought or sold. In DEFI this is not the case. If I am trading in my Metamask wallet on the Binance Smart Chain, all fees are paid in BNB. So if I want to swap one Binance Smart Chain token for another (say BUSD for CAKE), my fees will still be paid in BNB, not BUSD and not CAKE. It would be best to have columns that specify "fee currency" and "fee currency spot price" so that the fees in USD can be more easily calculated.

BRIDGING: Yes this gets a little confusing. As you know, there are now lots of blockchains that run Ethereum Virtual Machines (EVMs) which allows these blockchains to have their own tokens. Ethereum has its vast set of tokens and Polygon (MATIC) has its own set of tokens. However it gets more complicated than that: it turns out that not only is MATIC the native coin on the Polygon chain - there is also a version of MATIC that exists as an Ethereum token on the Ethereum chain. They always have the exact same price and name, they just exist on different chains. So if I buy MATIC at Coinbase, what I am really buying is the MATIC token that exists on the Ethereum chain. If I send that to my Metamask wallet, it will appear as an ETH token in the ETH network on my Metamask. If I want to use it in Polyon projects, I have to use something called the polygon bridge (at wallet.polygon.technology). I hook up my wallet and tell this site to bridge my MATIC (ETH token) over to the Polygon network. When it is finished, I now have the same amount of MATIC on the Polygon network and all my MATIC on the ETH network is consumed. To do all this I have to pay gas fees in ETH. When I want to send the MATIC back to Coinbase, I have to do the process in reverse: bridge it back to ETH. (You'd be amazed how many people forget this step and lose all their MATIC sending Polygon MATIC back to Coinbase).

In any case, I would guess that you could treat the bridge operation as an INTRA: you are basically transferring MATIC to yourself, just from one network to another. But again, you pay fees in ETH, not MATIC. Not sure how to account for this in the existing code.

PNL at datetime: One use case for this is for example what happened to me this past year. In 2021 I was doing the normal buy and hold for various cryptos until maybe August. I had only say 150 transactions total. Then in mid August I got into DEFI with all kinds of trading and bridging and staking, rebase DAOs, etc. I suddenly had about 2000 more transactions. It would be nice to see what my PNL was up through the time I switched from basic investing to DEFI.

A second use case -probably even more important - is this: The code and calculations are only as good as your transaction data. Garbage in, garbage out. And with 2000 transactions, it is easy to miss transactions or duplicate them or generally get them wrong. If we have to go through an iterative process to enter data, run calcs and fix the data wherever necessary, it would be really useful to compute PNL up to a given date. This makes it much easier to pinpoint where our input data likely went wrong.

Finally, there is a particular type of project that currently no software seems to handle properly. This is a type of investment where you invest some number of XYZ coins (whatever) and those coins are permalocked in the project. You can never withdraw them. Instead, the project gives you some percent daily return of these coins. In some cases this return can be maxed out by some set of project rules while in other cases it goes on forever. An example of the former is DRIP (drip.community) where you put in say 100 DRIP and then you get 1 DRIP per day back for a max of 365 days. An example of the latter is STRONG (strongblock.io) where you buy a "node" that consumes 10 STRONG from your wallet. But after the, the node produces 0.1 STRONG per day, forever.

The IRS is exceedingly unclear how to tax this and if you ask 10 CPAs you might get 10 different answers. A "reasonable" assumption is that if you invest 100 XYZ coins and get 1 back per day, the project is returning your 100 coins back to you every day, so your taxable income on the first 100 is just the price on the day it is returned minus the price you paid for it on the day you invested. After that, all coins are like a STAKING reward and are fully taxable. The only way I can figure out how to replicate this in your software is to use my own python code when creating these transactions and keep track of how many coins I have invested in a given project versus how many I have claimed back. The initial investment is just an INTRA transaction. For coins that are returned to me from my initial investment, I have to have two transactions: one that represents the staking reward at the current price, and an OUT transaction that represents the burning of the coins at the price I paid for them when I initially invested. In other words, this second transaction would be a pure COST transaction and it would happen only when we try to take out our tokens. We can't deduct the full investment amount at the time we invest, we can only deduct the cost when we take our daily reward back from this locked pool. I'm not sure how your code could be adjusted to handle this, or perhaps it is just up to us to keep track of which coins are returned from the pool and which are pure profit staking rewards.

I'm open to any suggestions on this.

westofpluto commented 2 years ago

I just looked at the INTRA table more closely and it seems to assume that INTRA transactions have no fees or are paid in fees in the coin being sent. This is an invalid assumption in DEFI. If I send 100 CAKE from one BSC wallet to another then the fees are paid in BNB not CAKE. Also, even when sending BNB, the fees are not deducted from the coins I send, they are taken out as gas fees from any BNB I have in my sending wallet. For example, If I have 102 BNB in one wallet and I send 100 BNB to a second wallet, the TRX fee may be 0.005 BNB, so I will still get 100 BNB in my second wallet but in my sending wallet I will have 1.995 BNB remaining instead of 2 BNB.

eprbell commented 2 years ago

This is turning into a really cool discussion! I haven't played with DEFI much, so your detailed messages are quite educational for me. I think our goal should be to produce the following for each of the use cases you gave:

To that end, I created a Wiki document to capture the outcome of this brainstorming. Feel free to edit, fix or comment on it as needed.

Note that the workarounds may be a little clunky, but they are useful because they allow us to define precisely the operational semantics of a given high-level operation. If a workaround is not possible, then we have a missing-primitive problem.

As mentioned previously, my main focus right now is on foundation primitives rather than higher level ones. I'm thinking about this like a compiler, in which a higher-level language gets translated to a lower-level one. Perhaps there could be a higher level of abstraction (possibly even a separate project), that will allow users to define high-level transaction types, which will get translated to lower-level RP2 (somewhat similar to C and ASM): e.g. SWAP -> BUY + SELL. All this is not set in stone and I'm open to feedback: it's just my current train of thought.

Ok, let's get to the issues:

Feel free to correct me if I didn't get the operational semantics (or anything else) right. Once we have the operational semantics in place we can decide what the next step should be.

Thanks a ton for all the feedback, context and ideas!

eprbell commented 2 years ago

BTW, I implemented the PNL at datetime you requested: check the from/to time filters with arbitrary dates (-f and -t command line options) in the latest version.

eprbell commented 2 years ago

Quick update on DeFi in RP2. I ended up implementing FEE-only transactions as a primitive (see issue #16): I have received several requests for them and I realized that this concept is important enough to deserve its own transaction type. So with the latest version of RP2 you can just set the transaction type of an out-transaction to "Fee" and it will work as a fee-only transaction: there is no need to use the sell transaction workaround anymore. I also updated the DeFi Brainstorming Wiki. See also relevant FAQ.

eprbell commented 2 years ago

This is a useful article on the topic of DeFi and taxes: https://cryptotrader.tax/blog/defi-crypto-tax-guide. It lists several real-world DeFi scenarios with ideas on how to handle them (all of these scenarios can be modeled with RP2).

eprbell commented 2 years ago

I just added the ability to pass crypto_fee to in-transactions (I received numerous requests for this). With the latest RP2 you can pass either crypto_fee or fiat_fee (or neither) but not both.

Note that passing crypto_fee causes RP2 to generate one more artificial, fee-only out-transaction to model the crypto_fee (in addition to the normal in-transaction). These two transactions are generated with notes that explain the situation. If the user passed in unique_id to the original transaction, the two generated transactions share the same unique_id so they are easy to correlate in the output file.