├── tests ├── __init__.py ├── test_bags.py └── test_relations.py ├── MANIFEST.in ├── contributors.txt ├── examples ├── example_output │ ├── Report2017.pdf │ ├── Details_2017.pdf │ ├── Report2017_de.pdf │ ├── Details_2017_de.pdf │ ├── Transactions2017.pdf │ ├── report_2017.csv │ ├── transactions2017.csv │ ├── status2017.json │ └── ccgains_20180509-114033.log ├── example_csv │ ├── bisq_trades_2017_fabricated.csv │ ├── poloniex_withdrawalHistory_2017_fabricated.csv │ ├── poloniex_depositHistory_2017_fabricated.csv │ ├── bitcoin.de_account_statement_2017_fabricated.csv │ ├── bisq_transactions_2017_fabricated.csv │ └── poloniex_tradeHistory_2017_fabricated.csv ├── example_windows.py └── example.py ├── data └── bitcoin_de_EUR_abridged_as_example.csv.gz ├── run_tests.sh ├── docs ├── Makefile ├── source │ ├── ccgains.rst │ ├── index.rst │ └── conf.py └── make.bat ├── ccgains ├── __init__.py ├── templates │ ├── generic_landscape_table.html │ ├── shortreport_de.html │ ├── shortreport_en.html │ ├── fullreport_de.html │ └── fullreport_en.html ├── binance_util.py └── relations.py ├── setup.py ├── .gitignore ├── COPYING.LESSER └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include ccgains/templates/*.html 2 | -------------------------------------------------------------------------------- /contributors.txt: -------------------------------------------------------------------------------- 1 | Jürgen Probst 2 | Cristóbal Tapia 3 | Anson VanDoren 4 | Vladimir Kamarzin 5 | -------------------------------------------------------------------------------- /examples/example_output/Report2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probstj/ccGains/HEAD/examples/example_output/Report2017.pdf -------------------------------------------------------------------------------- /examples/example_output/Details_2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probstj/ccGains/HEAD/examples/example_output/Details_2017.pdf -------------------------------------------------------------------------------- /examples/example_output/Report2017_de.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probstj/ccGains/HEAD/examples/example_output/Report2017_de.pdf -------------------------------------------------------------------------------- /examples/example_output/Details_2017_de.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probstj/ccGains/HEAD/examples/example_output/Details_2017_de.pdf -------------------------------------------------------------------------------- /data/bitcoin_de_EUR_abridged_as_example.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probstj/ccGains/HEAD/data/bitcoin_de_EUR_abridged_as_example.csv.gz -------------------------------------------------------------------------------- /examples/example_output/Transactions2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probstj/ccGains/HEAD/examples/example_output/Transactions2017.pdf -------------------------------------------------------------------------------- /examples/example_csv/bisq_trades_2017_fabricated.csv: -------------------------------------------------------------------------------- 1 | Trade ID,Date/Time,Market,Price,Amount in BTC,Amount,Trade type 2 | a5ed7482,"2017-02-14 4:10:17 PM",0.2 BTC,962.00,192.40 EUR,Buy BTC,Completed 3 | -------------------------------------------------------------------------------- /examples/example_csv/poloniex_withdrawalHistory_2017_fabricated.csv: -------------------------------------------------------------------------------- 1 | Date,Currency,Amount,Address,Status 2 | 2017-02-12 16:23:05,BTC,0.02010000,1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM,COMPLETE: df9266cfa9304559a4d31c6e28bc93d8f03d2fe354885420a86564b5e826907a 3 | -------------------------------------------------------------------------------- /examples/example_csv/poloniex_depositHistory_2017_fabricated.csv: -------------------------------------------------------------------------------- 1 | Date,Currency,Amount,Address,Status 2 | 2017-02-16 16:55:19,BTC,0.21850000,14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW,COMPLETE 3 | 2017-02-10 23:47:28,BTC,0.19790000,14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW,COMPLETE 4 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # discover and run all tests: 4 | python -m unittest discover 5 | 6 | # for help, see: 7 | #python -m unittest -h 8 | 9 | 10 | # only a single TestCase, e.g.: 11 | #python -m unittest tests.test_relations 12 | # or 13 | #python tests/test_relations.py 14 | 15 | # only a single test from a TestCase, e.g.: 16 | #python -m unittest tests.test_bags.TestBagQueue.test_trading_profits_no_fees 17 | # or 18 | #python tests/test_bags.py TestBagQueue.test_trading_profits_no_fees 19 | 20 | -------------------------------------------------------------------------------- /examples/example_csv/bitcoin.de_account_statement_2017_fabricated.csv: -------------------------------------------------------------------------------- 1 | Date;Type;Währungen;Reference;Kurs;"BTC incl. fee";"EUR incl. fee";"BTC excl. fee";"EUR excl. fee";"Incoming / Outgoing";"Account balance" 2 | "2017-02-06 17:04:03";Purchase;"BTC / EUR";AABBCC;978.90;0.20000000;195.78;0.19800000;194.80;0.19800000;0.19800000 3 | "2017-02-10 00:37:45";Disbursement;;7bf9065a97b8d0cac275474297bf0e7463d8ba82edc267293ed04f9100851a01;;;;;;-0.19790000;0.00010000 4 | "2017-02-10 00:37:45";"Network fee";;7bf9065a97b8d0cac275474297bf0e7463d8ba82edc267293ed04f9100851a01;;;;;;-0.00010000;0.00000000 5 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = ccGains 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/source/ccgains.rst: -------------------------------------------------------------------------------- 1 | ccgains package 2 | =============== 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: ccgains 8 | :members: 9 | :undoc-members: 10 | 11 | ccgains.bags module 12 | ------------------- 13 | 14 | .. automodule:: ccgains.bags 15 | :members: 16 | :undoc-members: 17 | 18 | ccgains.historic_data module 19 | ---------------------------- 20 | 21 | .. automodule:: ccgains.historic_data 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | 26 | ccgains.relations module 27 | ------------------------ 28 | 29 | .. automodule:: ccgains.relations 30 | :members: 31 | :undoc-members: 32 | 33 | ccgains.trades module 34 | --------------------- 35 | 36 | .. automodule:: ccgains.trades 37 | :members: 38 | :undoc-members: 39 | 40 | ccgains.reports module 41 | ---------------------- 42 | 43 | .. automodule:: ccgains.reports 44 | :members: 45 | :undoc-members: 46 | -------------------------------------------------------------------------------- /examples/example_csv/bisq_transactions_2017_fabricated.csv: -------------------------------------------------------------------------------- 1 | Date/Time,Details,Address,Transaction ID,Amount in BTC,Confirmations 2 | "2017-02-16 5:38:03 PM",Withdrawn from wallet,Sent to: 14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW,7ff38af2adcb85037e058f996e81a377f59213fa1157cff287a74d9509dc838e,-0.219,35048 3 | "2017-02-16 5:16:29 PM",MultiSig payout: a5ed7482,Received with: 1BVGiDELH2SWpuwb64rqdZx8MymcanJ7oY,8f58ee6d6b413c9cbe55f95eb4861cee9dbf46017a5e08b15b1232ca900ce161,0.21,35051 4 | "2017-02-14 4:10:19 PM",MultiSig deposit: a5ed7482,Sent to: 3D6Bk0MWLwTu5qHC8mXO7FNsbrfPZhAgox,c0b7b9248857ea8500eb015ae125b1e0b11ed5bb4a7c84c229cdbd3eeb958917,-0.0105,35269 5 | "2017-02-12 5:48:21 PM",Create offer fee: a5ed7482,Sent to: 1Fgs78zXQOohFLd1PAZSBaG40nl2wTejIE,95be615cce44ce5358c744e8d1637e09e177c5853dee63476cda2cce6ac8684c,-0.0005,35624 6 | "2017-02-12 5:31:20 PM",Received funds,Received with: 1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM,df9266cfa9304559a4d31c6e28bc93d8f03d2fe354885420a86564b5e826907a,0.02,35625 7 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=ccGains 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /examples/example_csv/poloniex_tradeHistory_2017_fabricated.csv: -------------------------------------------------------------------------------- 1 | Date,Market,Category,Type,Price,Amount,Total,Fee,Order Number,Base Total Less Fee,Quote Total Less Fee 2 | 2017-05-16 22:25:40,XMR/BTC,Exchange,Buy,0.01497120,92.71284466,1.38802254,0.15%,174231700001,-1.38802254,92.57377540 3 | 2017-04-30 17:18:31,GNT/BTC,Exchange,Sell,0.00017770,7259.18667370,1.28995747,0.15%,2776700001,1.28802254,-7259.18667370 4 | 2017-02-18 20:31:28,GNT/BTC,Exchange,Buy,0.00003627,7270.09181141,0.26368623,0.15%,151600001,-0.26368623,7259.18667370 5 | 2017-02-17 06:24:48,XMR/BTC,Exchange,Sell,0.01274401,3.26497244,0.04160884,0.15%,122421900001,0.04154643,-3.26497244 6 | 2017-02-12 17:35:55,PASC/BTC,Exchange,Sell,0.00037000,280.80959359,0.10389954,0.25%,3116800001,0.10363980,-280.80959359 7 | 2017-02-12 16:41:27,PASC/BTC,Exchange,Buy,0.00036701,281.23144075,0.10321475,0.15%,3112100001,-0.10321475,280.80959359 8 | 2017-02-12 16:08:11,XMR/BTC,Exchange,Sell,0.01235000,10.00000000,0.12350000,0.15%,119488400001,0.12331475,-10.00000000 9 | 2017-02-11 20:21:21,XMR/BTC,Exchange,Buy,0.01214900,16.28940654,0.19790000,0.15%,119073300001,-0.19790000,16.26497244 10 | -------------------------------------------------------------------------------- /ccgains/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # 4 | # ---------------------------------------------------------------------- 5 | # ccGains - Create capital gains reports for cryptocurrency trading. 6 | # Copyright (C) 2017 Jürgen Probst 7 | # 8 | # This file is part of ccGains. 9 | # 10 | # ccGains is free software: you can redistribute it and/or modify it 11 | # under the terms of the GNU Lesser General Public License as published 12 | # by the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # ccGains is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public 21 | # License along with ccGains. If not, see . 22 | # ---------------------------------------------------------------------- 23 | # 24 | # Get the latest version at: https://github.com/probstj/ccGains 25 | # 26 | 27 | import logging 28 | 29 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 30 | 31 | from .historic_data import ( 32 | HistoricDataAPI, 33 | HistoricDataCSV, 34 | HistoricDataAPICoinbase, 35 | HistoricDataAPIBinance, 36 | ) 37 | 38 | from .relations import CurrencyRelation 39 | from .trades import Trade, TradeHistory 40 | from .bags import Bag, BagQueue 41 | from .reports import PaymentReport, CapitalGainsReport 42 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. ccGains documentation master file, created by 2 | sphinx-quickstart on Mon Apr 30 19:15:34 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to the ccGains documentation! 7 | ===================================== 8 | 9 | The ccGains (cryptocurrency gains) package provides a python library for calculating capital gains made by trading cryptocurrencies or foreign currencies. 10 | 11 | Some of its features are: 12 | 13 | - calculates the capital gains using the first-in/first out (FIFO) principle, 14 | - creates capital gains reports as CSV, HTML or PDF (instantly ready to print out for the tax office), 15 | - can create a more detailed capital gains report outlining the calculation and used bags, 16 | - differs between short and long term gains (amounts held for less or more than a year), 17 | - treats amounts held and traded on different exchanges separately, 18 | - treats exchange fees and transaction fees directly resulting from trading properly as losses, 19 | - provides methods to import your trading history from various exchanges, 20 | - loads historic cryptocurrency prices from CSV files and/or 21 | - loads historic prices from APIs provided by exchanges, 22 | - caches historic price data on disk for quicker and offline retrieval and less traffic to exchanges, 23 | - for highest accuracy, uses the `decimal data type `_ for all amounts 24 | - supports saving and loading the state of your portfolio as JSON file for use in ccGains calculations in following years 25 | 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | :caption: Contents: 30 | 31 | ./ccgains 32 | 33 | 34 | Indices and tables 35 | ================== 36 | 37 | * :ref:`genindex` 38 | * :ref:`modindex` 39 | * :ref:`search` 40 | -------------------------------------------------------------------------------- /examples/example_output/report_2017.csv: -------------------------------------------------------------------------------- 1 | kind,amount,currency,purchase_date,sell_date,exchange,short_term,cost,proceeds,profit 2 | withdrawal fee,0.00010000,BTC,2017-02-06,2017-02-10,Bitcoin.de,True,0.09838384,0.00000000,-0.09838384 3 | sale,0.19790000,BTC,2017-02-06,2017-02-11,Poloniex,True,194.70161616,190.96298874,-3.73862743 4 | sale,10.00000000,XMR,2017-02-11,2017-02-12,Poloniex,True,117.40750834,117.15179226,-0.25571607 5 | withdrawal fee,0.00010000,BTC,2017-02-12,2017-02-12,Poloniex,True,0.09500225,0.00000000,-0.09500225 6 | sale,0.10321475,BTC,2017-02-12,2017-02-12,Poloniex,True,98.05633917,97.82677735,-0.22956182 7 | exchange fee,0.00050000,BTC,2017-02-12,2017-02-12,Bitsquare/bisq,True,0.47501127,0.00000000,-0.47501127 8 | sale,280.80959359,PASC,2017-02-12,2017-02-12,Poloniex,True,97.82677735,98.10621295,0.27943560 9 | withdrawal fee,0.00050000,BTC,2017-02-12,2017-02-14,Bitsquare/bisq,True,0.47501127,0.00000000,-0.47501127 10 | withdrawal fee,0.00050000,BTC,2017-02-12,2017-02-16,Bitsquare/bisq,True,0.47501127,0.00000000,-0.47501127 11 | sale,3.26497244,XMR,2017-02-11,2017-02-17,Poloniex,True,38.33322790,40.91307679,2.57984890 12 | sale,0.00850000,BTC,2017-02-12,2017-02-18,Poloniex,True,8.07519161,8.44772610,0.37253450 13 | sale,0.01000000,BTC,2017-02-12,2017-02-18,Poloniex,True,9.50022542,9.93850130,0.43827588 14 | sale,0.10363980,BTC,2017-02-12,2017-02-18,Poloniex,True,98.10621295,103.00242869,4.89621574 15 | sale,0.14154643,BTC,2017-02-14,2017-02-18,Poloniex,True,136.16766566,140.67593785,4.50827219 16 | sale,7259.18667370,GNT,2017-02-18,2017-04-30,Poloniex,True,262.06459394,1506.24816753,1244.18357358 17 | sale,0.05845357,BTC,2017-02-14,2017-05-17,Poloniex,True,56.23233434,93.27066727,37.03833293 18 | sale,0.04154643,BTC,2017-02-17,2017-05-17,Poloniex,True,40.91307679,66.29301254,25.37993574 19 | sale,1.28802254,BTC,2017-04-30,2017-05-17,Poloniex,True,1506.24816753,2055.21616164,548.96799411 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # 4 | # ---------------------------------------------------------------------- 5 | # ccGains - Create capital gains reports for cryptocurrency trading. 6 | # Copyright (C) 2017 Jürgen Probst 7 | # 8 | # This file is part of ccGains. 9 | # 10 | # ccGains is free software: you can redistribute it and/or modify it 11 | # under the terms of the GNU Lesser General Public License as published 12 | # by the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # ccGains is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public 21 | # License along with ccGains. If not, see . 22 | # ---------------------------------------------------------------------- 23 | # 24 | # Get the latest version at: https://github.com/probstj/ccGains 25 | # 26 | 27 | from setuptools import setup 28 | 29 | setup(name='ccGains', 30 | version='1.0', 31 | description='Python package for calculating cryptocurrency trading ' 32 | 'profits and creating capital gains reports', 33 | url='https://github.com/probstj/ccgains', 34 | author='Jürgen Probst', 35 | author_email='juergen.probst@gmail.com', 36 | license='LGPL-3.0-or-later', 37 | packages=['ccgains'], 38 | install_requires=[ 39 | 'tables', 40 | 'numpy', 41 | 'pandas', 42 | 'requests', 43 | 'jinja2', # I should make this and the next optional some day. 44 | 'babel', 45 | 'weasyprint', 46 | 'python-dateutil', 47 | ], 48 | include_package_data=True, 49 | zip_safe=False) 50 | 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # specific to ccGains project: 2 | # 3 | # nothing in examples folder: 4 | examples/* 5 | # except the actual examples: 6 | !examples/*.py 7 | # and except the example_csv folder: 8 | !examples/example_csv 9 | # and except the example_output folder: 10 | !examples/example_output 11 | 12 | # nothing in data folder 13 | data/* 14 | # except the abridged btc rates file used in the example: 15 | !data/bitcoin_de_EUR_abridged_for_example.csv.gz 16 | 17 | # vim temporary file 18 | *~ 19 | 20 | # PyCharm 21 | .idea/ 22 | 23 | # KDevelop 24 | .kdev4/ 25 | 26 | # Byte-compiled / optimized / DLL files 27 | __pycache__/ 28 | *.py[cod] 29 | *$py.class 30 | 31 | # C extensions 32 | *.so 33 | 34 | # Distribution / packaging 35 | .Python 36 | env/ 37 | build/ 38 | develop-eggs/ 39 | dist/ 40 | downloads/ 41 | eggs/ 42 | .eggs/ 43 | lib/ 44 | lib64/ 45 | parts/ 46 | sdist/ 47 | var/ 48 | *.egg-info/ 49 | .installed.cfg 50 | *.egg 51 | 52 | # PyInstaller 53 | # Usually these files are written by a python script from a template 54 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 55 | *.manifest 56 | *.spec 57 | 58 | # Installer logs 59 | pip-log.txt 60 | pip-delete-this-directory.txt 61 | 62 | # Unit test / coverage reports 63 | htmlcov/ 64 | .tox/ 65 | .coverage 66 | .coverage.* 67 | .cache 68 | nosetests.xml 69 | coverage.xml 70 | *,cover 71 | .hypothesis/ 72 | 73 | # Translations 74 | *.mo 75 | *.pot 76 | 77 | # Django stuff: 78 | *.log 79 | local_settings.py 80 | 81 | # Flask instance folder 82 | instance/ 83 | 84 | # Scrapy stuff: 85 | .scrapy 86 | 87 | # Sphinx documentation 88 | docs/_build/ 89 | 90 | # PyBuilder 91 | target/ 92 | 93 | # IPython Notebook 94 | .ipynb_checkpoints 95 | 96 | # pyenv 97 | .python-version 98 | 99 | # celery beat schedule file 100 | celerybeat-schedule 101 | 102 | # dotenv 103 | .env 104 | 105 | # virtualenv 106 | venv/ 107 | ENV/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | -------------------------------------------------------------------------------- /examples/example_output/transactions2017.csv: -------------------------------------------------------------------------------- 1 | kind,dtime,buy_currency,buy_amount,sell_currency,sell_amount,fee_currency,fee_amount,exchange,mark,comment 2 | Purchase,2017-02-06 17:04:03+01:00,BTC,0.19800000,EUR,194.80,BTC,0.00100000,Bitcoin.de,,AABBCC 3 | Disbursement,2017-02-10 00:37:45+01:00,EUR,0,BTC,0.19800000,BTC,0.00010000,Bitcoin.de,,7bf9065a97b8d0cac275474297bf0e7463d8ba82edc267293ed04f9100851a01 4 | Deposit,2017-02-11 00:47:28+01:00,BTC,0.19790000,,0,,0,Poloniex,,14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW 5 | Exchange,2017-02-11 21:21:21+01:00,XMR,16.26497244,BTC,0.19790000,XMR,0.02443410,Poloniex,Buy,119073300001 6 | Exchange,2017-02-12 17:08:11+01:00,BTC,0.12331475,XMR,10.00000000,BTC,0.00018525,Poloniex,Sell,119488400001 7 | Withdrawal,2017-02-12 17:23:05+01:00,,0,BTC,0.02010000,BTC,0.00010000,Poloniex,,1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM 8 | Received funds,2017-02-12 17:31:20+01:00,BTC,0.02,,0,,0,Bitsquare/Bisq,,Received with: 1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM 9 | Exchange,2017-02-12 17:41:27+01:00,PASC,280.80959359,BTC,0.10321475,PASC,0.42184716,Poloniex,Buy,3112100001 10 | Create offer fee: a5ed7482,2017-02-12 17:48:21+01:00,,0,BTC,0,BTC,0.0005,Bitsquare/Bisq,,Sent to: 1Fgs78zXQOohFLd1PAZSBaG40nl2wTejIE 11 | Exchange,2017-02-12 18:35:55+01:00,BTC,0.10363980,PASC,280.80959359,BTC,0.00025974,Poloniex,Sell,3116800001 12 | Buy BTC,2017-02-14 16:10:17+01:00,BTC,0.2,EUR,192.40,BTC,0,Bitsquare/Bisq,,a5ed7482 13 | MultiSig deposit: a5ed7482,2017-02-14 16:10:19+01:00,,0,BTC,0.0105,BTC,0.0005,Bitsquare/Bisq,,Sent to: 3D6Bk0MWLwTu5qHC8mXO7FNsbrfPZhAgox 14 | MultiSig payout: a5ed7482,2017-02-16 17:16:29+01:00,BTC,0.01,,0,,0,Bitsquare/Bisq,,Received with: 1BVGiDELH2SWpuwb64rqdZx8MymcanJ7oY 15 | Withdrawn from wallet,2017-02-16 17:38:03+01:00,,0,BTC,0.219,BTC,0.00050000,Bitsquare/Bisq,,Sent to: 14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW 16 | Deposit,2017-02-16 17:55:19+01:00,BTC,0.21850000,,0,,0,Poloniex,,14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW 17 | Exchange,2017-02-17 07:24:48+01:00,BTC,0.04154643,XMR,3.26497244,BTC,0.00006241,Poloniex,Sell,122421900001 18 | Exchange,2017-02-18 21:31:28+01:00,GNT,7259.18667370,BTC,0.26368623,GNT,10.90513771,Poloniex,Buy,151600001 19 | Exchange,2017-04-30 19:18:31+02:00,BTC,1.28802254,GNT,7259.18667370,BTC,0.00193493,Poloniex,Sell,2776700001 20 | Exchange,2017-05-17 00:25:40+02:00,XMR,92.57377540,BTC,1.38802254,XMR,0.13906926,Poloniex,Buy,174231700001 21 | -------------------------------------------------------------------------------- /ccgains/templates/generic_landscape_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ caption }} - created by ccGains 6 | 82 | 83 | 84 |
85 |

{{ caption }}

86 |
{{ intro }}
87 | {{ table }} 88 |
89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ccgains/binance_util.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Union, List 3 | 4 | QUOTE_ASSETS = ['BTC', 'ETH', 'USDT', 'TUSD', 'PAX', 'BNB'] 5 | 6 | 7 | def split_market_symbols(market: str) -> List[Union[str, None]]: 8 | # Binance trade csv provides a column called 'Market', which is 9 | # the non-separated trading pair (e.g. 'NEOBTC' or 'BNBUSDT') 10 | # Split this string to return [base, quote] assets 11 | for quote in QUOTE_ASSETS: 12 | quote_start = market.find(quote) 13 | if len(market) == quote_start + len(quote): # This quote asset is right-most 14 | return [market[:quote_start], market[quote_start:]] 15 | raise KeyError("Couldn't find a quote symbol for %s" % market) 16 | 17 | 18 | def currency_for(csv_line, side): 19 | market = split_market_symbols(csv_line[1]) # [base, quote] 20 | is_buy = csv_line[2].upper() == 'BUY' 21 | if side not in ['buy', 'sell']: 22 | return None 23 | if side == 'buy': 24 | return market[not is_buy] # base if is_buy, quote if not is_buy 25 | else: 26 | return market[is_buy] # quote if True, base if False 27 | 28 | 29 | # Binance trade csv output has following columns: 30 | # Date (UTC), Market, Type, Price, Amount, Total, Fee, Fee Coin 31 | TPLOC_BINANCE_TRADES = { 32 | 'kind': 2, 'dtime': 0, 33 | 'buy_currency': lambda cols: currency_for(cols, 'buy'), 34 | 'buy_amount': lambda cols: [Decimal(cols[4]), Decimal(cols[5])][(cols[2].upper() == 'SELL')], 35 | 'sell_currency': lambda cols: currency_for(cols, 'sell'), 36 | 'sell_amount': lambda cols: [Decimal(cols[4]), Decimal(cols[5])][(cols[2].upper() == 'BUY')], 37 | 'fee_currency': 7, 38 | 'fee_amount': 6, 39 | 'exchange': 'Binance', 40 | } 41 | # Binance deposit/withdrawal csv output has following columns: 42 | # Date, Coin, Amount, TransactionFee, Address, TXID, SourceAddress, PaymentID, Status 43 | TPLOC_BINANCE_DEPOSITS = { 44 | 'kind': "Deposit", 45 | 'dtime': 0, 46 | 'buy_currency': 1, 47 | 'buy_amount': 2, 48 | 'sell_currency': '', 49 | 'sell_amount': '0', 50 | 'fee_currency': 1, 51 | 'fee_amount': 3, 52 | 'exchange': "Binance", 53 | 'mark': -1, 54 | 'comment': 5 55 | } 56 | TPLOC_BINANCE_WITHDRAWALS = { 57 | 'kind': "Withdrawal", 58 | 'dtime': 0, 59 | 'buy_currency': '', 60 | 'buy_amount': '0', 61 | 'sell_currency': 1, 62 | 'sell_amount': 2, 63 | 'fee_currency': 1, 64 | 'fee_amount': 3, 65 | 'exchange': "Binance", 66 | 'mark': -1, 67 | 'comment': 5 68 | } 69 | TPLOC_BINANCE_DISTRIBUTIONS = { 70 | 'kind': 'Distribution', 71 | 'dtime': 0, 72 | 'buy_currency': 2, 73 | 'buy_amount': 3, 74 | 'sell_currency': '', 75 | 'sell_amount': '0', 76 | 'fee_currency': '', 77 | 'fee_amount': '0', 78 | 'exchange': 'Binance', 79 | 'mark': -1, 80 | 'comment': 4 81 | } 82 | -------------------------------------------------------------------------------- /ccgains/templates/shortreport_de.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Capital gains report {{ year }} - created by ccGains 6 | 89 | 90 | 91 |
92 |

Aufstellung privater Veräußerungsgeschäfte {{ year }}

93 |

aus dem Handel mit digitalen Währungen

94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
Zusammenfassung
Zeitraumvon {{ fromdate }} bis {{ todate }}
Gesamter Gewinn
(aus Verkäufen von Währungen in Besitz kürzer 1 Jahr)
{{ short_term_profit }}
Anzahl Veräußerungen (unten aufgeführt){{ num_trades }}
115 |
116 |
117 |
118 |

Auflistung aller Zahlungen mit digitalen Währungen und daraus erzielten Gewinne oder Verluste

119 | {{ cgtable }} 120 |
121 |
122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /ccgains/templates/shortreport_en.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Capital gains report {{ year }} - created by ccGains 6 | 89 | 90 | 91 |
92 |

Capital Gains Report {{ year }}

93 |

for trades with digital currencies

94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
Summary
Period{{ fromdate }} to {{ todate }}
Total profit (long and short term) {{ total_profit }}
Short term profit
(for currencies in possession less than a year)
{{ short_term_profit }}
Number of trades (listed below){{ num_trades }}
119 |
120 |
121 |
122 |

Trades listing

123 | {{ cgtable }} 124 |
125 |
126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/stable/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../..')) 18 | 19 | import mock 20 | MOCK_MODULES = ['numpy', 'scipy', 'matplotlib', 'matplotlib.pyplot', 21 | 'tables', 'pandas', 'requests', 'requests.exceptions', 22 | 'weasyprint', 'dateutil', 'dateutil.relativedelta', 23 | #sphinx autodoc breaks if I include these: 24 | #'jinja2', 'jinja2.sandbox', 25 | #'babel', 'babel.numbers', 'babel.dates', 26 | ] 27 | for mod_name in MOCK_MODULES: 28 | sys.modules[mod_name] = mock.Mock() 29 | 30 | 31 | # -- Project information ----------------------------------------------------- 32 | 33 | project = u'ccGains' 34 | copyright = u'2017, Jürgen Probst' 35 | author = u'Jürgen Probst' 36 | 37 | # The short X.Y version 38 | version = u'1.0' 39 | # The full version, including alpha/beta/rc tags 40 | release = u'1.0.0 beta' 41 | 42 | 43 | # -- General configuration --------------------------------------------------- 44 | 45 | # If your documentation needs a minimal Sphinx version, state it here. 46 | # 47 | # needs_sphinx = '1.0' 48 | 49 | # add docstrings from class and __init__ together: 50 | autoclass_content = 'both' 51 | # sort the members the same order they appear in the source: 52 | #autodoc_member_order = 'bysource' 53 | 54 | # Add any Sphinx extension module names here, as strings. They can be 55 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 56 | # ones. 57 | extensions = [ 58 | 'sphinx.ext.autodoc', 59 | 'sphinx.ext.coverage', 60 | ] 61 | 62 | # Add any paths that contain templates here, relative to this directory. 63 | templates_path = ['_templates'] 64 | 65 | # The suffix(es) of source filenames. 66 | # You can specify multiple suffix as a list of string: 67 | # 68 | # source_suffix = ['.rst', '.md'] 69 | source_suffix = '.rst' 70 | 71 | # The master toctree document. 72 | master_doc = 'index' 73 | 74 | # The language for content autogenerated by Sphinx. Refer to documentation 75 | # for a list of supported languages. 76 | # 77 | # This is also used if you do content translation via gettext catalogs. 78 | # Usually you set "language" from the command line for these cases. 79 | language = None 80 | 81 | # List of patterns, relative to source directory, that match files and 82 | # directories to ignore when looking for source files. 83 | # This pattern also affects html_static_path and html_extra_path . 84 | exclude_patterns = [] 85 | 86 | # The name of the Pygments (syntax highlighting) style to use. 87 | pygments_style = 'sphinx' 88 | 89 | 90 | # -- Options for HTML output ------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | # 95 | html_theme = 'sphinx_rtd_theme' 96 | 97 | # Theme options are theme-specific and customize the look and feel of a theme 98 | # further. For a list of options available for each theme, see the 99 | # documentation. 100 | # 101 | # html_theme_options = {} 102 | 103 | # Add any paths that contain custom static files (such as style sheets) here, 104 | # relative to this directory. They are copied after the builtin static files, 105 | # so a file named "default.css" will overwrite the builtin "default.css". 106 | html_static_path = ['_static'] 107 | 108 | # Custom sidebar templates, must be a dictionary that maps document names 109 | # to template names. 110 | # 111 | # The default sidebars (for documents that don't match any pattern) are 112 | # defined by theme itself. Builtin themes are using these templates by 113 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 114 | # 'searchbox.html']``. 115 | # 116 | # html_sidebars = {} 117 | 118 | 119 | # -- Options for HTMLHelp output --------------------------------------------- 120 | 121 | # Output file base name for HTML help builder. 122 | htmlhelp_basename = 'ccGainsdoc' 123 | 124 | 125 | # -- Options for LaTeX output ------------------------------------------------ 126 | 127 | latex_elements = { 128 | # The paper size ('letterpaper' or 'a4paper'). 129 | # 130 | # 'papersize': 'letterpaper', 131 | 132 | # The font size ('10pt', '11pt' or '12pt'). 133 | # 134 | # 'pointsize': '10pt', 135 | 136 | # Additional stuff for the LaTeX preamble. 137 | # 138 | # 'preamble': '', 139 | 140 | # Latex figure (float) alignment 141 | # 142 | # 'figure_align': 'htbp', 143 | } 144 | 145 | # Grouping the document tree into LaTeX files. List of tuples 146 | # (source start file, target name, title, 147 | # author, documentclass [howto, manual, or own class]). 148 | latex_documents = [ 149 | (master_doc, 'ccGains.tex', 'ccGains Documentation', 150 | u'Jürgen Probst', 'manual'), 151 | ] 152 | 153 | 154 | # -- Options for manual page output ------------------------------------------ 155 | 156 | # One entry per manual page. List of tuples 157 | # (source start file, name, description, authors, manual section). 158 | man_pages = [ 159 | (master_doc, 'ccgains', 'ccGains Documentation', 160 | [author], 1) 161 | ] 162 | 163 | 164 | # -- Options for Texinfo output ---------------------------------------------- 165 | 166 | # Grouping the document tree into Texinfo files. List of tuples 167 | # (source start file, target name, title, author, 168 | # dir menu entry, description, category) 169 | texinfo_documents = [ 170 | (master_doc, 'ccGains', 'ccGains Documentation', 171 | author, 'ccGains', 'One line description of project.', 172 | 'Miscellaneous'), 173 | ] 174 | 175 | 176 | # -- Extension configuration ------------------------------------------------- 177 | -------------------------------------------------------------------------------- /COPYING.LESSER: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /ccgains/templates/fullreport_de.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Capital gains report {{ year }} - created by ccGains 6 | 116 | 117 | 118 |

Berechnung der Gewinne aus privaten Veräußerungsgeschäften {{ year }}

119 |

aus dem Handel mit digitalen Währungen

120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
Zusammenfassung
Zeitraumvon {{ fromdate }} bis {{ todate }}
Gesamter Gewinn
(aus Verkäufen von Währungen in Besitz kürzer 1 Jahr)
{{ short_term_profit }}
Anzahl Veräußerungen (unten aufgeführt){{ num_trades }}
141 |
142 |
143 |

Auflistung aller Zahlungen mit digitalen Währungen und Berechnung der daraus erzielten Gewinne oder Verluste

144 | 145 | {% set vars = {'total': 0} %} 146 | {% for sale in sales_data.itertuples() %} 147 |
148 |

149 | #{{ loop.index }} 150 | {{ formatters["kind"](sale.kind) }} am {{ sale.sell_date|format_adapted_date }} 151 |

152 |
153 | {# amount of other currency bought in this sale: #} 154 | {% set buy_amount = sale.buy_ratio * sale.to_pay %} 155 | {# proceeds are value of spent amount at sale.sell_date: #} 156 | {% set proc = sale.bag_spent * sale.ex_rate %} 157 | {# fee-corrected proceeds for this partial sale: #} 158 | {% set corrproc = proc * (1 - sale.fee_ratio) %} 159 | {# profit for this sale (nevermind short/long term): #} 160 | {% set prof = corrproc - sale.spent_cost %} 161 | {% set _ = vars.update({'total': vars['total'] + prof}) %} 162 |

163 | Verkauf von {{ sale.to_pay|format_amount }} {{ sale.currency }} 164 | (davon {{ (sale.fee_ratio * 100)|round(3)|format_decimal }}% Gebühren) 165 | {% if sale.buy_currency %} 166 | für {{ buy_amount|format_amount }} {{ sale.buy_currency }} 167 | {% endif %} 168 | bei {{ sale.exchange }} 169 |
170 | Verwende Kaufmenge erworben am {{ sale.bag_date|format_adapted_date }}; 171 | Inhalt der Kaufmenge: {{ sale.bag_amount }} {{ sale.currency }} 172 |

173 | 174 | 175 | 176 | 177 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 192 | 193 | 194 | 195 | 196 | 199 | 200 | 201 | 202 | 203 | {% if sale.short_term %} 204 | 205 | 206 | {% else %} 207 | 208 | 209 | {% endif%} 210 | 211 | 212 |
Erwerbskosten der Kaufmenge: 178 | {{ sale.spent_cost|format_base_currency }} 179 | (Preis: {{ (sale.spent_cost / sale.bag_spent)|format_amount }} {{ sale.cost_currency }} / {{ sale.currency }})
Erlös:{{ proc|format_base_currency }}(Preis: {{ sale.ex_rate|format_amount }} {{ sale.cost_currency }} / {{ sale.currency }})
Erlös abzgl. Gebühren: 190 | {{ corrproc|format_base_currency }} 191 |
Gewinn: 197 | {{ prof|format_base_currency }} 198 | {% if prof < 0 %}(Verlust){% endif %}
Zu versteuern:Ja(In Besitz kürzer als ein Jahr)Nein(In Besitz länger als ein Jahr)
213 |

214 | Inhalt der Kaufmenge nach dem Verkauf: {{ (sale.bag_amount - sale.bag_spent)|format_amount }} {{ sale.currency }}; 215 | Verbleibt zu bezahlen mit nächster Kaufmenge: {{ (sale.to_pay - sale.bag_spent)|format_amount }} {{ sale.currency }} 216 |
217 | Summe zu versteuernder Gewinn {{ year }}: {{ vars['total']|format_base_currency }} 218 |

219 | 220 |

221 |
222 |
223 | {% endfor %} 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /ccgains/templates/fullreport_en.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Capital gains report {{ year }} - created by ccGains 6 | 116 | 117 | 118 |

Detailed calculation of capital gains {{ year }}

119 |

for trades with digital currencies

120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
Summary
Period{{ fromdate }} to {{ todate }}
Total profit (long and short term) {{ total_profit }}
Short term profit
(for currencies in possession less than a year)
{{ short_term_profit }}
Number of trades (listed below){{ num_trades }}
145 |
146 |
147 |

Listing of all trades made with digital currencies including calculation of gains and losses

148 | 149 | {% set vars = {'total': 0} %} 150 | {% for sale in sales_data.itertuples() %} 151 |
152 |

153 | #{{ loop.index }} 154 | {{ formatters["kind"](sale.kind) }} on {{ sale.sell_date|format_adapted_date }} 155 |

156 |
157 | {# amount of other currency bought in this sale: #} 158 | {% set buy_amount = sale.buy_ratio * sale.to_pay %} 159 | {# proceeds are value of spent amount at sale.sell_date: #} 160 | {% set proc = sale.bag_spent * sale.ex_rate %} 161 | {# fee-corrected proceeds for this partial sale: #} 162 | {% set corrproc = proc * (1 - sale.fee_ratio) %} 163 | {# profit for this sale (nevermind short/long term): #} 164 | {% set prof = corrproc - sale.spent_cost %} 165 | {% set _ = vars.update({'total': vars['total'] + prof}) %} 166 |

167 | Sold {{ sale.to_pay|format_amount }} {{ sale.currency }} 168 | (of which {{ (sale.fee_ratio * 100)|round(3)|format_decimal }}% are fees) 169 | {% if sale.buy_currency %} 170 | for {{ buy_amount|format_amount }} {{ sale.buy_currency }} 171 | {% endif %} 172 | on {{ sale.exchange }} 173 |
174 | Using currency bundle purchased on {{ sale.bag_date|format_adapted_date }}; 175 | Bundle's contents: {{ sale.bag_amount }} {{ sale.currency }} 176 |

177 | 178 | 179 | 180 | 181 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 196 | 197 | 198 | 199 | 200 | 203 | 204 | 205 | 206 | 207 | {% if sale.short_term %} 208 | 209 | 210 | {% else %} 211 | 212 | 213 | {% endif%} 214 | 215 | 216 |
Bundle's purchase cost: 182 | {{ sale.spent_cost|format_base_currency }} 183 | (price: {{ (sale.spent_cost / sale.bag_spent)|format_amount }} {{ sale.cost_currency }} / {{ sale.currency }})
Proceeds:{{ proc|format_base_currency }}(price: {{ sale.ex_rate|format_amount }} {{ sale.cost_currency }} / {{ sale.currency }})
Proceeds minus fees: 194 | {{ corrproc|format_base_currency }} 195 |
Profit: 201 | {{ prof|format_base_currency }} 202 | {% if prof < 0 %}(loss){% endif %}
Taxable:Yes(in possession for less than a year)No(in possession for more than a year)
217 |

218 | Bundle's contents after sale: {{ (sale.bag_amount - sale.bag_spent)|format_amount }} {{ sale.currency }}; 219 | Remaining to be paid using next bundle: {{ (sale.to_pay - sale.bag_spent)|format_amount }} {{ sale.currency }} 220 |
221 | Total taxable gains {{ year }}: {{ vars['total']|format_base_currency }} 222 |

223 | 224 |

225 |
226 |
227 | {% endfor %} 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /ccgains/relations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # 4 | # ---------------------------------------------------------------------- 5 | # ccGains - Create capital gains reports for cryptocurrency trading. 6 | # Copyright (C) 2017 Jürgen Probst 7 | # 8 | # This file is part of ccGains. 9 | # 10 | # ccGains is free software: you can redistribute it and/or modify it 11 | # under the terms of the GNU Lesser General Public License as published 12 | # by the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # ccGains is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public 21 | # License along with ccGains. If not, see . 22 | # ---------------------------------------------------------------------- 23 | # 24 | # Get the latest version at: https://github.com/probstj/ccGains 25 | # 26 | 27 | from __future__ import division 28 | 29 | from functools import total_ordering 30 | from typing import Dict, List, NamedTuple, Tuple 31 | 32 | 33 | class CurrencyPair(NamedTuple("CurrencyPair", [("base", str), ("quote", str)])): 34 | """CurrencyPair(base, quote)""" 35 | 36 | def reversed(self): 37 | """Swap base(from) for quote(to) currencies""" 38 | return CurrencyPair(self.quote, self.base) 39 | 40 | def __add__(self, other): 41 | if isinstance(other, CurrencyPair): 42 | return CurrencyPair(self.base, other.quote) 43 | raise NotImplementedError 44 | 45 | def __radd__(self, other): 46 | if isinstance(other, CurrencyPair): 47 | return CurrencyPair(other.base, self.quote) 48 | raise NotImplementedError 49 | 50 | def __repr__(self): 51 | return " ({0.base}, {0.quote})".format(self) 52 | 53 | 54 | class RecipeStep( 55 | NamedTuple("RecipeStep", [("base", str), ("quote", str), ("reciprocal", bool)]) 56 | ): 57 | """One step in a conversion recipe, indicating base (from) currency, quote (to) 58 | currency, and whether the reciprocal of the price should be used for this step. 59 | """ 60 | 61 | def as_recipe(self): 62 | """Create a Recipe (with one step) from this RecipeStep""" 63 | return Recipe(1, [self]) 64 | 65 | def reversed(self): 66 | """Return a copy of this recipe step with reciprocal set opposite""" 67 | return RecipeStep(self.base, self.quote, not self.reciprocal) 68 | 69 | def __add__(self, other): 70 | """Adding one RecipeStep to another creates a Recipe. 71 | 72 | Adding a Recipe to a RecipeStep creates a new Recipe with the 'other' 73 | Recipe's steps at the end of the new list of recipe steps 74 | """ 75 | 76 | if type(other) == Recipe: 77 | return Recipe(other.num_steps + 1, [self] + other.recipe_steps) 78 | elif type(other) == self.__class__: 79 | return Recipe(2, [self, other]) 80 | raise NotImplementedError 81 | 82 | def __radd__(self, other): 83 | if type(other) == Recipe: 84 | return Recipe(other.num_steps + 1, other.recipe_steps + [self]) 85 | elif type(other) == self.__class__: 86 | return Recipe(2, [other, self]) 87 | raise NotImplementedError 88 | 89 | 90 | @total_ordering 91 | class Recipe( 92 | NamedTuple("Recipe", [("num_steps", int), ("recipe_steps", List[RecipeStep])]) 93 | ): 94 | """A conversion recipe made up of `num_steps` steps which are given in 95 | `recipe_steps`, showing how to convert from `recipe_steps[0].base` to 96 | `recipe_steps[-1].quote` 97 | """ 98 | 99 | def reversed(self): 100 | """A reversed recipe has RecipeSteps in the reversed order, and the 101 | opposite value for `reciprocal` for each step 102 | """ 103 | 104 | reversed_steps = [step.reversed() for step in reversed(self.recipe_steps)] 105 | return Recipe(self.num_steps, reversed_steps) 106 | 107 | def __gt__(self, other): 108 | """One Recipe is 'greater than' another if it requires more steps""" 109 | if isinstance(other, self.__class__): 110 | return self.num_steps > other.num_steps 111 | raise NotImplementedError 112 | 113 | def __add__(self, other): 114 | """Adding an 'other' Recipe involves adding number of steps, and 115 | extending the list of recipe steps with the steps in 'other' 116 | 117 | Adding an 'other' Recipe step involves incrementing number of 118 | steps by 1, and extending recipe steps with the other RecipeStep 119 | """ 120 | 121 | if isinstance(other, Recipe): 122 | return Recipe( 123 | self.num_steps + other.num_steps, self.recipe_steps + other.recipe_steps 124 | ) 125 | elif isinstance(other, RecipeStep): 126 | return Recipe(self.num_steps + 1, self.recipe_steps + [other]) 127 | raise NotImplementedError 128 | 129 | def __radd__(self, other): 130 | """Reverse add still increments number of steps, but adds the 131 | additional steps on the opposite of the list 132 | """ 133 | 134 | if isinstance(other, Recipe): 135 | return Recipe( 136 | self.num_steps + other.num_steps, other.recipe_steps + self.recipe_steps 137 | ) 138 | elif isinstance(other, RecipeStep): 139 | return Recipe(self.num_steps + 1, [other] + self.recipe_steps) 140 | raise NotImplementedError 141 | 142 | 143 | # Custom type for static type-checking in a meaningful way 144 | RecipeDict = Dict[CurrencyPair, Recipe] 145 | 146 | 147 | class CurrencyRelation(object): 148 | def __init__(self, *args): 149 | """Create a CurrencyRelation object. This object allows 150 | exchanging values between currencies, using historical 151 | exchange rates at specific times. 152 | 153 | :param args: 154 | Any number of HistoricData objects. If multiple HistoricData 155 | objects are given with the same unit, only the last one will 156 | be used. More/updated HistoricData objects can be supplied 157 | later with `add_historic_data` method. 158 | """ 159 | 160 | self.historic_prices = {} 161 | for hist_data in args: 162 | key = CurrencyPair(hist_data.cfrom, hist_data.cto) 163 | self.historic_prices[key] = hist_data 164 | 165 | self.recipes = {} # type: RecipeDict 166 | self.update_available_pairs() 167 | 168 | def add_historic_data(self, hist_data): 169 | """Add an HistoricData object. If a HistoricData object with 170 | the same unit has already been added, it will be updated. 171 | """ 172 | 173 | key = CurrencyPair(hist_data.cfrom, hist_data.cto) 174 | self.historic_prices[key] = hist_data 175 | self.update_available_pairs(key) 176 | 177 | def update_available_pairs(self, update_pair=None): 178 | """Update internal list of pairs with available historical rate. 179 | 180 | :param update_pair: tuple (from_currency, to_currency); 181 | If supplied, updates existing recipes with pair supplied. 182 | Default (None) will force rebuild of pairs list from scratch 183 | based on supplied historical data sets. 184 | """ 185 | 186 | if not update_pair: 187 | # Regenerate all recipes 188 | self.recipes = {} # type: RecipeDict 189 | to_add = list(self.historic_prices.keys()) 190 | else: 191 | if not isinstance(update_pair, CurrencyPair): 192 | update_pair = CurrencyPair(update_pair[0], update_pair[1]) 193 | # check if update_pair provided is really available: 194 | if update_pair not in self.historic_prices: 195 | if update_pair.reversed() not in self.historic_prices: 196 | raise ValueError( 197 | "Supplied new pair {} has no historical data. " 198 | "Please provide it with `add_historic_data` first." 199 | "".format(update_pair) 200 | ) 201 | else: 202 | to_add = [update_pair.reversed()] 203 | else: 204 | to_add = [update_pair] 205 | 206 | def can_add_before(new: CurrencyPair, existing: CurrencyPair) -> bool: 207 | return new.quote == existing.base and new != existing.reversed() 208 | 209 | def can_add_after(new: CurrencyPair, existing: CurrencyPair) -> bool: 210 | return new.base == existing.quote and new != existing.reversed() 211 | 212 | def update_if_shorter(pair: CurrencyPair, recipe: Recipe) -> bool: 213 | if pair not in self.recipes or recipe < self.recipes[pair]: 214 | self.recipes[pair] = recipe 215 | self.recipes[pair.reversed()] = recipe.reversed() 216 | return True 217 | return False 218 | 219 | # Loop through each HistoricPrice 220 | for root_base, root_quote in to_add: 221 | added_after = [] # type: List[Tuple[CurrencyPair, Recipe]] 222 | added_before = [] # type: List[Tuple[CurrencyPair, Recipe]] 223 | 224 | root_pair = CurrencyPair(root_base, root_quote) 225 | root_step = RecipeStep(root_pair.base, root_pair.quote, False) 226 | 227 | for known_pair, known_recipe in tuple(self.recipes.items()): 228 | 229 | if can_add_before(root_pair, known_pair): 230 | before_pair = root_pair + known_pair 231 | new_recipe = root_step + known_recipe 232 | 233 | # If already known, only add if new recipe is shorter: 234 | if update_if_shorter(before_pair, new_recipe): 235 | # keep track of addition, will be needed later: 236 | added_before.append((known_pair, known_recipe)) 237 | 238 | elif can_add_after(root_pair, known_pair): 239 | # New pair can be added after existing recipe steps: 240 | after_pair = known_pair + root_pair 241 | new_recipe = known_recipe + root_step 242 | 243 | # If already known, only add if new recipe is shorter: 244 | if update_if_shorter(after_pair, new_recipe): 245 | # keep track of addition, will be needed later: 246 | added_after.append((known_pair, known_recipe)) 247 | 248 | # If the new pair could be added to the beginning as well as 249 | # to the end of existing recipes, there are also new recipes 250 | # where the new pair joins two old recipes together: 251 | for pair_a, recipe_a in added_after: 252 | for pair_b, recipe_b in added_before: 253 | 254 | middle_pair = pair_a + pair_b 255 | middle_recipe = recipe_a + root_step + recipe_b 256 | 257 | # If already known, only add if new recipe is shorter: 258 | update_if_shorter(middle_pair, middle_recipe) 259 | 260 | # And finally, don't forget to add the new pair by itself: 261 | update_if_shorter(root_pair, root_step.as_recipe()) 262 | 263 | return self.recipes 264 | 265 | def get_rate(self, dtime, from_currency, to_currency): 266 | """Return the rate for conversion of *from_currency* to 267 | *to_currency* at the datetime *dtime*. 268 | 269 | If a direct relation of the currency pair has not been added with 270 | `add_historic_data` before, an indirect route using multiple 271 | added pairs is tried. If this also fails, a KeyError is raised. 272 | """ 273 | 274 | key = CurrencyPair(from_currency.upper(), to_currency.upper()) 275 | steps = self.recipes[key].recipe_steps 276 | result = 1 277 | for base_cur, quote_cur, inverse in steps: 278 | step_key = CurrencyPair(base_cur, quote_cur) 279 | if not inverse: 280 | result *= self.historic_prices[step_key].get_price(dtime) 281 | else: 282 | result /= self.historic_prices[step_key].get_price(dtime) 283 | return result 284 | -------------------------------------------------------------------------------- /tests/test_bags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # 4 | # ---------------------------------------------------------------------- 5 | # ccGains - Create capital gains reports for cryptocurrency trading. 6 | # Copyright (C) 2017 Jürgen Probst 7 | # 8 | # This file is part of ccGains. 9 | # 10 | # ccGains is free software: you can redistribute it and/or modify it 11 | # under the terms of the GNU Lesser General Public License as published 12 | # by the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # ccGains is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public 21 | # License along with ccGains. If not, see . 22 | # ---------------------------------------------------------------------- 23 | # 24 | # Get the latest version at: https://github.com/probstj/ccGains 25 | # 26 | 27 | from __future__ import division 28 | 29 | import unittest 30 | 31 | from ccgains import trades, historic_data, relations, bags 32 | import pandas as pd 33 | import numpy as np 34 | from decimal import Decimal as D 35 | import logging 36 | try: 37 | # for Python2: 38 | from StringIO import StringIO 39 | # in Python 2, io.StringIO won't work 40 | except ImportError: 41 | # Python 3: 42 | from io import StringIO 43 | 44 | 45 | class TestBagQueue(unittest.TestCase): 46 | 47 | def setUp(self): 48 | self.logger = logging.getLogger('ccgains') 49 | self.logger.setLevel(logging.DEBUG) 50 | # Create console handler for debugging: 51 | self.handler = logging.StreamHandler() 52 | self.handler.setLevel(logging.DEBUG) 53 | # Create formatters and add them to the handlers 54 | chformatter = logging.Formatter('%(name)-13s %(levelname)-8s: %(message)s') 55 | self.handler.setFormatter(chformatter) 56 | self.logger.addHandler(logging.NullHandler()) 57 | # Add the handlers to the logger 58 | # (uncomment this to enable output for all tests): 59 | #self.logger.addHandler(self.handler) 60 | 61 | # Make up some historic data: 62 | self.rng = pd.date_range('2017-01-01', periods=5, freq='D', tz='UTC') 63 | h1 = historic_data.HistoricData('EUR/BTC') 64 | h1.data = pd.Series( 65 | data=map(D, np.linspace(1000, 3000, num=5)), index=self.rng) 66 | h2 = historic_data.HistoricData('XMR/BTC') 67 | h2.data = pd.Series( 68 | data=map(D, np.linspace(50, 30, num=5)), index=self.rng) 69 | self.rel = relations.CurrencyRelation(h1, h2) 70 | 71 | def tearDown(self): 72 | for h in self.logger.handlers[::-1]: 73 | h.close() 74 | self.logger.removeHandler(h) 75 | 76 | def log_bags(self, bagqueue): 77 | self.logger.info("State of bags: \n%s", 78 | ' ' + '\n '.join(str(bagqueue).split('\n'))) 79 | 80 | def trading_set( 81 | self, bagqueue, budget, currency, dayslist, currlist, feelist): 82 | """Starting with a budget, exchange all of the available budget 83 | every day in a given list to another currency. 84 | 85 | The trades will be processed by *bagqueue* and exchange rates 86 | taken from self.rel. Make sure that the involved currencies' 87 | exchange rates for the given days are available in self.rel. 88 | 89 | :param bagqueue: The BagQueue object which will process the trades 90 | :param budget: The starting budget 91 | :param currency: The currency of starting budget 92 | :param dayslist: List of datetimes. On each of these days, one 93 | currency exchange, using all of the available budget in 94 | whatever currency that day, will be made. 95 | :param currlist: List of currencies, same length as *dayslist*. 96 | Each day, the available budget will be exchanged into 97 | the day's currency in this list. 98 | :param feelist: List of (fee_portion, fee_currency)-tuples, same 99 | length as *dayslist*. For each day's trade, a ratio of 100 | the budget (or currency traded into) will be used for paying 101 | the exchange fee. This fee amounts to `fee_portion * budget` 102 | or `fee_portion * budget * exhange_rate`, depending on 103 | fee_currency. 104 | :returns: The budget after the last trade, i.e. the available 105 | amount of currency currlist[-1]. 106 | 107 | Examine *bagqueue* to see profits etc. afterwards. 108 | 109 | Some intermediate results about the profits and bag's status 110 | will be logged (info level). To see this, add a handler to 111 | self.logger before calling this. 112 | 113 | """ 114 | current_budget = D(budget) 115 | current_curr = currency 116 | for i, day in enumerate(dayslist): 117 | to_curr = currlist[i] 118 | fee_p, fee_c = D(feelist[i][0]), feelist[i][1] 119 | rate = D(self.rel.get_rate(day, current_curr, to_curr)) 120 | if fee_c == current_curr or fee_p == 0: 121 | fee = current_budget * fee_p 122 | to_amount = rate * (current_budget - fee) 123 | elif fee_c == to_curr: 124 | to_amount = rate * current_budget 125 | fee = to_amount * fee_p 126 | to_amount -= fee 127 | else: 128 | raise ValueError( 129 | 'The fee currency must be one of the two available ' 130 | 'currencies on the given day: either the current ' 131 | 'budget\'s or the currency to be traded into.') 132 | trade = trades.Trade( 133 | 'Trade', day, to_curr, to_amount, 134 | current_curr, current_budget, fee_c, fee) 135 | bagqueue.process_trade(trade) 136 | self.log_bags(bagqueue) 137 | self.logger.info( 138 | "Profit so far: %.2f %s\n", 139 | bagqueue.profit[str(day.year)], bagqueue.currency) 140 | current_budget = to_amount 141 | current_curr = to_curr 142 | return to_amount 143 | 144 | def test_trading_profits_no_fees(self): 145 | # Add handler to the logger (uncomment this to enable output) 146 | #self.logger.addHandler(self.handler) 147 | 148 | bagqueue = bags.BagQueue('EUR', self.rel) 149 | budget=1000 150 | # Make up and process some trades: 151 | proceeds = self.trading_set( 152 | bagqueue, 153 | budget=budget, 154 | currency='EUR', 155 | dayslist=[self.rng[0], self.rng[2], self.rng[4]], 156 | currlist=['BTC', 'XMR', 'EUR'], 157 | feelist=[(0, ''), (0, ''), (0, '')]) 158 | 159 | # check correct profit: 160 | self.assertEqual( 161 | proceeds - budget, bagqueue.profit[str(self.rng[0].year)]) 162 | # check that bagqueue is empty and cleaned up: 163 | self.assertFalse(bagqueue.totals) 164 | self.assertFalse(bagqueue.bags) 165 | self.assertFalse(bagqueue.in_transit) 166 | 167 | def test_trading_profits_with_fees(self): 168 | # Add handler to the logger (uncomment this to enable output) 169 | #self.logger.addHandler(self.handler) 170 | 171 | budget=1000 172 | # List of fee percentages to be tested: 173 | fee_p = [D('0.00025'), D(1)/D(3), D(2)/D(3)] 174 | days = [self.rng[0], self.rng[2], self.rng[4]] 175 | currlist = ['BTC', 'XMR', 'EUR'] 176 | for fee in fee_p: 177 | for trade_to_fee in range(len(currlist)): 178 | for i in range(2): 179 | feelist = [(0, '')] * len(currlist) 180 | feelist[trade_to_fee] = (fee, currlist[trade_to_fee - i]) 181 | self.logger.info("Testing fee list %s" % feelist) 182 | self.logger.info("Working with new empty BagQueue object") 183 | bagqueue = bags.BagQueue('EUR', self.rel) 184 | # Make up and process some trades: 185 | proceeds = self.trading_set( 186 | bagqueue, 187 | budget=budget, 188 | currency='EUR', 189 | dayslist=days, 190 | currlist=currlist, 191 | feelist=feelist) 192 | 193 | # check correct profit: 194 | self.assertEqual( 195 | proceeds - budget, 196 | bagqueue.profit[str(self.rng[0].year)]) 197 | # check that bagqueue is empty and cleaned up: 198 | self.assertFalse(bagqueue.totals) 199 | self.assertFalse(bagqueue.bags) 200 | self.assertFalse(bagqueue.in_transit) 201 | 202 | def test_bag_cost_after_trading_with_fees(self): 203 | # Add handler to the logger (uncomment this to enable output) 204 | #self.logger.addHandler(self.handler) 205 | 206 | budget=1000 207 | # List of fee percentages to be tested: 208 | fee_p = [0, D('0.00025'), D(1)/D(3), D(2)/D(3)] 209 | days = [self.rng[0], self.rng[2]] 210 | currlist = ['BTC', 'XMR'] 211 | for fee in fee_p: 212 | for fee_cur in currlist: 213 | bagqueue = bags.BagQueue('EUR', self.rel) 214 | self.logger.info("Working with new empty BagQueue object") 215 | # Make up and process some trades: 216 | self.trading_set( 217 | bagqueue, 218 | budget=budget, 219 | currency='EUR', 220 | dayslist=days, 221 | currlist=currlist, 222 | feelist=[(0, ''), (fee, fee_cur)]) 223 | 224 | self.assertEqual( 225 | bagqueue.bags[''][-1].cost, 226 | budget + bagqueue.profit[str(self.rng[0].year)]) 227 | 228 | def test_saving_loading(self): 229 | # Add handler to the logger (uncomment this to enable output) 230 | #self.logger.addHandler(self.handler) 231 | 232 | bagqueue = bags.BagQueue('EUR', self.rel) 233 | 234 | # Make up some transactions: 235 | budget = 1000 236 | day1 = self.rng[0] 237 | day2 = self.rng[2] 238 | day3 = self.rng[4] 239 | btc = self.rel.get_rate(day1, 'EUR', 'BTC') * budget 240 | t1 = trades.Trade('Buy', day1, 'BTC', btc, 'EUR', budget) 241 | # also include withdrawal and deposit: 242 | t2 = trades.Trade('Withdraw', day1, '', 0, 'BTC', btc / 3) 243 | t3 = trades.Trade('Deposit', day2, 'BTC', btc / 3, '', 0) 244 | xmr = self.rel.get_rate(day2, 'BTC', 'XMR') * btc 245 | t4 = trades.Trade( 246 | 'Trade', day2, 'XMR', xmr, 'BTC', btc, 'XMR', '0') 247 | proceeds = self.rel.get_rate(day3, 'XMR', 'EUR') * xmr 248 | t5 = trades.Trade( 249 | 'Trade', day3, 'EUR', proceeds, 'XMR', xmr, '', '0') 250 | 251 | # Only process first two transactions, so we have a bag in 252 | # bagqueue.bags and one in bagqueue.in_transit to save: 253 | for t in [t1, t2]: 254 | bagqueue.process_trade(t) 255 | self.log_bags(bagqueue) 256 | self.logger.info("Profit so far: %.2f %s\n", 257 | bagqueue.profit[str(day1.year)], 258 | bagqueue.currency) 259 | 260 | # save state 261 | outfile = StringIO() 262 | bagqueue.save(outfile) 263 | # create new BagQueue and restore state: 264 | bf2 = bags.BagQueue('EUR', self.rel) 265 | outfile.seek(0) 266 | bf2.load(outfile) 267 | 268 | # skip bf2.bags, bf2.in_transit and bf2.report, since the bags 269 | # and the report are new objects: 270 | excl = ['bags', 'in_transit', 'report'] 271 | self.assertDictEqual( 272 | {k:v for k, v in bagqueue.__dict__.items() if k not in excl}, 273 | {k:v for k, v in bf2.__dict__.items() if k not in excl}) 274 | # But the bags' contents must be equal: 275 | for ex in bagqueue.bags: 276 | for i, b in enumerate(bagqueue.bags[ex]): 277 | self.assertDictEqual(b.__dict__, bf2.bags[ex][i].__dict__) 278 | for cur in bagqueue.in_transit: 279 | for i, b in enumerate(bagqueue.in_transit[cur]): 280 | self.assertDictEqual( 281 | b.__dict__, bf2.in_transit[cur][i].__dict__) 282 | # We did not pay anything yet, thus, the report should be empty: 283 | self.assertListEqual(bagqueue.report.data, bf2.report.data) 284 | self.assertListEqual(bf2.report.data, []) 285 | 286 | # process the rest of the transactions: 287 | for t in [t3, t4, t5]: 288 | bagqueue.process_trade(t) 289 | bf2.process_trade(t) 290 | self.log_bags(bagqueue) 291 | self.logger.info("Profit so far: %.2f %s\n", 292 | bagqueue.profit[str(day3.year)], bagqueue.currency) 293 | 294 | # Now, bags lists should be empty, but we still need to 295 | # check the report manually: 296 | self.assertDictEqual( 297 | {k:v for k, v in bagqueue.__dict__.items() if k != 'report'}, 298 | {k:v for k, v in bf2.__dict__.items() if k != 'report'}) 299 | self.assertListEqual(bagqueue.report.data, bf2.report.data) 300 | 301 | def test_no_like_for_like(self): 302 | """Test that it is not possible to deposit, withdraw, buy, or pay 303 | with the BagFIFO base currency. 304 | 305 | Note: testing against both the new CurrencyTypeException and also ValueError 306 | This is in case a future change modifies the order in which arguments are 307 | checked in any of these functions, which is not critical in some cases. 308 | The point is that none of these should succeed if the BagFIFO base currency 309 | (fiat) is used in any transaction instead of expected crypto 310 | """ 311 | 312 | # Note: 'usd' will be converted to uppercase in the constructor 313 | bagfifo = bags.BagFIFO('usd', self.rel) 314 | 315 | assert bagfifo.currency == 'USD' 316 | 317 | # Should not be able to buy 318 | with self.subTest(method="buy_with_base_currency"): 319 | with self.assertRaises((bags.CurrencyTypeException, ValueError)): 320 | bagfifo.buy_with_base_currency(self.rng[0], 10, 'usd', 10, 'Coinbase') 321 | 322 | # Should not be able to pay 323 | with self.subTest(method="pay"): 324 | with self.assertRaises((bags.CurrencyTypeException, ValueError)): 325 | bagfifo.pay(self.rng[3], 'usd', '1','Coinbase') 326 | 327 | 328 | if __name__ == '__main__': 329 | unittest.main() 330 | -------------------------------------------------------------------------------- /tests/test_relations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # 4 | # ---------------------------------------------------------------------- 5 | # ccGains - Create capital gains reports for cryptocurrency trading. 6 | # Copyright (C) 2017 Jürgen Probst 7 | # 8 | # This file is part of ccGains. 9 | # 10 | # ccGains is free software: you can redistribute it and/or modify it 11 | # under the terms of the GNU Lesser General Public License as published 12 | # by the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # ccGains is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public 21 | # License along with ccGains. If not, see . 22 | # ---------------------------------------------------------------------- 23 | # 24 | # Get the latest version at: https://github.com/probstj/ccGains 25 | # 26 | 27 | from __future__ import division 28 | 29 | import unittest 30 | 31 | from ccgains import relations 32 | from ccgains.relations import CurrencyPair, RecipeStep, Recipe 33 | 34 | 35 | class TestCurrencyRelation(unittest.TestCase): 36 | def setUp(self): 37 | self.rel = relations.CurrencyRelation() 38 | 39 | def test_update_pairs_one(self): 40 | # Create dummy pairs: 41 | pairs = [("A", "B")] 42 | # Expected result: 43 | d = {("A", "B"): (1, [("A", "B", False)]), ("B", "A"): (1, [("A", "B", True)])} 44 | 45 | # Make dummy historic data dict: 46 | for p in pairs: 47 | self.rel.historic_prices[p] = None 48 | self.rel.update_available_pairs() 49 | 50 | self.assertDictEqual(self.rel.recipes, d) 51 | 52 | def test_update_pairs_two_separate(self): 53 | # Create dummy pairs: 54 | pairs = [("A", "B"), ("C", "D")] 55 | # Expected result: 56 | d = { 57 | ("A", "B"): (1, [("A", "B", False)]), 58 | ("B", "A"): (1, [("A", "B", True)]), 59 | ("C", "D"): (1, [("C", "D", False)]), 60 | ("D", "C"): (1, [("C", "D", True)]), 61 | } 62 | 63 | # Make dummy historic data dict: 64 | for p in pairs: 65 | self.rel.historic_prices[p] = None 66 | self.rel.update_available_pairs() 67 | 68 | self.assertDictEqual(self.rel.recipes, d) 69 | 70 | def test_update_pairs_two_joined(self): 71 | # Create dummy pairs: 72 | pairs = [("A", "B"), ("B", "C")] 73 | # Expected result: 74 | d = { 75 | ("A", "B"): (1, [("A", "B", False)]), 76 | ("B", "A"): (1, [("A", "B", True)]), 77 | ("B", "C"): (1, [("B", "C", False)]), 78 | ("C", "B"): (1, [("B", "C", True)]), 79 | ("A", "C"): (2, [("A", "B", False), ("B", "C", False)]), 80 | ("C", "A"): (2, [("B", "C", True), ("A", "B", True)]), 81 | } 82 | 83 | # Make dummy historic data dict: 84 | for p in pairs: 85 | self.rel.historic_prices[p] = None 86 | self.rel.update_available_pairs() 87 | 88 | self.assertDictEqual(self.rel.recipes, d) 89 | 90 | def test_update_pairs_two_joinedreverse(self): 91 | # Create dummy pairs: 92 | pairs = [("A", "B"), ("C", "B")] 93 | # Expected result: 94 | d = { 95 | ("A", "B"): (1, [("A", "B", False)]), 96 | ("B", "A"): (1, [("A", "B", True)]), 97 | ("C", "B"): (1, [("C", "B", False)]), 98 | ("B", "C"): (1, [("C", "B", True)]), 99 | ("A", "C"): (2, [("A", "B", False), ("C", "B", True)]), 100 | ("C", "A"): (2, [("C", "B", False), ("A", "B", True)]), 101 | } 102 | 103 | # Make dummy historic data dict: 104 | for p in pairs: 105 | self.rel.historic_prices[p] = None 106 | self.rel.update_available_pairs() 107 | 108 | self.assertDictEqual(self.rel.recipes, d) 109 | 110 | def test_update_pairs_two_separate_then_joined(self): 111 | # Diff for this test case might be long. Disable length constraint: 112 | oldmaxDiff = self.maxDiff 113 | self.maxDiff = None 114 | 115 | # Create dummy pairs: 116 | pairs = [("A", "B"), ("C", "D")] 117 | pair_extra = ("B", "C") 118 | # Expected result: 119 | d = { 120 | ("A", "B"): (1, [("A", "B", False)]), 121 | ("B", "A"): (1, [("A", "B", True)]), 122 | ("C", "D"): (1, [("C", "D", False)]), 123 | ("D", "C"): (1, [("C", "D", True)]), 124 | } 125 | d_extra = { 126 | ("B", "C"): (1, [("B", "C", False)]), 127 | ("C", "B"): (1, [("B", "C", True)]), 128 | ("A", "C"): (2, [("A", "B", False), ("B", "C", False)]), 129 | ("C", "A"): (2, [("B", "C", True), ("A", "B", True)]), 130 | ("B", "D"): (2, [("B", "C", False), ("C", "D", False)]), 131 | ("D", "B"): (2, [("C", "D", True), ("B", "C", True)]), 132 | ("A", "D"): (3, [("A", "B", False), ("B", "C", False), ("C", "D", False)]), 133 | ("D", "A"): (3, [("C", "D", True), ("B", "C", True), ("A", "B", True)]), 134 | } 135 | 136 | # Make dummy historic data dict: 137 | for p in pairs: 138 | self.rel.historic_prices[p] = None 139 | self.rel.update_available_pairs() 140 | 141 | self.assertDictEqual(self.rel.recipes, d) 142 | 143 | # make this a dummy HistoricData object: 144 | self.cfrom, self.cto = pair_extra 145 | self.rel.add_historic_data(self) 146 | 147 | d.update(d_extra) 148 | self.assertDictEqual(self.rel.recipes, d) 149 | 150 | # If everything went well, reset maxDiff: 151 | self.maxDiff = oldmaxDiff 152 | 153 | def test_update_pairs_two_separate_then_joinedreverse(self): 154 | # Diff for this test case might be long. Disable length constraint: 155 | oldmaxDiff = self.maxDiff 156 | self.maxDiff = None 157 | 158 | # Create dummy pairs: 159 | pairs = [("A", "B"), ("C", "D")] 160 | pair_extra = ("C", "B") 161 | # Expected result: 162 | d = { 163 | ("A", "B"): (1, [("A", "B", False)]), 164 | ("B", "A"): (1, [("A", "B", True)]), 165 | ("C", "D"): (1, [("C", "D", False)]), 166 | ("D", "C"): (1, [("C", "D", True)]), 167 | } 168 | d_extra = { 169 | ("B", "C"): (1, [("C", "B", True)]), 170 | ("C", "B"): (1, [("C", "B", False)]), 171 | ("A", "C"): (2, [("A", "B", False), ("C", "B", True)]), 172 | ("C", "A"): (2, [("C", "B", False), ("A", "B", True)]), 173 | ("B", "D"): (2, [("C", "B", True), ("C", "D", False)]), 174 | ("D", "B"): (2, [("C", "D", True), ("C", "B", False)]), 175 | ("A", "D"): (3, [("A", "B", False), ("C", "B", True), ("C", "D", False)]), 176 | ("D", "A"): (3, [("C", "D", True), ("C", "B", False), ("A", "B", True)]), 177 | } 178 | 179 | # Make dummy historic data dict: 180 | for p in pairs: 181 | self.rel.historic_prices[p] = None 182 | self.rel.update_available_pairs() 183 | 184 | self.assertDictEqual(self.rel.recipes, d) 185 | 186 | # make this a dummy HistoricData object: 187 | self.cfrom, self.cto = pair_extra 188 | self.rel.add_historic_data(self) 189 | 190 | d.update(d_extra) 191 | self.assertDictEqual(self.rel.recipes, d) 192 | 193 | # If everything went well, reset maxDiff: 194 | self.maxDiff = oldmaxDiff 195 | 196 | def test_update_pairs_two_separate_then_joined_then_optimized(self): 197 | # Create dummy pairs: 198 | pairs = [("A", "B"), ("C", "D")] 199 | pair_extra = ("B", "C") 200 | 201 | # Make dummy historic data dict: 202 | for p in pairs: 203 | self.rel.historic_prices[p] = None 204 | self.rel.update_available_pairs() 205 | 206 | # make this a dummy HistoricData object: 207 | self.cfrom, self.cto = pair_extra 208 | self.rel.add_historic_data(self) 209 | 210 | # add pair with direct exchange rate data: 211 | direct_pair = ("A", "D") 212 | self.cfrom, self.cto = direct_pair 213 | self.rel.add_historic_data(self) 214 | 215 | self.assertEqual(self.rel.recipes[direct_pair], (1, [("A", "D", False)])) 216 | self.assertEqual(self.rel.recipes[direct_pair[::-1]], (1, [("A", "D", True)])) 217 | 218 | 219 | class TestRelationCustomTypes(unittest.TestCase): 220 | def test_currency_pair_reverse(self): 221 | """Test reversing a CurrencyPair results in base and quote swapped""" 222 | pair = CurrencyPair("BTC", "USD") 223 | expected_reversed_pair = CurrencyPair("USD", "BTC") 224 | 225 | self.assertEqual(pair.reversed(), expected_reversed_pair) 226 | 227 | def test_currency_pair_add(self): 228 | """Test that adding one CurrencyPair to another results in a CurrencyPair 229 | with (first.base, second.quote) 230 | """ 231 | first = CurrencyPair("XRP", "BTC") 232 | second = CurrencyPair("BTC", "USD") 233 | expected_pair = CurrencyPair("XRP", "USD") 234 | self.assertEqual(first + second, expected_pair) 235 | 236 | def test_currency_pair_immutable_and_hashable(self): 237 | """CurrencyPairs are used as dictionary keys, so must be hashable 238 | and immutable. Hashes of two equal CurrencyPairs must be equal 239 | """ 240 | pair = CurrencyPair("XRP", "BTC") 241 | second_pair = CurrencyPair("XRP", "BTC") 242 | first_hash = hash(pair) 243 | 244 | with self.assertRaises(AttributeError): 245 | pair.base = "ETH" 246 | 247 | self.assertEqual(hash(pair), first_hash) 248 | 249 | self.assertEqual(pair, second_pair) 250 | 251 | self.assertEqual(hash(pair), hash(second_pair)) 252 | 253 | def test_recipe_step_to_recipe(self): 254 | """Test that a RecipeStep can be turned into a correct Recipe""" 255 | step = RecipeStep("XLM", "ETH", False) 256 | recipe = step.as_recipe() 257 | expected_recipe = Recipe(1, [RecipeStep("XLM", "ETH", False)]) 258 | 259 | self.assertEqual(recipe, expected_recipe) 260 | 261 | def test_recipe_step_reversal(self): 262 | """Reversing a RecipeStep should invert the reciprocal and leave currency 263 | ordering intact 264 | """ 265 | 266 | step = RecipeStep("ZEC", "BNB", False) 267 | expected_reversed = RecipeStep("ZEC", "BNB", True) 268 | self.assertEqual(step.reversed(), expected_reversed) 269 | 270 | def test_add_recipe_steps(self): 271 | """Adding two recipe steps should result in a Recipe with correct order""" 272 | step_one = RecipeStep("ZEC", "BNB", False) 273 | step_two = RecipeStep("BNB", "BTC", False) 274 | expected_recipe = Recipe( 275 | 2, [RecipeStep("ZEC", "BNB", False), RecipeStep("BNB", "BTC", False)] 276 | ) 277 | 278 | self.assertEqual(step_one + step_two, expected_recipe) 279 | 280 | def test_add_recipe_step_to_recipe(self): 281 | """Adding a RecipeStep to a Recipe should put the step in the correct location 282 | and increment the number of steps 283 | """ 284 | 285 | step = RecipeStep("ZEC", "BNB", False) 286 | recipe = Recipe( 287 | 2, [RecipeStep("BNB", "BTC", False), RecipeStep("BTC", "USD", False)] 288 | ) 289 | 290 | # RecipeStep + Recipe 291 | expected_add_right = Recipe( 292 | 3, 293 | [ 294 | RecipeStep("ZEC", "BNB", False), 295 | RecipeStep("BNB", "BTC", False), 296 | RecipeStep("BTC", "USD", False), 297 | ], 298 | ) 299 | 300 | # Recipe + RecipeStep 301 | expected_add_left = Recipe( 302 | 3, 303 | [ 304 | RecipeStep("BNB", "BTC", False), 305 | RecipeStep("BTC", "USD", False), 306 | RecipeStep("ZEC", "BNB", False), 307 | ], 308 | ) 309 | 310 | self.assertEqual(step + recipe, expected_add_right) 311 | self.assertEqual(recipe + step, expected_add_left) 312 | 313 | def test_recipe_comparisons(self): 314 | """Test comparison operators on Recipes. GT/LT are based on number of steps. 315 | Equality requires same number of steps and same list (including ordering) of 316 | each step 317 | """ 318 | 319 | smaller_recipe = Recipe( 320 | 2, [RecipeStep("BNB", "BTC", False), RecipeStep("BTC", "USD", False)] 321 | ) 322 | 323 | bigger_recipe = Recipe( 324 | 3, 325 | [ 326 | RecipeStep("BNB", "BTC", False), 327 | RecipeStep("BTC", "USD", False), 328 | RecipeStep("ZEC", "BNB", False), 329 | ], 330 | ) 331 | bigger_recipe_different_order = Recipe( 332 | 3, 333 | [ 334 | RecipeStep("BTC", "USD", False), 335 | RecipeStep("BNB", "BTC", False), 336 | RecipeStep("ZEC", "BNB", False), 337 | ], 338 | ) 339 | 340 | self.assertLess(smaller_recipe, bigger_recipe) 341 | self.assertGreater(bigger_recipe, smaller_recipe) 342 | self.assertNotEqual(bigger_recipe, bigger_recipe_different_order) 343 | 344 | def test_add_recipe_to_recipe(self): 345 | """Recipe + Recipe should give a new recipe with steps in order""" 346 | recipe_one = Recipe( 347 | 2, [RecipeStep("BNB", "BTC", False), RecipeStep("BTC", "USD", False)] 348 | ) 349 | recipe_two = Recipe( 350 | 3, 351 | [ 352 | RecipeStep("BNB", "BTC", False), 353 | RecipeStep("BTC", "USD", False), 354 | RecipeStep("ZEC", "BNB", False), 355 | ], 356 | ) 357 | 358 | # one + two 359 | expected_add_right = Recipe( 360 | 5, 361 | [ 362 | RecipeStep("BNB", "BTC", False), # recipe_one 363 | RecipeStep("BTC", "USD", False), # recipe_one 364 | RecipeStep("BNB", "BTC", False), # recipe_two 365 | RecipeStep("BTC", "USD", False), # recipe_two 366 | RecipeStep("ZEC", "BNB", False), # recipe_two 367 | ], 368 | ) 369 | 370 | # two + one 371 | expected_add_left = Recipe( 372 | 5, 373 | [ 374 | RecipeStep("BNB", "BTC", False), # recipe_two 375 | RecipeStep("BTC", "USD", False), # recipe_two 376 | RecipeStep("ZEC", "BNB", False), # recipe_two 377 | RecipeStep("BNB", "BTC", False), # recipe_one 378 | RecipeStep("BTC", "USD", False), # recipe_one 379 | ], 380 | ) 381 | 382 | self.assertEqual(recipe_one + recipe_two, expected_add_right) 383 | self.assertEqual(recipe_two + recipe_one, expected_add_left) 384 | 385 | 386 | if __name__ == "__main__": 387 | unittest.main() 388 | -------------------------------------------------------------------------------- /examples/example_windows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # 4 | # ---------------------------------------------------------------------- 5 | # ccGains - Create capital gains reports for cryptocurrency trading. 6 | # Copyright (C) 2017 Jürgen Probst 7 | # 8 | # This file is part of ccGains. 9 | # 10 | # ccGains is free software: you can redistribute it and/or modify it 11 | # under the terms of the GNU Lesser General Public License as published 12 | # by the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # ccGains is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public 21 | # License along with ccGains. If not, see . 22 | # ---------------------------------------------------------------------- 23 | # 24 | # Get the latest version at: https://github.com/probstj/ccGains 25 | # 26 | 27 | # import the library 28 | import ccgains 29 | 30 | ######################################## 31 | ####### Calculate taxable profit ####### 32 | ######################################## 33 | 34 | def main(): 35 | ######################################################################### 36 | # 1. Provide list of BTC historical prices in your native fiat currency # 37 | ######################################################################### 38 | 39 | # Hourly data for a lot of exchanges is available for download at: 40 | # https://api.bitcoincharts.com/v1/csv/ 41 | # To understand which file to download, consult this list: 42 | # https://bitcoincharts.com/markets/list/ 43 | 44 | # E.g., for EUR prices on Bitcoin.de, download: 45 | # https://api.bitcoincharts.com/v1/csv/btcdeEUR.csv.gz 46 | # and place it in the ../data folder. 47 | 48 | # The file consists of three comma-separated columns: 49 | # the unix timestamp, the price, and the volume (amount traded). 50 | 51 | # Create the HistoricData object by loading the mentioned file and 52 | # specifying the price unit, i.e. fiat/btc: 53 | h1 = ccgains.HistoricDataCSV( 54 | '../data/bitcoin_de_EUR_abridged_as_example.csv.gz', 'EUR/BTC') 55 | 56 | # (Note: the abridged file provided with the source code on Github only 57 | # covers a small time range - please download the full file yourself) 58 | 59 | ######################################################################### 60 | # 2. Provide source of historical BTC prices for all traded alt coins # 61 | ######################################################################### 62 | 63 | # For all coins that you possessed at some point, their historical price 64 | # in your native fiat currency must be known, which can also be derived 65 | # from their BTC price and the BTC/fiat price given above (or even from 66 | # their price in any other alt-coin, whose price can be derived, in 67 | # turn.) 68 | 69 | # This data can be provided from any website that serves this data 70 | # through an API, or from a csv-file, like above. Note that currently, 71 | # only the API from Poloniex.com is implemented. 72 | 73 | # Create HistoricData objects to fetch rates from Poloniex.com: 74 | # (it is important to mention at least all traded coins here) 75 | h2 = ccgains.HistoricDataAPI('../data', 'btc/xmr') 76 | h3 = ccgains.HistoricDataAPI('../data', 'btc/eth') 77 | h4 = ccgains.HistoricDataAPI('../data', 'btc/usdt') 78 | h5 = ccgains.HistoricDataAPI('../data', 'btc/pasc') 79 | h6 = ccgains.HistoricDataAPI('../data', 'btc/gnt') 80 | 81 | 82 | ######################################################################### 83 | # 3. Add all objects from above into a single 'CurrencyRelation' object # 84 | ######################################################################### 85 | 86 | # Create a CurrencyRelation object that puts all provided HistoricData 87 | # currencies in relation in order to serve exchange rates for any pair 88 | # of these currencies: 89 | rel = ccgains.CurrencyRelation(h1, h2, h3, h4, h5, h6) 90 | 91 | 92 | ######################################################################### 93 | # 4. Create the 'BagQueue', which calculates the capital gains # 94 | ######################################################################### 95 | 96 | # Create the BagQueue object that calculates taxable profit from trades 97 | # using the first-in/first-out method: 98 | # (this needs to know your native fiat currency and the CurrencyRelation 99 | # created above) 100 | bf = ccgains.BagQueue('EUR', rel) 101 | 102 | 103 | ######################################################################### 104 | # 5. Create the object that will load all your trades # 105 | ######################################################################### 106 | 107 | # The TradeHistory object provides methods to load your trades from 108 | # csv-files exported from various exchanges or apps. 109 | th = ccgains.TradeHistory() 110 | 111 | 112 | ######################################################################### 113 | # 6. Load all your trades from csv-files # 114 | ######################################################################### 115 | 116 | # Export your trades from exchanges or apps as comma-separated files 117 | # and append them to the list of trades managed by the TradeHistory 118 | # object. All trades will be sorted automatically. 119 | 120 | # To load from a supported exchange, use the methods named 121 | # `append__csv` found in TradeHistory (see trades.py). 122 | # If your exchange is not supported yet, please file a new issue 123 | # on the GitHub repo https://github.com/probstj/ccgains/issues together 124 | # with a short example csv exported from your exchange and I will add 125 | # a method to import it. 126 | 127 | # Append from a Bitcoin.de csv: 128 | th.append_bitcoin_de_csv( 129 | './example_csv/bitcoin.de_account_statement_2017_fabricated.csv') 130 | 131 | # Append trades from Poloniex. Deposits, withdrawals and trades are 132 | # exported separately by Poloniex, import each one separately: 133 | th.append_poloniex_csv( 134 | './example_csv/poloniex_depositHistory_2017_fabricated.csv', 135 | 'deposits') 136 | th.append_poloniex_csv( 137 | './example_csv/poloniex_tradeHistory_2017_fabricated.csv', 138 | 'trades', 139 | condense_trades=True) 140 | th.append_poloniex_csv( 141 | './example_csv/poloniex_withdrawalHistory_2017_fabricated.csv', 142 | 'withdr') 143 | 144 | # From Bisq (formerly Bitsquare), we need the exported trade history csv 145 | # and the transaction history csv: 146 | th.append_bitsquare_csv( 147 | './example_csv/bisq_trades_2017_fabricated.csv', 148 | './example_csv/bisq_transactions_2017_fabricated.csv') 149 | 150 | #th.append_monero_wallet_csv('./csv/xmr_wallet_show_transfers') 151 | 152 | 153 | ######################################################################### 154 | # 7. Optionally, fix withdrawal fees # 155 | ######################################################################### 156 | 157 | # Some exchanges, like Poloniex, does not include withdrawal fees in 158 | # their exported csv files. This will try to add these missing fees 159 | # by comparing withdrawn amounts with amounts deposited on other 160 | # exchanges shortly after withdrawal. Call this only after all 161 | # transactions from every involved exchange and wallet were imported. 162 | 163 | # This uses a really simple algorithm, so it is not guaranteed to 164 | # work in every case, especially if you made withdrawals in tight 165 | # succession on different exchanges, so please check the output. 166 | 167 | th.add_missing_transaction_fees(raise_on_error=False) 168 | 169 | 170 | ######################################################################### 171 | # 8. Optionally, export all trades for future reference # 172 | ######################################################################### 173 | 174 | # You can export all imported trades for future reference into a single 175 | # file, optionally filtered by year. 176 | 177 | # ...either as a comma-separated text file (can be imported into ccgains): 178 | th.export_to_csv('transactions2017.csv', year=2017) 179 | 180 | # ...or as html or pdf file, with the possibility to filter or rename 181 | # column headers or contents: 182 | # (This is an example for a translation into German) 183 | my_column_names=[ 184 | 'Art', 'Datum', 'Kaufmenge', 'Verkaufsmenge', u'Gebühren', u'Börse'] 185 | transdct = {'Purchase': 'Anschaffung', 186 | 'Exchange': 'Tausch', 'Disbursement': 'Abhebung', 187 | 'Deposit': 'Einzahlung', 'Withdrawal': 'Abhebung', 188 | 'Received funds': 'Einzahlung', 189 | 'Withdrawn from wallet': 'Abhebung', 190 | 'Create offer fee: a5ed7482': u'Börsengebühr', 191 | 'Buy BTC' : 'Anschaffung', 192 | 'MultiSig deposit: a5ed7482': 'Abhebung', 193 | 'MultiSig payout: a5ed7482' : 'Einzahlung'} 194 | th.export_to_pdf('Transactions2017.pdf', 195 | year=2017, drop_columns=['mark', 'comment'], 196 | font_size=12, 197 | caption=u"Handel mit digitalen Währungen %(year)s", 198 | intro=u"

Auflistung aller Transaktionen zwischen " 199 | "%(fromdate)s und %(todate)s:

", 200 | locale="de_DE", 201 | custom_column_names=my_column_names, 202 | custom_formatters={ 203 | 'Art': lambda x: transdct[x] if x in transdct else x}) 204 | 205 | 206 | ######################################################################### 207 | # 9. Now, finally, the calculation is ready to start # 208 | ######################################################################### 209 | 210 | # If the calculation run for previous years already, we can load the state 211 | # of the bags here, no need to calculate everything again: 212 | # bf.load('./status2016.json') 213 | 214 | # Or, if the current calculation crashed (e.g. you forgot to add a traded 215 | # currency in #2 above), the file 'precrash.json' will be created 216 | # automatically. Load it here to continue: 217 | # bf.load('./precrash.json') 218 | 219 | # The following just looks where to start calculating trades, in case you 220 | # already calculated some and restarted by loading 'precrash.json': 221 | last_trade = 0 222 | while (last_trade < len(th.tlist) 223 | and th[last_trade].dtime <= bf._last_date): 224 | last_trade += 1 225 | if last_trade > 0: 226 | logger.info("continuing with trade #%i" % (last_trade + 1)) 227 | 228 | # Now, the calculation. This goes through your imported list of trades: 229 | for i, trade in enumerate(th.tlist[last_trade:]): 230 | # Most of this is just the log output to the console and to the 231 | # file 'ccgains_.log' 232 | # (check out this file for all gory calculation details!): 233 | logger.info('TRADE #%i', i + last_trade + 1) 234 | logger.info(trade) 235 | # This is the important part: 236 | bf.process_trade(trade) 237 | # more logging: 238 | log_bags(bf) 239 | logger.info("Totals: %s", str(bf.totals)) 240 | logger.info("Gains (in %s): %s\n" % (bf.currency, str(bf.profit))) 241 | 242 | 243 | ######################################################################### 244 | # 10. Save the state of your holdings for the calculation due next year # 245 | ######################################################################### 246 | 247 | bf.save('status2017.json') 248 | 249 | 250 | ######################################################################### 251 | # 11. Create your capital gains report for cryptocurrency trades # 252 | ######################################################################### 253 | 254 | # The default column names used in the report don't look very nice: 255 | # ['kind', 'bag_spent', 'currency', 'bag_date', 'sell_date', 256 | # 'exchange', 'short_term', 'spent_cost', 'proceeds', 'profit'], 257 | # so we rename them: 258 | my_column_names=[ 259 | 'Type', 'Amount spent', u'Currency', 'Purchase date', 260 | 'Sell date', u'Exchange', u'Short term', 'Purchase cost', 261 | 'Proceeds', 'Profit'] 262 | 263 | # Here we create the report pdf for capital gains in 2017. 264 | # The date_precision='D' means we only mention the day of the trade, not 265 | # the precise time. We also set combine=True, so multiple trades made on 266 | # the same day and on the same exchange are combined into a single trade 267 | # on the report: 268 | bf.report.export_report_to_pdf( 269 | 'Report2017.pdf', year=2017, 270 | date_precision='D', combine=True, 271 | custom_column_names=my_column_names, 272 | locale="en_US" 273 | ) 274 | 275 | # And now, let's translate it to German, by using a different 276 | # html template file: 277 | my_column_names=[ 278 | 'Art', 'Verkaufsmenge', u'Währung', 'Erwerbsdatum', 279 | 'Verkaufsdatum', u'Börse', u'in\xa0Besitz', 280 | 'Anschaffungskosten', u'Verkaufserlös', 'Gewinn'] 281 | transdct = {'sale': u'Veräußerung', 282 | 'withdrawal fee': u'Börsengebühr', 283 | 'deposit fee': u'Börsengebühr', 284 | 'exchange fee': u'Börsengebühr'} 285 | convert_short_term=[u'>\xa01\xa0Jahr', u'<\xa01\xa0Jahr'] 286 | 287 | bf.report.export_report_to_pdf( 288 | 'Report2017_de.pdf', year=2017, 289 | date_precision='D', combine=True, 290 | custom_column_names=my_column_names, 291 | custom_formatters={ 292 | u'in\xa0Besitz': lambda b: convert_short_term[b], 293 | 'Art': lambda x: transdct[x]}, 294 | locale="de_DE", 295 | template_file='shortreport_de.html' 296 | ) 297 | 298 | # If you rather want your report in a spreadsheet, you can export 299 | # to csv: 300 | bf.report.export_short_report_to_csv( 301 | 'report_2017.csv', year=2017, 302 | date_precision='D', combine=False, 303 | convert_timezone=True, strip_timezone=True) 304 | 305 | 306 | ######################################################################### 307 | # 12. Optional: Create a detailed report outlining the calculation # 308 | ######################################################################### 309 | 310 | # The simple capital gains report created above is just a plain listing 311 | # of all trades and the gains made, enough for the tax report. 312 | # A more detailed listing outlining the calculation is also available: 313 | 314 | bf.report.export_extended_report_to_pdf( 315 | 'Details_2017.pdf', year=2017, 316 | date_precision='S', combine=False, 317 | font_size=10, locale="en_US") 318 | 319 | # And again, let's translate this report to German: 320 | # (Using transdct from above again to translate the payment kind) 321 | bf.report.export_extended_report_to_pdf( 322 | 'Details_2017_de.pdf', year=2017, 323 | date_precision='S', combine=False, 324 | font_size=10, locale="de_DE", 325 | template_file='fullreport_de.html', 326 | payment_kind_translation=transdct) 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | ####################################################### 335 | ### Setup logger ### 336 | ####################################################### 337 | 338 | # Don't worry about the following. It just sets up the logger, 339 | # which manages output and prints to the file 340 | # "ccgains_.log". 341 | 342 | import logging, time, sys 343 | 344 | logger = logging.getLogger('ccgains') 345 | logger.setLevel(logging.DEBUG) 346 | # This is my highest logger, don't propagate to root logger: 347 | logger.propagate = 0 348 | # Reset logger in case any handlers were already added: 349 | for h in logger.handlers[::-1]: 350 | h.close() 351 | logger.removeHandler(h) 352 | # Create file handler which logs even debug messages 353 | fname = 'ccgains_%s.log' % time.strftime("%Y%m%d-%H%M%S") 354 | fh = logging.FileHandler(fname, mode='w') 355 | fh.setLevel(logging.DEBUG) 356 | # Create console handler for debugging: 357 | ch = logging.StreamHandler(stream=sys.stdout) 358 | ch.setLevel(logging.DEBUG) 359 | # Create formatters and add them to the handlers 360 | fhformatter = logging.Formatter( 361 | '%(asctime)s %(levelname)-8s - %(module)13s -> %(funcName)-13s: ' 362 | '%(message)s') 363 | chformatter = logging.Formatter('%(levelname)-8s: %(message)s') 364 | #fh.setFormatter(fhformatter) 365 | fh.setFormatter(chformatter) 366 | ch.setFormatter(chformatter) 367 | # Add the handlers to the logger 368 | logger.addHandler(fh) 369 | logger.addHandler(ch) 370 | 371 | def log_bags(bags): 372 | logger.info("State of bags: \n%s\n", 373 | ' ' + '\n '.join(str(bags).split('\n'))) 374 | 375 | 376 | # run the main() function above: 377 | if __name__ == "__main__": 378 | main() 379 | 380 | -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # 4 | # ---------------------------------------------------------------------- 5 | # ccGains - Create capital gains reports for cryptocurrency trading. 6 | # Copyright (C) 2017 Jürgen Probst 7 | # 8 | # This file is part of ccGains. 9 | # 10 | # ccGains is free software: you can redistribute it and/or modify it 11 | # under the terms of the GNU Lesser General Public License as published 12 | # by the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # ccGains is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public 21 | # License along with ccGains. If not, see . 22 | # ---------------------------------------------------------------------- 23 | # 24 | # Get the latest version at: https://github.com/probstj/ccGains 25 | # 26 | 27 | # import the library 28 | import ccgains 29 | 30 | ######################################## 31 | ####### Calculate taxable profit ####### 32 | ######################################## 33 | 34 | def main(): 35 | ######################################################################### 36 | # 1. Provide list of BTC historical prices in your native fiat currency # 37 | ######################################################################### 38 | 39 | # Hourly data for a lot of exchanges is available for download at: 40 | # https://api.bitcoincharts.com/v1/csv/ 41 | # To understand which file to download, consult this list: 42 | # https://bitcoincharts.com/markets/list/ 43 | 44 | # E.g., for EUR prices on Bitcoin.de, download: 45 | # https://api.bitcoincharts.com/v1/csv/btcdeEUR.csv.gz 46 | # and place it in the ../data folder. 47 | 48 | # The file consists of three comma-separated columns: 49 | # the unix timestamp, the price, and the volume (amount traded). 50 | 51 | # Create the HistoricData object by loading the mentioned file and 52 | # specifying the price unit, i.e. fiat/btc: 53 | h1 = ccgains.HistoricDataCSV( 54 | '../data/bitcoin_de_EUR_abridged_as_example.csv.gz', 'EUR/BTC') 55 | 56 | # (Note: the abridged file provided with the source code on Github only 57 | # covers a small time range - please download the full file yourself) 58 | 59 | ######################################################################### 60 | # 2. Provide source of historical BTC prices for all traded alt coins # 61 | ######################################################################### 62 | 63 | # For all coins that you possessed at some point, their historical price 64 | # in your native fiat currency must be known, which can also be derived 65 | # from their BTC price and the BTC/fiat price given above (or even from 66 | # their price in any other alt-coin, whose price can be derived, in 67 | # turn.) 68 | 69 | # This data can be provided from any website that serves this data 70 | # through an API, or from a csv-file, like above. Note that currently, 71 | # only the API from Poloniex.com is implemented. 72 | 73 | # Create HistoricData objects to fetch rates from Poloniex.com: 74 | # (it is important to mention at least all traded coins here) 75 | h2 = ccgains.HistoricDataAPI('../data', 'btc/xmr') 76 | h3 = ccgains.HistoricDataAPI('../data', 'btc/eth') 77 | h4 = ccgains.HistoricDataAPI('../data', 'btc/usdt') 78 | h5 = ccgains.HistoricDataAPI('../data', 'btc/pasc') 79 | h6 = ccgains.HistoricDataAPI('../data', 'btc/gnt') 80 | 81 | 82 | ######################################################################### 83 | # 3. Add all objects from above into a single 'CurrencyRelation' object # 84 | ######################################################################### 85 | 86 | # Create a CurrencyRelation object that puts all provided HistoricData 87 | # currencies in relation in order to serve exchange rates for any pair 88 | # of these currencies: 89 | rel = ccgains.CurrencyRelation(h1, h2, h3, h4, h5, h6) 90 | 91 | 92 | ######################################################################### 93 | # 4. Create the 'BagQueue', which calculates the capital gains # 94 | ######################################################################### 95 | 96 | # Create the BagQueue object that calculates taxable profit from trades 97 | # using the first-in/first-out method: 98 | # (this needs to know your native fiat currency and the CurrencyRelation 99 | # created above) 100 | bf = ccgains.BagQueue('EUR', rel) 101 | 102 | 103 | ######################################################################### 104 | # 5. Create the object that will load all your trades # 105 | ######################################################################### 106 | 107 | # The TradeHistory object provides methods to load your trades from 108 | # csv-files exported from various exchanges or apps. 109 | th = ccgains.TradeHistory() 110 | 111 | 112 | ######################################################################### 113 | # 6. Load all your trades from csv-files # 114 | ######################################################################### 115 | 116 | # Export your trades from exchanges or apps as comma-separated files 117 | # and append them to the list of trades managed by the TradeHistory 118 | # object. All trades will be sorted automatically. 119 | 120 | # To load from a supported exchange, use the methods named 121 | # `append__csv` found in TradeHistory (see trades.py). 122 | # If your exchange is not supported yet, please file a new issue 123 | # on the GitHub repo https://github.com/probstj/ccgains/issues together 124 | # with a short example csv exported from your exchange and I will add 125 | # a method to import it. 126 | 127 | # Append from a Bitcoin.de csv: 128 | th.append_bitcoin_de_csv( 129 | './example_csv/bitcoin.de_account_statement_2017_fabricated.csv') 130 | 131 | # Append trades from Poloniex. Deposits, withdrawals and trades are 132 | # exported separately by Poloniex, import each one separately: 133 | th.append_poloniex_csv( 134 | './example_csv/poloniex_depositHistory_2017_fabricated.csv', 135 | 'deposits') 136 | th.append_poloniex_csv( 137 | './example_csv/poloniex_tradeHistory_2017_fabricated.csv', 138 | 'trades', 139 | condense_trades=True) 140 | th.append_poloniex_csv( 141 | './example_csv/poloniex_withdrawalHistory_2017_fabricated.csv', 142 | 'withdr') 143 | 144 | # From Bisq (formerly Bitsquare), we need the exported trade history csv 145 | # and the transaction history csv: 146 | th.append_bitsquare_csv( 147 | './example_csv/bisq_trades_2017_fabricated.csv', 148 | './example_csv/bisq_transactions_2017_fabricated.csv') 149 | 150 | #th.append_monero_wallet_csv('./csv/xmr_wallet_show_transfers') 151 | 152 | 153 | ######################################################################### 154 | # 7. Optionally, fix withdrawal fees # 155 | ######################################################################### 156 | 157 | # Some exchanges, like Poloniex, does not include withdrawal fees in 158 | # their exported csv files. This will try to add these missing fees 159 | # by comparing withdrawn amounts with amounts deposited on other 160 | # exchanges shortly after withdrawal. Call this only after all 161 | # transactions from every involved exchange and wallet were imported. 162 | 163 | # This uses a really simple algorithm, so it is not guaranteed to 164 | # work in every case, especially if you made withdrawals in tight 165 | # succession on different exchanges, so please check the output. 166 | 167 | th.add_missing_transaction_fees(raise_on_error=False) 168 | 169 | 170 | ######################################################################### 171 | # 8. Optionally, rename currencies # 172 | ######################################################################### 173 | 174 | # Some currencies have changed ticker symbols since their first listing 175 | # date (e.g., AntShares (ANS) -> Neo (NEO)). This can lead to situations 176 | # where all historical pricing data lists the new ticker symbol, but 177 | # transaction history still lists the old ticker. 178 | 179 | # This method allows for renaming symbols in the TradeHistory, if any 180 | # occurrences of the old name/ticker are found. 181 | 182 | th.update_ticker_names({'ANS': 'NEO'}) 183 | 184 | ######################################################################### 185 | # 9. Optionally, export all trades for future reference # 186 | ######################################################################### 187 | 188 | # You can export all imported trades for future reference into a single 189 | # file, optionally filtered by year. 190 | 191 | # ...either as a comma-separated text file (can be imported into ccgains): 192 | th.export_to_csv('transactions2017.csv', year=2017) 193 | 194 | # ...or as html or pdf file, with the possibility to filter or rename 195 | # column headers or contents: 196 | # (This is an example for a translation into German) 197 | my_column_names=[ 198 | 'Art', 'Datum', 'Kaufmenge', 'Verkaufsmenge', u'Gebühren', u'Börse'] 199 | transdct = {'Purchase': 'Anschaffung', 200 | 'Exchange': 'Tausch', 'Disbursement': 'Abhebung', 201 | 'Deposit': 'Einzahlung', 'Withdrawal': 'Abhebung', 202 | 'Received funds': 'Einzahlung', 203 | 'Withdrawn from wallet': 'Abhebung', 204 | 'Create offer fee: a5ed7482': u'Börsengebühr', 205 | 'Buy BTC' : 'Anschaffung', 206 | 'MultiSig deposit: a5ed7482': 'Abhebung', 207 | 'MultiSig payout: a5ed7482' : 'Einzahlung'} 208 | th.export_to_pdf('Transactions2017.pdf', 209 | year=2017, drop_columns=['mark', 'comment'], 210 | font_size=12, 211 | caption=u"Handel mit digitalen Währungen %(year)s", 212 | intro=u"

Auflistung aller Transaktionen zwischen " 213 | "%(fromdate)s und %(todate)s:

", 214 | locale="de_DE", 215 | custom_column_names=my_column_names, 216 | custom_formatters={ 217 | 'Art': lambda x: transdct[x] if x in transdct else x}) 218 | 219 | 220 | ######################################################################### 221 | # 10. Now, finally, the calculation is ready to start # 222 | ######################################################################### 223 | 224 | # If the calculation run for previous years already, we can load the state 225 | # of the bags here, no need to calculate everything again: 226 | # bf.load('./status2016.json') 227 | 228 | # Or, if the current calculation crashed (e.g. you forgot to add a traded 229 | # currency in #2 above), the file 'precrash.json' will be created 230 | # automatically. Load it here to continue: 231 | # bf.load('./precrash.json') 232 | 233 | # The following just looks where to start calculating trades, in case you 234 | # already calculated some and restarted by loading 'precrash.json': 235 | last_trade = 0 236 | while (last_trade < len(th.tlist) 237 | and th[last_trade].dtime <= bf._last_date): 238 | last_trade += 1 239 | if last_trade > 0: 240 | logger.info("continuing with trade #%i" % (last_trade + 1)) 241 | 242 | # Now, the calculation. This goes through your imported list of trades: 243 | for i, trade in enumerate(th.tlist[last_trade:]): 244 | # Most of this is just the log output to the console and to the 245 | # file 'ccgains_.log' 246 | # (check out this file for all gory calculation details!): 247 | logger.info('TRADE #%i', i + last_trade + 1) 248 | logger.info(trade) 249 | # This is the important part: 250 | bf.process_trade(trade) 251 | # more logging: 252 | log_bags(bf) 253 | logger.info("Totals: %s", str(bf.totals)) 254 | logger.info("Gains (in %s): %s\n" % (bf.currency, str(bf.profit))) 255 | 256 | 257 | ######################################################################### 258 | # 11. Save the state of your holdings for the calculation due next year # 259 | ######################################################################### 260 | 261 | bf.save('status2017.json') 262 | 263 | 264 | ######################################################################### 265 | # 12. Create your capital gains report for cryptocurrency trades # 266 | ######################################################################### 267 | 268 | # The default column names used in the report don't look very nice: 269 | # ['kind', 'bag_spent', 'currency', 'bag_date', 'sell_date', 270 | # 'exchange', 'short_term', 'spent_cost', 'proceeds', 'profit'], 271 | # so we rename them: 272 | my_column_names=[ 273 | 'Type', 'Amount spent', u'Currency', 'Purchase date', 274 | 'Sell date', u'Exchange', u'Short term', 'Purchase cost', 275 | 'Proceeds', 'Profit'] 276 | 277 | # Here we create the report pdf for capital gains in 2017. 278 | # The date_precision='D' means we only mention the day of the trade, not 279 | # the precise time. We also set combine=True, so multiple trades made on 280 | # the same day and on the same exchange are combined into a single trade 281 | # on the report: 282 | bf.report.export_report_to_pdf( 283 | 'Report2017.pdf', year=2017, 284 | date_precision='D', combine=True, 285 | custom_column_names=my_column_names, 286 | locale="en_US" 287 | ) 288 | 289 | # And now, let's translate it to German, by using a different 290 | # html template file: 291 | my_column_names=[ 292 | 'Art', 'Verkaufsmenge', u'Währung', 'Erwerbsdatum', 293 | 'Verkaufsdatum', u'Börse', u'in\xa0Besitz', 294 | 'Anschaffungskosten', u'Verkaufserlös', 'Gewinn'] 295 | transdct = {'sale': u'Veräußerung', 296 | 'withdrawal fee': u'Börsengebühr', 297 | 'deposit fee': u'Börsengebühr', 298 | 'exchange fee': u'Börsengebühr'} 299 | convert_short_term=[u'>\xa01\xa0Jahr', u'<\xa01\xa0Jahr'] 300 | 301 | bf.report.export_report_to_pdf( 302 | 'Report2017_de.pdf', year=2017, 303 | date_precision='D', combine=True, 304 | custom_column_names=my_column_names, 305 | custom_formatters={ 306 | u'in\xa0Besitz': lambda b: convert_short_term[b], 307 | 'Art': lambda x: transdct[x]}, 308 | locale="de_DE", 309 | template_file='shortreport_de.html' 310 | ) 311 | 312 | # If you rather want your report in a spreadsheet, you can export 313 | # to csv: 314 | bf.report.export_short_report_to_csv( 315 | 'report_2017.csv', year=2017, 316 | date_precision='D', combine=False, 317 | convert_timezone=True, strip_timezone=True) 318 | 319 | 320 | ######################################################################### 321 | # 13. Optional: Create a detailed report outlining the calculation # 322 | ######################################################################### 323 | 324 | # The simple capital gains report created above is just a plain listing 325 | # of all trades and the gains made, enough for the tax report. 326 | # A more detailed listing outlining the calculation is also available: 327 | 328 | bf.report.export_extended_report_to_pdf( 329 | 'Details_2017.pdf', year=2017, 330 | date_precision='S', combine=False, 331 | font_size=10, locale="en_US") 332 | 333 | # And again, let's translate this report to German: 334 | # (Using transdct from above again to translate the payment kind) 335 | bf.report.export_extended_report_to_pdf( 336 | 'Details_2017_de.pdf', year=2017, 337 | date_precision='S', combine=False, 338 | font_size=10, locale="de_DE", 339 | template_file='fullreport_de.html', 340 | payment_kind_translation=transdct) 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | ####################################################### 349 | ### Setup logger ### 350 | ####################################################### 351 | 352 | # Don't worry about the following. It just sets up the logger, 353 | # which manages output and prints to the file 354 | # "ccgains_.log". 355 | 356 | import logging, time, sys 357 | 358 | logger = logging.getLogger('ccgains') 359 | logger.setLevel(logging.DEBUG) 360 | # This is my highest logger, don't propagate to root logger: 361 | logger.propagate = 0 362 | # Reset logger in case any handlers were already added: 363 | for h in logger.handlers[::-1]: 364 | h.close() 365 | logger.removeHandler(h) 366 | # Create file handler which logs even debug messages 367 | fname = 'ccgains_%s.log' % time.strftime("%Y%m%d-%H%M%S") 368 | fh = logging.FileHandler(fname, mode='w') 369 | fh.setLevel(logging.DEBUG) 370 | # Create console handler for debugging: 371 | ch = logging.StreamHandler(stream=sys.stdout) 372 | ch.setLevel(logging.DEBUG) 373 | # Create formatters and add them to the handlers 374 | fhformatter = logging.Formatter( 375 | '%(asctime)s %(levelname)-8s - %(module)13s -> %(funcName)-13s: ' 376 | '%(message)s') 377 | chformatter = logging.Formatter('%(levelname)-8s: %(message)s') 378 | #fh.setFormatter(fhformatter) 379 | fh.setFormatter(chformatter) 380 | ch.setFormatter(chformatter) 381 | # Add the handlers to the logger 382 | logger.addHandler(fh) 383 | logger.addHandler(ch) 384 | 385 | def log_bags(bags): 386 | logger.info("State of bags: \n%s\n", 387 | ' ' + '\n '.join(str(bags).split('\n'))) 388 | 389 | 390 | # run the main() function above: 391 | if __name__ == "__main__": 392 | main() 393 | 394 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ccGains 2 | 3 | The ccGains (cryptocurrency gains) package provides a python library for calculating capital gains made by trading cryptocurrencies or foreign currencies. 4 | 5 | Some of its features are: 6 | 7 | - calculates the capital gains using the first-in/first-out (FIFO), last-in/first-out (LIFO), lowest-price/first-out (LPFO) principles, 8 | - creates capital gains reports as CSV, HTML or PDF (instantly ready to print out for the tax office), 9 | - can create a more detailed capital gains report outlining the calculation and used bags, 10 | - differs between short and long term gains (amounts held for less or more than a year), 11 | - treats amounts held and traded on different exchanges separately, 12 | - treats exchange fees and transaction fees directly resulting from trading properly as losses, 13 | - provides methods to import your trading history from various exchanges, 14 | - loads historic cryptocurrency prices from CSV files and/or 15 | - loads historic prices from APIs provided by exchanges, 16 | - caches historic price data on disk for quicker and offline retrieval and less traffic to exchanges, 17 | - for highest accuracy, uses the [decimal data type](https://docs.python.org/3/library/decimal.html) for all amounts 18 | - supports saving and loading the state of your portfolio as JSON file for use in ccGains calculations in following years 19 | 20 | Please have a look at some example PDF reports in `examples/example_output/`. 21 | 22 | 23 | ## Installation 24 | 25 | You'll need Python (ccGains is tested under Python 2.7 and Python 3.x). Get it here: https://www.python.org/ 26 | 27 | 28 | ccGains can then easily be installed via the Python package manager pip: 29 | 30 | - Download the source code, e.g. by `git clone https://github.com/probstj/ccgains.git` 31 | - Inside the main ccGains directory run: `pip install .` 32 | (note the `.` at the end) 33 | 34 | - Alternatively, to install locally without admin rights: `pip install --user .` 35 | - And if you want to add changes to the source code and quickly want to try it without reinstalling, pip can install by linking to the source code folder: `pip install -e .` 36 | 37 | 38 | ## Usage 39 | 40 | Please have a look at `examples/example.py` and follow the comments to adapt it for your purposes. The main part of the script is also included here, see 'Example' below... 41 | 42 | Then run the example with Python, e.g. with `python example.py`. 43 | 44 | A lot of functionality is still missing, most crucially, support for more exchanges. You can help improve ccGains - programming experience not needed: See below "Bug reports & feature requests". 45 | 46 | 47 | ## License 48 | 49 | Copyright (C) 2017 Jürgen Probst 50 | 51 | ccGains is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 52 | 53 | ccGains is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 54 | 55 | ## Help and support 56 | 57 | ### Documentation 58 | 59 | The ccGains documentation is available online at [Read the Docs](https://ccgains.readthedocs.io). You can also download a [PDF version](https://readthedocs.org/projects/ccgains/downloads/pdf/latest/). 60 | 61 | ### Bug reports & feature requests 62 | 63 | A lot of functionality is still missing, most crucially, support for more exchanges. If your exchange is not supported yet, please file a `New issue` on GitHub at https://github.com/probstj/ccgains/issues together with a short example CSV exported from your exchange and I will add a method to import it. 64 | 65 | ### Example 66 | An example script is included in the `example` subdirectory: `example.py` (or use `example_windows.py` on Windows; this is the same script, just with Windows' line endings so you can open it in any text editor). Follow the comments in the file to adapt the script for your purposes. 67 | 68 | All example output can already be viewed in `examples/example_output/`. 69 | 70 | Here is the main part of the example script copied verbatim: 71 | ```python 72 | import ccgains 73 | 74 | def main(): 75 | ######################################################################### 76 | # 1. Provide list of BTC historical prices in your native fiat currency # 77 | ######################################################################### 78 | 79 | # Hourly data for a lot of exchanges is available for download at: 80 | # https://api.bitcoincharts.com/v1/csv/ 81 | # To understand which file to download, consult this list: 82 | # https://bitcoincharts.com/markets/list/ 83 | 84 | # E.g., for EUR prices on Bitcoin.de, download: 85 | # https://api.bitcoincharts.com/v1/csv/btcdeEUR.csv.gz 86 | # and place it in the ../data folder. 87 | 88 | # The file consists of three comma-separated columns: 89 | # the unix timestamp, the price, and the volume (amount traded). 90 | 91 | # Create the HistoricData object by loading the mentioned file and 92 | # specifying the price unit, i.e. fiat/btc: 93 | h1 = ccgains.HistoricDataCSV( 94 | '../data/bitcoin_de_EUR_abridged_as_example.csv.gz', 'EUR/BTC') 95 | 96 | # (Note: the abridged file provided with the source code on Github only 97 | # covers a small time range - please download the full file yourself) 98 | 99 | ######################################################################### 100 | # 2. Provide source of historical BTC prices for all traded alt coins # 101 | ######################################################################### 102 | 103 | # For all coins that you possessed at some point, their historical price 104 | # in your native fiat currency must be known, which can also be derived 105 | # from their BTC price and the BTC/fiat price given above (or even from 106 | # their price in any other alt-coin, whose price can be derived, in 107 | # turn.) 108 | 109 | # This data can be provided from any website that serves this data 110 | # through an API, or from a csv-file, like above. Note that currently, 111 | # only the API from Poloniex.com is implemented. 112 | 113 | # Create HistoricData objects to fetch rates from Poloniex.com: 114 | # (it is important to mention at least all traded coins here) 115 | h2 = ccgains.HistoricDataAPI('../data', 'btc/xmr') 116 | h3 = ccgains.HistoricDataAPI('../data', 'btc/eth') 117 | h4 = ccgains.HistoricDataAPI('../data', 'btc/usdt') 118 | h5 = ccgains.HistoricDataAPI('../data', 'btc/pasc') 119 | h6 = ccgains.HistoricDataAPI('../data', 'btc/gnt') 120 | 121 | 122 | ######################################################################### 123 | # 3. Add all objects from above into a single 'CurrencyRelation' object # 124 | ######################################################################### 125 | 126 | # Create a CurrencyRelation object that puts all provided HistoricData 127 | # currencies in relation in order to serve exchange rates for any pair 128 | # of these currencies: 129 | rel = ccgains.CurrencyRelation(h1, h2, h3, h4, h5, h6) 130 | 131 | 132 | ######################################################################### 133 | # 4. Create the 'BagQueue', which calculates the capital gains # 134 | ######################################################################### 135 | 136 | # Create the BagQueue object that calculates taxable profit from trades 137 | # using the first-in/first-out method: 138 | # (this needs to know your native fiat currency and the CurrencyRelation 139 | # created above) 140 | bf = ccgains.BagQueue('EUR', rel) 141 | 142 | 143 | ######################################################################### 144 | # 5. Create the object that will load all your trades # 145 | ######################################################################### 146 | 147 | # The TradeHistory object provides methods to load your trades from 148 | # csv-files exported from various exchanges or apps. 149 | th = ccgains.TradeHistory() 150 | 151 | 152 | ######################################################################### 153 | # 6. Load all your trades from csv-files # 154 | ######################################################################### 155 | 156 | # Export your trades from exchanges or apps as comma-separated files 157 | # and append them to the list of trades managed by the TradeHistory 158 | # object. All trades will be sorted automatically. 159 | 160 | # To load from a supported exchange, use the methods named 161 | # `append__csv` found in TradeHistory (see trades.py). 162 | # If your exchange is not supported yet, please file a new issue 163 | # on the GitHub repo https://github.com/probstj/ccgains/issues together 164 | # with a short example csv exported from your exchange and I will add 165 | # a method to import it. 166 | 167 | # Append from a Bitcoin.de csv: 168 | th.append_bitcoin_de_csv( 169 | './example_csv/bitcoin.de_account_statement_2017_fabricated.csv') 170 | 171 | # Append trades from Poloniex. Deposits, withdrawals and trades are 172 | # exported separately by Poloniex, import each one separately: 173 | th.append_poloniex_csv( 174 | './example_csv/poloniex_depositHistory_2017_fabricated.csv', 175 | 'deposits') 176 | th.append_poloniex_csv( 177 | './example_csv/poloniex_tradeHistory_2017_fabricated.csv', 178 | 'trades', 179 | condense_trades=True) 180 | th.append_poloniex_csv( 181 | './example_csv/poloniex_withdrawalHistory_2017_fabricated.csv', 182 | 'withdr') 183 | 184 | # From Bisq (formerly Bitsquare), we need the exported trade history csv 185 | # and the transaction history csv: 186 | th.append_bitsquare_csv( 187 | './example_csv/bisq_trades_2017_fabricated.csv', 188 | './example_csv/bisq_transactions_2017_fabricated.csv') 189 | 190 | #th.append_monero_wallet_csv('./csv/xmr_wallet_show_transfers') 191 | 192 | 193 | ######################################################################### 194 | # 7. Optionally, fix withdrawal fees # 195 | ######################################################################### 196 | 197 | # Some exchanges, like Poloniex, does not include withdrawal fees in 198 | # their exported csv files. This will try to add these missing fees 199 | # by comparing withdrawn amounts with amounts deposited on other 200 | # exchanges shortly after withdrawal. Call this only after all 201 | # transactions from every involved exchange and wallet were imported. 202 | 203 | # This uses a really simple algorithm, so it is not guaranteed to 204 | # work in every case, especially if you made withdrawals in tight 205 | # succession on different exchanges, so please check the output. 206 | 207 | th.add_missing_transaction_fees(raise_on_error=False) 208 | 209 | 210 | ######################################################################### 211 | # 8. Optionally, export all trades for future reference # 212 | ######################################################################### 213 | 214 | # You can export all imported trades for future reference into a single 215 | # file, optionally filtered by year. 216 | 217 | # ...either as a comma-separated text file (can be imported into ccgains): 218 | th.export_to_csv('transactions2017.csv', year=2017) 219 | 220 | # ...or as html or pdf file, with the possibility to filter or rename 221 | # column headers or contents: 222 | # (This is an example for a translation into German) 223 | my_column_names=[ 224 | 'Art', 'Datum', 'Kaufmenge', 'Verkaufsmenge', u'Gebühren', u'Börse'] 225 | transdct = {'Purchase': 'Anschaffung', 226 | 'Exchange': 'Tausch', 'Disbursement': 'Abhebung', 227 | 'Deposit': 'Einzahlung', 'Withdrawal': 'Abhebung', 228 | 'Received funds': 'Einzahlung', 229 | 'Withdrawn from wallet': 'Abhebung', 230 | 'Create offer fee: a5ed7482': u'Börsengebühr', 231 | 'Buy BTC' : 'Anschaffung', 232 | 'MultiSig deposit: a5ed7482': 'Abhebung', 233 | 'MultiSig payout: a5ed7482' : 'Einzahlung'} 234 | th.export_to_pdf('Transactions2017.pdf', 235 | year=2017, drop_columns=['mark', 'comment'], 236 | font_size=12, 237 | caption=u"Handel mit digitalen Währungen %(year)s", 238 | intro=u"

Auflistung aller Transaktionen zwischen " 239 | "%(fromdate)s und %(todate)s:

", 240 | locale="de_DE", 241 | custom_column_names=my_column_names, 242 | custom_formatters={ 243 | 'Art': lambda x: transdct[x] if x in transdct else x}) 244 | 245 | 246 | ######################################################################### 247 | # 9. Now, finally, the calculation is ready to start # 248 | ######################################################################### 249 | 250 | # If the calculation run for previous years already, we can load the state 251 | # of the bags here, no need to calculate everything again: 252 | # bf.load('./status2016.json') 253 | 254 | # Or, if the current calculation crashed (e.g. you forgot to add a traded 255 | # currency in #2 above), the file 'precrash.json' will be created 256 | # automatically. Load it here to continue: 257 | # bf.load('./precrash.json') 258 | 259 | # The following just looks where to start calculating trades, in case you 260 | # already calculated some and restarted by loading 'precrash.json': 261 | last_trade = 0 262 | while (last_trade < len(th.tlist) 263 | and th[last_trade].dtime <= bf._last_date): 264 | last_trade += 1 265 | if last_trade > 0: 266 | logger.info("continuing with trade #%i" % (last_trade + 1)) 267 | 268 | # Now, the calculation. This goes through your imported list of trades: 269 | for i, trade in enumerate(th.tlist[last_trade:]): 270 | # Most of this is just the log output to the console and to the 271 | # file 'ccgains_.log' 272 | # (check out this file for all gory calculation details!): 273 | logger.info('TRADE #%i', i + last_trade + 1) 274 | logger.info(trade) 275 | # This is the important part: 276 | bf.process_trade(trade) 277 | # more logging: 278 | log_bags(bf) 279 | logger.info("Totals: %s", str(bf.totals)) 280 | logger.info("Gains: %s %.12f\n" % (bf.currency, bf.profit)) 281 | 282 | 283 | ######################################################################### 284 | # 10. Save the state of your holdings for the calculation due next year # 285 | ######################################################################### 286 | 287 | bf.save('status2017.json') 288 | 289 | 290 | ######################################################################### 291 | # 11. Create your capital gains report for cryptocurrency trades # 292 | ######################################################################### 293 | 294 | # The default column names used in the report don't look very nice: 295 | # ['kind', 'bag_spent', 'currency', 'bag_date', 'sell_date', 296 | # 'exchange', 'short_term', 'spent_cost', 'proceeds', 'profit'], 297 | # so we rename them: 298 | my_column_names=[ 299 | 'Type', 'Amount spent', u'Currency', 'Purchase date', 300 | 'Sell date', u'Exchange', u'Short term', 'Purchase cost', 301 | 'Proceeds', 'Profit'] 302 | 303 | # Here we create the report pdf for capital gains in 2017. 304 | # The date_precision='D' means we only mention the day of the trade, not 305 | # the precise time. We also set combine=True, so multiple trades made on 306 | # the same day and on the same exchange are combined into a single trade 307 | # on the report: 308 | bf.report.export_report_to_pdf( 309 | 'Report2017.pdf', year=2017, 310 | date_precision='D', combine=True, 311 | custom_column_names=my_column_names, 312 | locale="en_US" 313 | ) 314 | 315 | # And now, let's translate it to German, by using a different 316 | # html template file: 317 | my_column_names=[ 318 | 'Art', 'Verkaufsmenge', u'Währung', 'Erwerbsdatum', 319 | 'Verkaufsdatum', u'Börse', u'in\xa0Besitz', 320 | 'Anschaffungskosten', u'Verkaufserlös', 'Gewinn'] 321 | transdct = {'sale': u'Veräußerung', 322 | 'withdrawal fee': u'Börsengebühr', 323 | 'deposit fee': u'Börsengebühr', 324 | 'exchange fee': u'Börsengebühr'} 325 | convert_short_term=[u'>\xa01\xa0Jahr', u'<\xa01\xa0Jahr'] 326 | 327 | bf.report.export_report_to_pdf( 328 | 'Report2017_de.pdf', year=2017, 329 | date_precision='D', combine=True, 330 | custom_column_names=my_column_names, 331 | custom_formatters={ 332 | u'in\xa0Besitz': lambda b: convert_short_term[b], 333 | 'Art': lambda x: transdct[x]}, 334 | locale="de_DE", 335 | template_file='shortreport_de.html' 336 | ) 337 | 338 | # If you rather want your report in a spreadsheet, you can export 339 | # to csv: 340 | bf.report.export_short_report_to_csv( 341 | 'report_2017.csv', year=2017, 342 | date_precision='D', combine=False, 343 | convert_timezone=True, strip_timezone=True) 344 | 345 | 346 | ######################################################################### 347 | # 12. Optional: Create a detailed report outlining the calculation # 348 | ######################################################################### 349 | 350 | # The simple capital gains report created above is just a plain listing 351 | # of all trades and the gains made, enough for the tax report. 352 | # A more detailed listing outlining the calculation is also available: 353 | 354 | bf.report.export_extended_report_to_pdf( 355 | 'Details_2017.pdf', year=2017, 356 | date_precision='S', combine=False, 357 | font_size=10, locale="en_US") 358 | 359 | # And again, let's translate this report to German: 360 | # (Using transdct from above again to translate the payment kind) 361 | bf.report.export_extended_report_to_pdf( 362 | 'Details_2017_de.pdf', year=2017, 363 | date_precision='S', combine=False, 364 | font_size=10, locale="de_DE", 365 | template_file='fullreport_de.html', 366 | payment_kind_translation=transdct) 367 | ``` 368 | -------------------------------------------------------------------------------- /examples/example_output/status2017.json: -------------------------------------------------------------------------------- 1 | { 2 | "bags": { 3 | "Poloniex": [ 4 | { 5 | "type(Bag)": { 6 | "dtime": { 7 | "type(datetime)": "2017-02-11 20:21:21+00:00" 8 | }, 9 | "price": { 10 | "type(Decimal)": "11.74075083378513264767273711" 11 | }, 12 | "currency": "XMR", 13 | "amount": { 14 | "type(Decimal)": "3.00000000" 15 | }, 16 | "cost": { 17 | "type(Decimal)": "35.22225250135539794301821130" 18 | }, 19 | "cost_currency": "EUR", 20 | "id": 2 21 | } 22 | }, 23 | { 24 | "type(Bag)": { 25 | "dtime": { 26 | "type(datetime)": "2017-05-16 22:25:40+00:00" 27 | }, 28 | "price": { 29 | "type(Decimal)": "23.92448435729462223075183658" 30 | }, 31 | "currency": "XMR", 32 | "amount": { 33 | "type(Decimal)": "92.57377540" 34 | }, 35 | "cost": { 36 | "type(Decimal)": "2214.779841453005710017467493" 37 | }, 38 | "cost_currency": "EUR", 39 | "id": 12 40 | } 41 | } 42 | ] 43 | }, 44 | "in_transit": {}, 45 | "profit": { 46 | "type(Decimal)": "1862.802093954361107960485704" 47 | }, 48 | "num_created_bags": 12, 49 | "totals": { 50 | "Poloniex": { 51 | "XMR": { 52 | "type(Decimal)": "95.57377540" 53 | } 54 | } 55 | }, 56 | "_last_date": { 57 | "type(datetime)": "2017-05-16 22:25:40+00:00" 58 | }, 59 | "currency": "EUR", 60 | "report": { 61 | "type(CapitalGainsReport)": { 62 | "data": [ 63 | [ 64 | "withdrawal fee", 65 | "Bitcoin.de", 66 | { 67 | "type(datetime)": "2017-02-09 23:37:45+00:00" 68 | }, 69 | "BTC", 70 | { 71 | "type(Decimal)": "0.00010000" 72 | }, 73 | { 74 | "type(Decimal)": "1" 75 | }, 76 | { 77 | "type(datetime)": "2017-02-06 16:04:03+00:00" 78 | }, 79 | { 80 | "type(Decimal)": "0.19800000" 81 | }, 82 | { 83 | "type(Decimal)": "0.00010000" 84 | }, 85 | "EUR", 86 | { 87 | "type(Decimal)": "0.09838383838383838383838383838" 88 | }, 89 | true, 90 | { 91 | "type(Decimal)": "930.418929076578706371947191655635833740234375" 92 | }, 93 | { 94 | "type(Decimal)": "0E-29" 95 | }, 96 | { 97 | "type(Decimal)": "-0.09838383838383838383838383838" 98 | }, 99 | "", 100 | 0 101 | ], 102 | [ 103 | "sale", 104 | "Poloniex", 105 | { 106 | "type(datetime)": "2017-02-11 20:21:21+00:00" 107 | }, 108 | "BTC", 109 | { 110 | "type(Decimal)": "0.19790000" 111 | }, 112 | { 113 | "type(Decimal)": "0.001499999397768115375429754607" 114 | }, 115 | { 116 | "type(datetime)": "2017-02-06 16:04:03+00:00" 117 | }, 118 | { 119 | "type(Decimal)": "0.19790000" 120 | }, 121 | { 122 | "type(Decimal)": "0.19790000" 123 | }, 124 | "EUR", 125 | { 126 | "type(Decimal)": "194.7016161616161616161616162" 127 | }, 128 | true, 129 | { 130 | "type(Decimal)": "966.396480123142964657745324075222015380859375" 131 | }, 132 | { 133 | "type(Decimal)": "190.9629887364222033961412992" 134 | }, 135 | { 136 | "type(Decimal)": "-3.7386274251939582200203170" 137 | }, 138 | "XMR", 139 | { 140 | "type(Decimal)": "82.18783446184941889843355230" 141 | } 142 | ], 143 | [ 144 | "sale", 145 | "Poloniex", 146 | { 147 | "type(datetime)": "2017-02-12 16:08:11+00:00" 148 | }, 149 | "XMR", 150 | { 151 | "type(Decimal)": "10.00000000" 152 | }, 153 | { 154 | "type(Decimal)": "0.0015" 155 | }, 156 | { 157 | "type(datetime)": "2017-02-11 20:21:21+00:00" 158 | }, 159 | { 160 | "type(Decimal)": "16.26497244" 161 | }, 162 | { 163 | "type(Decimal)": "10.00000000" 164 | }, 165 | "EUR", 166 | { 167 | "type(Decimal)": "117.4075083378513264767273711" 168 | }, 169 | true, 170 | { 171 | "type(Decimal)": "11.73277839390548393794233561493456363677978515625" 172 | }, 173 | { 174 | "type(Decimal)": "117.1517922631462571203542211" 175 | }, 176 | { 177 | "type(Decimal)": "-0.2557160747050693563731500" 178 | }, 179 | "BTC", 180 | { 181 | "type(Decimal)": "0.012331475" 182 | } 183 | ], 184 | [ 185 | "withdrawal fee", 186 | "Poloniex", 187 | { 188 | "type(datetime)": "2017-02-12 16:23:05+00:00" 189 | }, 190 | "BTC", 191 | { 192 | "type(Decimal)": "0.00010000" 193 | }, 194 | { 195 | "type(Decimal)": "1" 196 | }, 197 | { 198 | "type(datetime)": "2017-02-12 16:08:11+00:00" 199 | }, 200 | { 201 | "type(Decimal)": "0.12331475" 202 | }, 203 | { 204 | "type(Decimal)": "0.00010000" 205 | }, 206 | "EUR", 207 | { 208 | "type(Decimal)": "0.09500225420166383755418895234" 209 | }, 210 | true, 211 | { 212 | "type(Decimal)": "949.2222570495226818820810876786708831787109375" 213 | }, 214 | { 215 | "type(Decimal)": "0E-29" 216 | }, 217 | { 218 | "type(Decimal)": "-0.09500225420166383755418895234" 219 | }, 220 | "", 221 | 0 222 | ], 223 | [ 224 | "sale", 225 | "Poloniex", 226 | { 227 | "type(datetime)": "2017-02-12 16:41:27+00:00" 228 | }, 229 | "BTC", 230 | { 231 | "type(Decimal)": "0.10321475" 232 | }, 233 | { 234 | "type(Decimal)": "0.001499999995999736028803173566" 235 | }, 236 | { 237 | "type(datetime)": "2017-02-12 16:08:11+00:00" 238 | }, 239 | { 240 | "type(Decimal)": "0.10321475" 241 | }, 242 | { 243 | "type(Decimal)": "0.10321475" 244 | }, 245 | "EUR", 246 | { 247 | "type(Decimal)": "98.05633916861182577196224163" 248 | }, 249 | true, 250 | { 251 | "type(Decimal)": "949.2222570495226818820810876786708831787109375" 252 | }, 253 | { 254 | "type(Decimal)": "97.82677734926043871201192078" 255 | }, 256 | { 257 | "type(Decimal)": "-0.22956181935138705995032085" 258 | }, 259 | "PASC", 260 | { 261 | "type(Decimal)": "2720.634343347244458761950206" 262 | } 263 | ], 264 | [ 265 | "exchange fee", 266 | "Bitsquare/bisq", 267 | { 268 | "type(datetime)": "2017-02-12 16:48:21+00:00" 269 | }, 270 | "BTC", 271 | { 272 | "type(Decimal)": "0.0005" 273 | }, 274 | { 275 | "type(Decimal)": "1" 276 | }, 277 | { 278 | "type(datetime)": "2017-02-12 16:08:11+00:00" 279 | }, 280 | { 281 | "type(Decimal)": "0.02000000" 282 | }, 283 | { 284 | "type(Decimal)": "0.0005" 285 | }, 286 | "EUR", 287 | { 288 | "type(Decimal)": "0.4750112710083191877709447618" 289 | }, 290 | true, 291 | { 292 | "type(Decimal)": "949.2222570495226818820810876786708831787109375" 293 | }, 294 | { 295 | "type(Decimal)": "0E-28" 296 | }, 297 | { 298 | "type(Decimal)": "-0.4750112710083191877709447618" 299 | }, 300 | "", 301 | 0 302 | ], 303 | [ 304 | "sale", 305 | "Poloniex", 306 | { 307 | "type(datetime)": "2017-02-12 17:35:55+00:00" 308 | }, 309 | "PASC", 310 | { 311 | "type(Decimal)": "280.80959359" 312 | }, 313 | { 314 | "type(Decimal)": "0.002499914821567063723285011656" 315 | }, 316 | { 317 | "type(datetime)": "2017-02-12 16:41:27+00:00" 318 | }, 319 | { 320 | "type(Decimal)": "280.80959359" 321 | }, 322 | { 323 | "type(Decimal)": "280.80959359" 324 | }, 325 | "EUR", 326 | { 327 | "type(Decimal)": "97.82677734926043871201192078" 328 | }, 329 | true, 330 | { 331 | "type(Decimal)": "0.350244746021280695114086256580776534974575042724609375" 332 | }, 333 | { 334 | "type(Decimal)": "98.10621295277688817794340655" 335 | }, 336 | { 337 | "type(Decimal)": "0.27943560351644946593148577" 338 | }, 339 | "BTC", 340 | { 341 | "type(Decimal)": "0.0003690749973140901621002912260" 342 | } 343 | ], 344 | [ 345 | "withdrawal fee", 346 | "Bitsquare/bisq", 347 | { 348 | "type(datetime)": "2017-02-14 15:10:19+00:00" 349 | }, 350 | "BTC", 351 | { 352 | "type(Decimal)": "0.0005" 353 | }, 354 | { 355 | "type(Decimal)": "1" 356 | }, 357 | { 358 | "type(datetime)": "2017-02-12 16:08:11+00:00" 359 | }, 360 | { 361 | "type(Decimal)": "0.01950000" 362 | }, 363 | { 364 | "type(Decimal)": "0.0005" 365 | }, 366 | "EUR", 367 | { 368 | "type(Decimal)": "0.4750112710083191877709447618" 369 | }, 370 | true, 371 | { 372 | "type(Decimal)": "959.36693043702462091459892690181732177734375" 373 | }, 374 | { 375 | "type(Decimal)": "0E-28" 376 | }, 377 | { 378 | "type(Decimal)": "-0.4750112710083191877709447618" 379 | }, 380 | "", 381 | 0 382 | ], 383 | [ 384 | "withdrawal fee", 385 | "Bitsquare/bisq", 386 | { 387 | "type(datetime)": "2017-02-16 16:38:03+00:00" 388 | }, 389 | "BTC", 390 | { 391 | "type(Decimal)": "0.00050000" 392 | }, 393 | { 394 | "type(Decimal)": "1" 395 | }, 396 | { 397 | "type(datetime)": "2017-02-12 16:08:11+00:00" 398 | }, 399 | { 400 | "type(Decimal)": "0.00900000" 401 | }, 402 | { 403 | "type(Decimal)": "0.00050000" 404 | }, 405 | "EUR", 406 | { 407 | "type(Decimal)": "0.4750112710083191877709447618" 408 | }, 409 | true, 410 | { 411 | "type(Decimal)": "984.135840337172112413099966943264007568359375" 412 | }, 413 | { 414 | "type(Decimal)": "0E-28" 415 | }, 416 | { 417 | "type(Decimal)": "-0.4750112710083191877709447618" 418 | }, 419 | "", 420 | 0 421 | ], 422 | [ 423 | "sale", 424 | "Poloniex", 425 | { 426 | "type(datetime)": "2017-02-17 06:24:48+00:00" 427 | }, 428 | "XMR", 429 | { 430 | "type(Decimal)": "3.26497244" 431 | }, 432 | { 433 | "type(Decimal)": "0.001499921651264490911066013857" 434 | }, 435 | { 436 | "type(datetime)": "2017-02-11 20:21:21+00:00" 437 | }, 438 | { 439 | "type(Decimal)": "6.26497244" 440 | }, 441 | { 442 | "type(Decimal)": "3.26497244" 443 | }, 444 | "EUR", 445 | { 446 | "type(Decimal)": "38.33322789721547897639571680" 447 | }, 448 | true, 449 | { 450 | "type(Decimal)": "12.549733922906032290711664245463907718658447265625" 451 | }, 452 | { 453 | "type(Decimal)": "40.91307679484288391125976530" 454 | }, 455 | { 456 | "type(Decimal)": "2.57984889762740493486404850" 457 | }, 458 | "BTC", 459 | { 460 | "type(Decimal)": "0.01272489454765504850632062303" 461 | } 462 | ], 463 | [ 464 | "sale", 465 | "Poloniex", 466 | { 467 | "type(datetime)": "2017-02-18 20:31:28+00:00" 468 | }, 469 | "BTC", 470 | { 471 | "type(Decimal)": "0.26368623" 472 | }, 473 | { 474 | "type(Decimal)": "0.001499999999021332854581917677" 475 | }, 476 | { 477 | "type(datetime)": "2017-02-12 16:08:11+00:00" 478 | }, 479 | { 480 | "type(Decimal)": "0.00850000" 481 | }, 482 | { 483 | "type(Decimal)": "0.00850000" 484 | }, 485 | "EUR", 486 | { 487 | "type(Decimal)": "8.075191607141426192106060953" 488 | }, 489 | true, 490 | { 491 | "type(Decimal)": "995.34314463544797035865485668182373046875" 492 | }, 493 | { 494 | "type(Decimal)": "8.447726104315485718316103998" 495 | }, 496 | { 497 | "type(Decimal)": "0.372534497174059526210043045" 498 | }, 499 | "GNT", 500 | { 501 | "type(Decimal)": "27529.63881997175203270948202" 502 | } 503 | ], 504 | [ 505 | "sale", 506 | "Poloniex", 507 | { 508 | "type(datetime)": "2017-02-18 20:31:28+00:00" 509 | }, 510 | "BTC", 511 | { 512 | "type(Decimal)": "0.25518623" 513 | }, 514 | { 515 | "type(Decimal)": "0.001499999999021332854581917677" 516 | }, 517 | { 518 | "type(datetime)": "2017-02-12 16:08:11+00:00" 519 | }, 520 | { 521 | "type(Decimal)": "0.0100" 522 | }, 523 | { 524 | "type(Decimal)": "0.0100" 525 | }, 526 | "EUR", 527 | { 528 | "type(Decimal)": "9.500225420166383755418895235" 529 | }, 530 | true, 531 | { 532 | "type(Decimal)": "995.34314463544797035865485668182373046875" 533 | }, 534 | { 535 | "type(Decimal)": "9.938501299194689080371887057" 536 | }, 537 | { 538 | "type(Decimal)": "0.438275879028305324952991822" 539 | }, 540 | "GNT", 541 | { 542 | "type(Decimal)": "27529.63881997175203270948202" 543 | } 544 | ], 545 | [ 546 | "sale", 547 | "Poloniex", 548 | { 549 | "type(datetime)": "2017-02-18 20:31:28+00:00" 550 | }, 551 | "BTC", 552 | { 553 | "type(Decimal)": "0.24518623" 554 | }, 555 | { 556 | "type(Decimal)": "0.001499999999021332854581917677" 557 | }, 558 | { 559 | "type(datetime)": "2017-02-12 17:35:55+00:00" 560 | }, 561 | { 562 | "type(Decimal)": "0.10363980" 563 | }, 564 | { 565 | "type(Decimal)": "0.10363980" 566 | }, 567 | "EUR", 568 | { 569 | "type(Decimal)": "98.10621295277688817794340655" 570 | }, 571 | true, 572 | { 573 | "type(Decimal)": "995.34314463544797035865485668182373046875" 574 | }, 575 | { 576 | "type(Decimal)": "103.0024286948277737351926300" 577 | }, 578 | { 579 | "type(Decimal)": "4.89621574205088555724922345" 580 | }, 581 | "GNT", 582 | { 583 | "type(Decimal)": "27529.63881997175203270948202" 584 | } 585 | ], 586 | [ 587 | "sale", 588 | "Poloniex", 589 | { 590 | "type(datetime)": "2017-02-18 20:31:28+00:00" 591 | }, 592 | "BTC", 593 | { 594 | "type(Decimal)": "0.14154643" 595 | }, 596 | { 597 | "type(Decimal)": "0.001499999999021332854581917677" 598 | }, 599 | { 600 | "type(datetime)": "2017-02-14 15:10:17+00:00" 601 | }, 602 | { 603 | "type(Decimal)": "0.20000000" 604 | }, 605 | { 606 | "type(Decimal)": "0.14154643" 607 | }, 608 | "EUR", 609 | { 610 | "type(Decimal)": "136.167665660" 611 | }, 612 | true, 613 | { 614 | "type(Decimal)": "995.34314463544797035865485668182373046875" 615 | }, 616 | { 617 | "type(Decimal)": "140.6759378451370114286623686" 618 | }, 619 | { 620 | "type(Decimal)": "4.5082721851370114286623686" 621 | }, 622 | "GNT", 623 | { 624 | "type(Decimal)": "27529.63881997175203270948202" 625 | } 626 | ], 627 | [ 628 | "sale", 629 | "Poloniex", 630 | { 631 | "type(datetime)": "2017-04-30 17:18:31+00:00" 632 | }, 633 | "GNT", 634 | { 635 | "type(Decimal)": "7259.18667370" 636 | }, 637 | { 638 | "type(Decimal)": "0.001499995189763891983198484831" 639 | }, 640 | { 641 | "type(datetime)": "2017-02-18 20:31:28+00:00" 642 | }, 643 | { 644 | "type(Decimal)": "7259.18667370" 645 | }, 646 | { 647 | "type(Decimal)": "7259.18667370" 648 | }, 649 | "EUR", 650 | { 651 | "type(Decimal)": "262.0645939434749599625429896" 652 | }, 653 | true, 654 | { 655 | "type(Decimal)": "0.2078071545020687416194249408363248221576213836669921875" 656 | }, 657 | { 658 | "type(Decimal)": "1506.248167527236695455002141" 659 | }, 660 | { 661 | "type(Decimal)": "1244.183573583761735492459151" 662 | }, 663 | "BTC", 664 | { 665 | "type(Decimal)": "0.0001774334505911660531540905537" 666 | } 667 | ], 668 | [ 669 | "sale", 670 | "Poloniex", 671 | { 672 | "type(datetime)": "2017-05-16 22:25:40+00:00" 673 | }, 674 | "BTC", 675 | { 676 | "type(Decimal)": "1.38802254" 677 | }, 678 | { 679 | "type(Decimal)": "0.001499999924605915980315472288" 680 | }, 681 | { 682 | "type(datetime)": "2017-02-14 15:10:17+00:00" 683 | }, 684 | { 685 | "type(Decimal)": "0.05845357" 686 | }, 687 | { 688 | "type(Decimal)": "0.05845357" 689 | }, 690 | "EUR", 691 | { 692 | "type(Decimal)": "56.232334340" 693 | }, 694 | true, 695 | { 696 | "type(Decimal)": "1598.033848779182108046370558440685272216796875" 697 | }, 698 | { 699 | "type(Decimal)": "93.27066727386298134676237846" 700 | }, 701 | { 702 | "type(Decimal)": "37.03833293386298134676237846" 703 | }, 704 | "XMR", 705 | { 706 | "type(Decimal)": "66.69472053386107116099137698" 707 | } 708 | ], 709 | [ 710 | "sale", 711 | "Poloniex", 712 | { 713 | "type(datetime)": "2017-05-16 22:25:40+00:00" 714 | }, 715 | "BTC", 716 | { 717 | "type(Decimal)": "1.32956897" 718 | }, 719 | { 720 | "type(Decimal)": "0.001499999924605915980315472288" 721 | }, 722 | { 723 | "type(datetime)": "2017-02-17 06:24:48+00:00" 724 | }, 725 | { 726 | "type(Decimal)": "0.04154643" 727 | }, 728 | { 729 | "type(Decimal)": "0.04154643" 730 | }, 731 | "EUR", 732 | { 733 | "type(Decimal)": "40.91307679484288391125976530" 734 | }, 735 | true, 736 | { 737 | "type(Decimal)": "1598.033848779182108046370558440685272216796875" 738 | }, 739 | { 740 | "type(Decimal)": "66.29301253878658196778347128" 741 | }, 742 | { 743 | "type(Decimal)": "25.37993574394369805652370598" 744 | }, 745 | "XMR", 746 | { 747 | "type(Decimal)": "66.69472053386107116099137698" 748 | } 749 | ], 750 | [ 751 | "sale", 752 | "Poloniex", 753 | { 754 | "type(datetime)": "2017-05-16 22:25:40+00:00" 755 | }, 756 | "BTC", 757 | { 758 | "type(Decimal)": "1.28802254" 759 | }, 760 | { 761 | "type(Decimal)": "0.001499999924605915980315472288" 762 | }, 763 | { 764 | "type(datetime)": "2017-04-30 17:18:31+00:00" 765 | }, 766 | { 767 | "type(Decimal)": "1.28802254" 768 | }, 769 | { 770 | "type(Decimal)": "1.28802254" 771 | }, 772 | "EUR", 773 | { 774 | "type(Decimal)": "1506.248167527236695455002141" 775 | }, 776 | true, 777 | { 778 | "type(Decimal)": "1598.033848779182108046370558440685272216796875" 779 | }, 780 | { 781 | "type(Decimal)": "2055.216161640356146702921643" 782 | }, 783 | { 784 | "type(Decimal)": "548.967994113119451247919502" 785 | }, 786 | "XMR", 787 | { 788 | "type(Decimal)": "66.69472053386107116099137698" 789 | } 790 | ] 791 | ] 792 | } 793 | }, 794 | "dump_file": "/home/juergen/Coding/projects/ccGains/examples/precrash.json" 795 | } -------------------------------------------------------------------------------- /examples/example_output/ccgains_20180509-114033.log: -------------------------------------------------------------------------------- 1 | INFO : Loaded 2 transactions from ./example_csv/bitcoin.de_account_statement_2017_fabricated.csv 2 | INFO : Loaded 2 transactions from ./example_csv/poloniex_depositHistory_2017_fabricated.csv 3 | INFO : Loaded 8 transactions from ./example_csv/poloniex_tradeHistory_2017_fabricated.csv 4 | WARNING : Poloniex does not include withdrawal fees in exported csv-files. Please include the fees manually, or call `add_missing_transaction_fees` after transactions from all relevant exchanges were imported. 5 | INFO : Loaded 1 transactions from ./example_csv/poloniex_withdrawalHistory_2017_fabricated.csv 6 | WARNING : Bitsquare/Bisq does not include withdrawal fees in exported csv-files. Please include the fees manually, or call `add_missing_transaction_fees` after transactions from all relevant exchanges were imported. 7 | INFO : Loaded 6 transactions from ./example_csv/bisq_trades_2017_fabricated.csv and ./example_csv/bisq_transactions_2017_fabricated.csv 8 | INFO : amended withdrawal: Withdrawal on 2017-02-12 16:23:05+00:00: Acquired 0.00000000 , disposed of 0.02010000 BTC for a fee of 0.00010000 BTC on Poloniex [1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM] 9 | INFO : amended withdrawal: Withdrawn from wallet on 2017-02-16 16:38:03+00:00: Acquired 0.00000000 , disposed of 0.21900000 BTC for a fee of 0.00050000 BTC on Bitsquare/Bisq [Sent to: 14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW] 10 | INFO : Saved trading history for year 2017 to transactions2017.csv 11 | INFO : Saved trading history for year 2017 to Transactions2017.pdf 12 | INFO : TRADE #1 13 | INFO : Purchase on 2017-02-06 16:04:03+00:00: Acquired 0.19800000 BTC, disposed of 194.80000000 EUR for a fee of 0.00100000 BTC on Bitcoin.de [AABBCC] 14 | INFO : Processing trade: Purchase, 2017-02-06 16:04:03+00:00, BTC, 0.19800000, EUR, 194.80000000, BTC, 0.00100000, Bitcoin.de, , AABBCC 15 | INFO : Buying 0.19800000 BTC for 194.80000000 EUR at Bitcoin.de (2017-02-06 16:04:03+00:00) 16 | INFO : State of bags: 17 | exchange date currency amount costcur cost price 18 | id 19 | 1 Bitcoin.de 2017-02-06 16:04:03+00:00 BTC 0.19800000 EUR 194.80000000 983.83838384 20 | 21 | INFO : Totals: {'Bitcoin.de': {'BTC': Decimal('0.19800000')}} 22 | INFO : Gains: EUR 0.000000000000 23 | 24 | INFO : TRADE #2 25 | INFO : Disbursement on 2017-02-09 23:37:45+00:00: Acquired 0.00000000 EUR, disposed of 0.19800000 BTC for a fee of 0.00010000 BTC on Bitcoin.de [7bf9065a97b8d0cac275474297bf0e7463d8ba82edc267293ed04f9100851a01] 26 | INFO : Processing trade: Disbursement, 2017-02-09 23:37:45+00:00, EUR, 0.00000000, BTC, 0.19800000, BTC, 0.00010000, Bitcoin.de, , 7bf9065a97b8d0cac275474297bf0e7463d8ba82edc267293ed04f9100851a01 27 | INFO : Withdrawing 0.19800000 BTC from Bitcoin.de (2017-02-09 23:37:45+00:00, fee: 0.00010000 BTC) 28 | INFO : Paying 0.00010000 BTC from Bitcoin.de (including 0.00010000 BTC fees) 29 | INFO : Paying with bag from 2017-02-06 16:04:03+00:00, containing 0.19800000 BTC 30 | INFO : Contents of bag after payment: 0.19790000 BTC (spent 0.00010000 BTC) 31 | INFO : Profits in this transaction: 32 | Original bag cost: 0.098 EUR (Price 983.83838384 EUR/BTC) 33 | Proceeds : 0.093 EUR (Price 930.41892908 EUR/BTC) 34 | Proceeds w/o fees: 0.000 EUR 35 | Profit : -0.098 EUR 36 | Taxable? : yes (held for less than a year) 37 | INFO : Taxable loss due to fees: -0.098 EUR 38 | INFO : State of bags: 39 | exchange date currency amount costcur cost price 40 | id 41 | 1 2017-02-06 16:04:03+00:00 BTC 0.19790000 EUR 194.70161616 983.83838384 42 | 43 | INFO : Totals: {'in_transit': {'BTC': Decimal('0.19790000')}} 44 | INFO : Gains: EUR -0.098383838384 45 | 46 | INFO : TRADE #3 47 | INFO : Deposit on 2017-02-10 23:47:28+00:00: Acquired 0.19790000 BTC, disposed of 0.00000000 for a fee of 0.00000000 on Poloniex [14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW] 48 | INFO : Processing trade: Deposit, 2017-02-10 23:47:28+00:00, BTC, 0.19790000, , 0.00000000, , 0.00000000, Poloniex, , 14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW 49 | INFO : Depositing 0.19790000 BTC at Poloniex (2017-02-10 23:47:28+00:00, fee: 0.00000000 ) 50 | INFO : State of bags: 51 | exchange date currency amount costcur cost price 52 | id 53 | 1 Poloniex 2017-02-06 16:04:03+00:00 BTC 0.19790000 EUR 194.70161616 983.83838384 54 | 55 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.19790000')}} 56 | INFO : Gains: EUR -0.098383838384 57 | 58 | INFO : TRADE #4 59 | INFO : Exchange on 2017-02-11 20:21:21+00:00: Acquired 16.26497244 XMR, disposed of 0.19790000 BTC for a fee of 0.02443410 XMR on Poloniex (Buy) [119073300001] 60 | INFO : Processing trade: Exchange, 2017-02-11 20:21:21+00:00, XMR, 16.26497244, BTC, 0.19790000, XMR, 0.02443410, Poloniex, Buy, 119073300001 61 | INFO : Selling 0.19790000 BTC for 16.26497244 XMR at Poloniex (2017-02-11 20:21:21+00:00, fee: 0.02443410 XMR) 62 | INFO : Paying 0.19790000 BTC from Poloniex (including 0.00029685 BTC fees) 63 | INFO : Paying with bag from 2017-02-06 16:04:03+00:00, containing 0.19790000 BTC 64 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 0.19790000 BTC) 65 | INFO : Profits in this transaction: 66 | Original bag cost: 194.702 EUR (Price 983.83838384 EUR/BTC) 67 | Proceeds : 191.250 EUR (Price 966.39648012 EUR/BTC) 68 | Proceeds w/o fees: 190.963 EUR 69 | Profit : -3.739 EUR 70 | Taxable? : yes (held for less than a year) 71 | INFO : State of bags: 72 | exchange date currency amount costcur cost price 73 | id 74 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 16.26497244 EUR 190.96298874 11.74075083 75 | 76 | INFO : Totals: {'Poloniex': {'XMR': Decimal('16.26497244')}} 77 | INFO : Gains: EUR -3.837011263578 78 | 79 | INFO : TRADE #5 80 | INFO : Exchange on 2017-02-12 16:08:11+00:00: Acquired 0.12331475 BTC, disposed of 10.00000000 XMR for a fee of 0.00018525 BTC on Poloniex (Sell) [119488400001] 81 | INFO : Processing trade: Exchange, 2017-02-12 16:08:11+00:00, BTC, 0.12331475, XMR, 10.00000000, BTC, 0.00018525, Poloniex, Sell, 119488400001 82 | INFO : Selling 10.00000000 XMR for 0.12331475 BTC at Poloniex (2017-02-12 16:08:11+00:00, fee: 0.00018525 BTC) 83 | INFO : Fetched historical price data with request: https://poloniex.com/public?start=1486857600&end=1486944000&command=returnTradeHistory¤cyPair=BTC_XMR 84 | INFO : Successfully fetched 8425 trades 85 | INFO : Paying 10.00000000 XMR from Poloniex (including 0.01500000 XMR fees) 86 | INFO : Paying with bag from 2017-02-11 20:21:21+00:00, containing 16.26497244 XMR 87 | INFO : Contents of bag after payment: 6.26497244 XMR (spent 10.00000000 XMR) 88 | INFO : Profits in this transaction: 89 | Original bag cost: 117.408 EUR (Price 11.74075083 EUR/XMR) 90 | Proceeds : 117.328 EUR (Price 11.73277839 EUR/XMR) 91 | Proceeds w/o fees: 117.152 EUR 92 | Profit : -0.256 EUR 93 | Taxable? : yes (held for less than a year) 94 | INFO : State of bags: 95 | exchange date currency amount costcur cost price 96 | id 97 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 98 | 3 Poloniex 2017-02-12 16:08:11+00:00 BTC 0.12331475 EUR 117.15179226 950.02254202 99 | 100 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.12331475'), 'XMR': Decimal('6.26497244')}} 101 | INFO : Gains: EUR -4.092727338283 102 | 103 | INFO : TRADE #6 104 | INFO : Withdrawal on 2017-02-12 16:23:05+00:00: Acquired 0.00000000 , disposed of 0.02010000 BTC for a fee of 0.00010000 BTC on Poloniex [1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM] 105 | INFO : Processing trade: Withdrawal, 2017-02-12 16:23:05+00:00, , 0.00000000, BTC, 0.02010000, BTC, 0.00010000, Poloniex, , 1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM 106 | INFO : Withdrawing 0.02010000 BTC from Poloniex (2017-02-12 16:23:05+00:00, fee: 0.00010000 BTC) 107 | INFO : Paying 0.00010000 BTC from Poloniex (including 0.00010000 BTC fees) 108 | INFO : Paying with bag from 2017-02-12 16:08:11+00:00, containing 0.12331475 BTC 109 | INFO : Contents of bag after payment: 0.12321475 BTC (spent 0.00010000 BTC) 110 | INFO : Profits in this transaction: 111 | Original bag cost: 0.095 EUR (Price 950.02254202 EUR/BTC) 112 | Proceeds : 0.095 EUR (Price 949.22225705 EUR/BTC) 113 | Proceeds w/o fees: 0.000 EUR 114 | Profit : -0.095 EUR 115 | Taxable? : yes (held for less than a year) 116 | INFO : Taxable loss due to fees: -0.095 EUR 117 | INFO : State of bags: 118 | exchange date currency amount costcur cost price 119 | id 120 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 121 | 3 Poloniex 2017-02-12 16:08:11+00:00 BTC 0.10321475 EUR 98.05633917 950.02254202 122 | 4 2017-02-12 16:08:11+00:00 BTC 0.02000000 EUR 19.00045084 950.02254202 123 | 124 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.10321475'), 'XMR': Decimal('6.26497244')}, 'in_transit': {'BTC': Decimal('0.02000000')}} 125 | INFO : Gains: EUR -4.187729592485 126 | 127 | INFO : TRADE #7 128 | INFO : Received funds on 2017-02-12 16:31:20+00:00: Acquired 0.02000000 BTC, disposed of 0.00000000 for a fee of 0.00000000 on Bitsquare/Bisq [Received with: 1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM] 129 | INFO : Processing trade: Received funds, 2017-02-12 16:31:20+00:00, BTC, 0.02000000, , 0.00000000, , 0.00000000, Bitsquare/Bisq, , Received with: 1B6QTS4jbhRrqi23Puy9zF0nfVEapZtgNM 130 | INFO : Depositing 0.02000000 BTC at Bitsquare/Bisq (2017-02-12 16:31:20+00:00, fee: 0.00000000 ) 131 | INFO : State of bags: 132 | exchange date currency amount costcur cost price 133 | id 134 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 135 | 3 Poloniex 2017-02-12 16:08:11+00:00 BTC 0.10321475 EUR 98.05633917 950.02254202 136 | 4 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.02000000 EUR 19.00045084 950.02254202 137 | 138 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.10321475'), 'XMR': Decimal('6.26497244')}, 'Bitsquare/bisq': {'BTC': Decimal('0.02000000')}} 139 | INFO : Gains: EUR -4.187729592485 140 | 141 | INFO : TRADE #8 142 | INFO : Exchange on 2017-02-12 16:41:27+00:00: Acquired 280.80959359 PASC, disposed of 0.10321475 BTC for a fee of 0.42184716 PASC on Poloniex (Buy) [3112100001] 143 | INFO : Processing trade: Exchange, 2017-02-12 16:41:27+00:00, PASC, 280.80959359, BTC, 0.10321475, PASC, 0.42184716, Poloniex, Buy, 3112100001 144 | INFO : Selling 0.10321475 BTC for 280.80959359 PASC at Poloniex (2017-02-12 16:41:27+00:00, fee: 0.42184716 PASC) 145 | INFO : Paying 0.10321475 BTC from Poloniex (including 0.00015482 BTC fees) 146 | INFO : Paying with bag from 2017-02-12 16:08:11+00:00, containing 0.10321475 BTC 147 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 0.10321475 BTC) 148 | INFO : Profits in this transaction: 149 | Original bag cost: 98.056 EUR (Price 950.02254202 EUR/BTC) 150 | Proceeds : 97.974 EUR (Price 949.22225705 EUR/BTC) 151 | Proceeds w/o fees: 97.827 EUR 152 | Profit : -0.230 EUR 153 | Taxable? : yes (held for less than a year) 154 | INFO : State of bags: 155 | exchange date currency amount costcur cost price 156 | id 157 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 158 | 5 Poloniex 2017-02-12 16:41:27+00:00 PASC 280.80959359 EUR 97.82677735 0.34837406 159 | 4 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.02000000 EUR 19.00045084 950.02254202 160 | 161 | INFO : Totals: {'Poloniex': {'XMR': Decimal('6.26497244'), 'PASC': Decimal('280.80959359')}, 'Bitsquare/bisq': {'BTC': Decimal('0.02000000')}} 162 | INFO : Gains: EUR -4.417291411836 163 | 164 | INFO : TRADE #9 165 | INFO : Create offer fee: a5ed7482 on 2017-02-12 16:48:21+00:00: Acquired 0.00000000 , disposed of 0.00000000 BTC for a fee of 0.00050000 BTC on Bitsquare/Bisq [Sent to: 1Fgs78zXQOohFLd1PAZSBaG40nl2wTejIE] 166 | INFO : Processing trade: Create offer fee: a5ed7482, 2017-02-12 16:48:21+00:00, , 0.00000000, BTC, 0, BTC, 0.00050000, Bitsquare/Bisq, , Sent to: 1Fgs78zXQOohFLd1PAZSBaG40nl2wTejIE 167 | INFO : Paying 0.00050000 BTC from Bitsquare/bisq (including 0.00050000 BTC fees) 168 | INFO : Paying with bag from 2017-02-12 16:08:11+00:00, containing 0.02000000 BTC 169 | INFO : Contents of bag after payment: 0.01950000 BTC (spent 0.00050000 BTC) 170 | INFO : Profits in this transaction: 171 | Original bag cost: 0.475 EUR (Price 950.02254202 EUR/BTC) 172 | Proceeds : 0.475 EUR (Price 949.22225705 EUR/BTC) 173 | Proceeds w/o fees: 0.000 EUR 174 | Profit : -0.475 EUR 175 | Taxable? : yes (held for less than a year) 176 | INFO : Taxable loss due to fees: -0.475 EUR 177 | INFO : State of bags: 178 | exchange date currency amount costcur cost price 179 | id 180 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 181 | 5 Poloniex 2017-02-12 16:41:27+00:00 PASC 280.80959359 EUR 97.82677735 0.34837406 182 | 4 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.01950000 EUR 18.52543957 950.02254202 183 | 184 | INFO : Totals: {'Poloniex': {'XMR': Decimal('6.26497244'), 'PASC': Decimal('280.80959359')}, 'Bitsquare/bisq': {'BTC': Decimal('0.01950000')}} 185 | INFO : Gains: EUR -4.892302682844 186 | 187 | INFO : TRADE #10 188 | INFO : Exchange on 2017-02-12 17:35:55+00:00: Acquired 0.10363980 BTC, disposed of 280.80959359 PASC for a fee of 0.00025974 BTC on Poloniex (Sell) [3116800001] 189 | INFO : Processing trade: Exchange, 2017-02-12 17:35:55+00:00, BTC, 0.10363980, PASC, 280.80959359, BTC, 0.00025974, Poloniex, Sell, 3116800001 190 | INFO : Selling 280.80959359 PASC for 0.10363980 BTC at Poloniex (2017-02-12 17:35:55+00:00, fee: 0.00025974 BTC) 191 | INFO : Fetched historical price data with request: https://poloniex.com/public?start=1486857600&end=1486944000&command=returnTradeHistory¤cyPair=BTC_PASC 192 | INFO : Successfully fetched 5243 trades 193 | INFO : Paying 280.80959359 PASC from Poloniex (including 0.70200007 PASC fees) 194 | INFO : Paying with bag from 2017-02-12 16:41:27+00:00, containing 280.80959359 PASC 195 | INFO : Contents of bag after payment: 0.00000000 PASC (spent 280.80959359 PASC) 196 | INFO : Profits in this transaction: 197 | Original bag cost: 97.827 EUR (Price 0.34837406 EUR/PASC) 198 | Proceeds : 98.352 EUR (Price 0.35024475 EUR/PASC) 199 | Proceeds w/o fees: 98.106 EUR 200 | Profit : 0.279 EUR 201 | Taxable? : yes (held for less than a year) 202 | INFO : State of bags: 203 | exchange date currency amount costcur cost price 204 | id 205 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 206 | 6 Poloniex 2017-02-12 17:35:55+00:00 BTC 0.10363980 EUR 98.10621295 946.60750940 207 | 4 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.01950000 EUR 18.52543957 950.02254202 208 | 209 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.10363980'), 'XMR': Decimal('6.26497244')}, 'Bitsquare/bisq': {'BTC': Decimal('0.01950000')}} 210 | INFO : Gains: EUR -4.612867079328 211 | 212 | INFO : TRADE #11 213 | INFO : Buy BTC on 2017-02-14 15:10:17+00:00: Acquired 0.20000000 BTC, disposed of 192.40000000 EUR for a fee of 0.00000000 BTC on Bitsquare/Bisq [a5ed7482] 214 | INFO : Processing trade: Buy BTC, 2017-02-14 15:10:17+00:00, BTC, 0.20000000, EUR, 192.40000000, BTC, 0.00000000, Bitsquare/Bisq, , a5ed7482 215 | INFO : Buying 0.20000000 BTC for 192.40000000 EUR at Bitsquare/Bisq (2017-02-14 15:10:17+00:00) 216 | INFO : State of bags: 217 | exchange date currency amount costcur cost price 218 | id 219 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 220 | 6 Poloniex 2017-02-12 17:35:55+00:00 BTC 0.10363980 EUR 98.10621295 946.60750940 221 | 4 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.01950000 EUR 18.52543957 950.02254202 222 | 7 Bitsquare/bisq 2017-02-14 15:10:17+00:00 BTC 0.20000000 EUR 192.40000000 962.00000000 223 | 224 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.10363980'), 'XMR': Decimal('6.26497244')}, 'Bitsquare/bisq': {'BTC': Decimal('0.21950000')}} 225 | INFO : Gains: EUR -4.612867079328 226 | 227 | INFO : TRADE #12 228 | INFO : MultiSig deposit: a5ed7482 on 2017-02-14 15:10:19+00:00: Acquired 0.00000000 , disposed of 0.01050000 BTC for a fee of 0.00050000 BTC on Bitsquare/Bisq [Sent to: 3D6Bk0MWLwTu5qHC8mXO7FNsbrfPZhAgox] 229 | INFO : Processing trade: MultiSig deposit: a5ed7482, 2017-02-14 15:10:19+00:00, , 0.00000000, BTC, 0.01050000, BTC, 0.00050000, Bitsquare/Bisq, , Sent to: 3D6Bk0MWLwTu5qHC8mXO7FNsbrfPZhAgox 230 | INFO : Withdrawing 0.01050000 BTC from Bitsquare/Bisq (2017-02-14 15:10:19+00:00, fee: 0.00050000 BTC) 231 | INFO : Paying 0.00050000 BTC from Bitsquare/bisq (including 0.00050000 BTC fees) 232 | INFO : Paying with bag from 2017-02-12 16:08:11+00:00, containing 0.01950000 BTC 233 | INFO : Contents of bag after payment: 0.01900000 BTC (spent 0.00050000 BTC) 234 | INFO : Profits in this transaction: 235 | Original bag cost: 0.475 EUR (Price 950.02254202 EUR/BTC) 236 | Proceeds : 0.480 EUR (Price 959.36693044 EUR/BTC) 237 | Proceeds w/o fees: 0.000 EUR 238 | Profit : -0.475 EUR 239 | Taxable? : yes (held for less than a year) 240 | INFO : Taxable loss due to fees: -0.475 EUR 241 | INFO : State of bags: 242 | exchange date currency amount costcur cost price 243 | id 244 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 245 | 6 Poloniex 2017-02-12 17:35:55+00:00 BTC 0.10363980 EUR 98.10621295 946.60750940 246 | 4 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.00900000 EUR 8.55020288 950.02254202 247 | 7 Bitsquare/bisq 2017-02-14 15:10:17+00:00 BTC 0.20000000 EUR 192.40000000 962.00000000 248 | 8 2017-02-12 16:08:11+00:00 BTC 0.01000000 EUR 9.50022542 950.02254202 249 | 250 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.10363980'), 'XMR': Decimal('6.26497244')}, 'Bitsquare/bisq': {'BTC': Decimal('0.20900000')}, 'in_transit': {'BTC': Decimal('0.0100')}} 251 | INFO : Gains: EUR -5.087878350336 252 | 253 | INFO : TRADE #13 254 | INFO : MultiSig payout: a5ed7482 on 2017-02-16 16:16:29+00:00: Acquired 0.01000000 BTC, disposed of 0.00000000 for a fee of 0.00000000 on Bitsquare/Bisq [Received with: 1BVGiDELH2SWpuwb64rqdZx8MymcanJ7oY] 255 | INFO : Processing trade: MultiSig payout: a5ed7482, 2017-02-16 16:16:29+00:00, BTC, 0.01000000, , 0.00000000, , 0.00000000, Bitsquare/Bisq, , Received with: 1BVGiDELH2SWpuwb64rqdZx8MymcanJ7oY 256 | INFO : Depositing 0.01000000 BTC at Bitsquare/Bisq (2017-02-16 16:16:29+00:00, fee: 0.00000000 ) 257 | INFO : State of bags: 258 | exchange date currency amount costcur cost price 259 | id 260 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 261 | 6 Poloniex 2017-02-12 17:35:55+00:00 BTC 0.10363980 EUR 98.10621295 946.60750940 262 | 4 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.00900000 EUR 8.55020288 950.02254202 263 | 8 Bitsquare/bisq 2017-02-12 16:08:11+00:00 BTC 0.01000000 EUR 9.50022542 950.02254202 264 | 7 Bitsquare/bisq 2017-02-14 15:10:17+00:00 BTC 0.20000000 EUR 192.40000000 962.00000000 265 | 266 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.10363980'), 'XMR': Decimal('6.26497244')}, 'Bitsquare/bisq': {'BTC': Decimal('0.21900000')}} 267 | INFO : Gains: EUR -5.087878350336 268 | 269 | INFO : TRADE #14 270 | INFO : Withdrawn from wallet on 2017-02-16 16:38:03+00:00: Acquired 0.00000000 , disposed of 0.21900000 BTC for a fee of 0.00050000 BTC on Bitsquare/Bisq [Sent to: 14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW] 271 | INFO : Processing trade: Withdrawn from wallet, 2017-02-16 16:38:03+00:00, , 0.00000000, BTC, 0.21900000, BTC, 0.00050000, Bitsquare/Bisq, , Sent to: 14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW 272 | INFO : Withdrawing 0.21900000 BTC from Bitsquare/Bisq (2017-02-16 16:38:03+00:00, fee: 0.00050000 BTC) 273 | INFO : Paying 0.00050000 BTC from Bitsquare/bisq (including 0.00050000 BTC fees) 274 | INFO : Paying with bag from 2017-02-12 16:08:11+00:00, containing 0.00900000 BTC 275 | INFO : Contents of bag after payment: 0.00850000 BTC (spent 0.00050000 BTC) 276 | INFO : Profits in this transaction: 277 | Original bag cost: 0.475 EUR (Price 950.02254202 EUR/BTC) 278 | Proceeds : 0.492 EUR (Price 984.13584034 EUR/BTC) 279 | Proceeds w/o fees: 0.000 EUR 280 | Profit : -0.475 EUR 281 | Taxable? : yes (held for less than a year) 282 | INFO : Taxable loss due to fees: -0.475 EUR 283 | INFO : State of bags: 284 | exchange date currency amount costcur cost price 285 | id 286 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 287 | 6 Poloniex 2017-02-12 17:35:55+00:00 BTC 0.10363980 EUR 98.10621295 946.60750940 288 | 4 2017-02-12 16:08:11+00:00 BTC 0.00850000 EUR 8.07519161 950.02254202 289 | 8 2017-02-12 16:08:11+00:00 BTC 0.01000000 EUR 9.50022542 950.02254202 290 | 7 2017-02-14 15:10:17+00:00 BTC 0.20000000 EUR 192.40000000 962.00000000 291 | 292 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.10363980'), 'XMR': Decimal('6.26497244')}, 'in_transit': {'BTC': Decimal('0.21850000')}} 293 | INFO : Gains: EUR -5.562889621344 294 | 295 | INFO : TRADE #15 296 | INFO : Deposit on 2017-02-16 16:55:19+00:00: Acquired 0.21850000 BTC, disposed of 0.00000000 for a fee of 0.00000000 on Poloniex [14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW] 297 | INFO : Processing trade: Deposit, 2017-02-16 16:55:19+00:00, BTC, 0.21850000, , 0.00000000, , 0.00000000, Poloniex, , 14FciuDXA2Vx5UtvyC7JsL4hQd0kwTlzaW 298 | INFO : Depositing 0.21850000 BTC at Poloniex (2017-02-16 16:55:19+00:00, fee: 0.00000000 ) 299 | INFO : State of bags: 300 | exchange date currency amount costcur cost price 301 | id 302 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 6.26497244 EUR 73.55548040 11.74075083 303 | 4 Poloniex 2017-02-12 16:08:11+00:00 BTC 0.00850000 EUR 8.07519161 950.02254202 304 | 8 Poloniex 2017-02-12 16:08:11+00:00 BTC 0.01000000 EUR 9.50022542 950.02254202 305 | 6 Poloniex 2017-02-12 17:35:55+00:00 BTC 0.10363980 EUR 98.10621295 946.60750940 306 | 7 Poloniex 2017-02-14 15:10:17+00:00 BTC 0.20000000 EUR 192.40000000 962.00000000 307 | 308 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.32213980'), 'XMR': Decimal('6.26497244')}} 309 | INFO : Gains: EUR -5.562889621344 310 | 311 | INFO : TRADE #16 312 | INFO : Exchange on 2017-02-17 06:24:48+00:00: Acquired 0.04154643 BTC, disposed of 3.26497244 XMR for a fee of 0.00006241 BTC on Poloniex (Sell) [122421900001] 313 | INFO : Processing trade: Exchange, 2017-02-17 06:24:48+00:00, BTC, 0.04154643, XMR, 3.26497244, BTC, 0.00006241, Poloniex, Sell, 122421900001 314 | INFO : Selling 3.26497244 XMR for 0.04154643 BTC at Poloniex (2017-02-17 06:24:48+00:00, fee: 0.00006241 BTC) 315 | INFO : Fetched historical price data with request: https://poloniex.com/public?start=1487289600&end=1487376000&command=returnTradeHistory¤cyPair=BTC_XMR 316 | INFO : Successfully fetched 10129 trades 317 | INFO : Paying 3.26497244 XMR from Poloniex (including 0.00489720 XMR fees) 318 | INFO : Paying with bag from 2017-02-11 20:21:21+00:00, containing 6.26497244 XMR 319 | INFO : Contents of bag after payment: 3.00000000 XMR (spent 3.26497244 XMR) 320 | INFO : Profits in this transaction: 321 | Original bag cost: 38.333 EUR (Price 11.74075083 EUR/XMR) 322 | Proceeds : 40.975 EUR (Price 12.54973392 EUR/XMR) 323 | Proceeds w/o fees: 40.913 EUR 324 | Profit : 2.580 EUR 325 | Taxable? : yes (held for less than a year) 326 | INFO : State of bags: 327 | exchange date currency amount costcur cost price 328 | id 329 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 3.00000000 EUR 35.22225250 11.74075083 330 | 4 Poloniex 2017-02-12 16:08:11+00:00 BTC 0.00850000 EUR 8.07519161 950.02254202 331 | 8 Poloniex 2017-02-12 16:08:11+00:00 BTC 0.01000000 EUR 9.50022542 950.02254202 332 | 6 Poloniex 2017-02-12 17:35:55+00:00 BTC 0.10363980 EUR 98.10621295 946.60750940 333 | 7 Poloniex 2017-02-14 15:10:17+00:00 BTC 0.20000000 EUR 192.40000000 962.00000000 334 | 9 Poloniex 2017-02-17 06:24:48+00:00 BTC 0.04154643 EUR 40.91307679 984.75553242 335 | 336 | INFO : Totals: {'Poloniex': {'BTC': Decimal('0.36368623'), 'XMR': Decimal('3.00000000')}} 337 | INFO : Gains: EUR -2.983040723717 338 | 339 | INFO : TRADE #17 340 | INFO : Exchange on 2017-02-18 20:31:28+00:00: Acquired 7259.18667370 GNT, disposed of 0.26368623 BTC for a fee of 10.90513771 GNT on Poloniex (Buy) [151600001] 341 | INFO : Processing trade: Exchange, 2017-02-18 20:31:28+00:00, GNT, 7259.18667370, BTC, 0.26368623, GNT, 10.90513771, Poloniex, Buy, 151600001 342 | INFO : Selling 0.26368623 BTC for 7259.18667370 GNT at Poloniex (2017-02-18 20:31:28+00:00, fee: 10.90513771 GNT) 343 | INFO : Paying 0.26368623 BTC from Poloniex (including 0.00039553 BTC fees) 344 | INFO : Paying with bag from 2017-02-12 16:08:11+00:00, containing 0.00850000 BTC 345 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 0.00850000 BTC) 346 | INFO : Profits in this transaction: 347 | Original bag cost: 8.075 EUR (Price 950.02254202 EUR/BTC) 348 | Proceeds : 8.460 EUR (Price 995.34314464 EUR/BTC) 349 | Proceeds w/o fees: 8.448 EUR 350 | Profit : 0.373 EUR 351 | Taxable? : yes (held for less than a year) 352 | INFO : Still to be paid with another bag: 0.25518623 BTC 353 | INFO : Paying with bag from 2017-02-12 16:08:11+00:00, containing 0.01000000 BTC 354 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 0.01000000 BTC) 355 | INFO : Profits in this transaction: 356 | Original bag cost: 9.500 EUR (Price 950.02254202 EUR/BTC) 357 | Proceeds : 9.953 EUR (Price 995.34314464 EUR/BTC) 358 | Proceeds w/o fees: 9.939 EUR 359 | Profit : 0.438 EUR 360 | Taxable? : yes (held for less than a year) 361 | INFO : Still to be paid with another bag: 0.24518623 BTC 362 | INFO : Paying with bag from 2017-02-12 17:35:55+00:00, containing 0.10363980 BTC 363 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 0.10363980 BTC) 364 | INFO : Profits in this transaction: 365 | Original bag cost: 98.106 EUR (Price 946.60750940 EUR/BTC) 366 | Proceeds : 103.157 EUR (Price 995.34314464 EUR/BTC) 367 | Proceeds w/o fees: 103.002 EUR 368 | Profit : 4.896 EUR 369 | Taxable? : yes (held for less than a year) 370 | INFO : Still to be paid with another bag: 0.14154643 BTC 371 | INFO : Paying with bag from 2017-02-14 15:10:17+00:00, containing 0.20000000 BTC 372 | INFO : Contents of bag after payment: 0.05845357 BTC (spent 0.14154643 BTC) 373 | INFO : Profits in this transaction: 374 | Original bag cost: 136.168 EUR (Price 962.00000000 EUR/BTC) 375 | Proceeds : 140.887 EUR (Price 995.34314464 EUR/BTC) 376 | Proceeds w/o fees: 140.676 EUR 377 | Profit : 4.508 EUR 378 | Taxable? : yes (held for less than a year) 379 | INFO : State of bags: 380 | exchange date currency amount costcur cost price 381 | id 382 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 3.00000000 EUR 35.22225250 11.74075083 383 | 7 Poloniex 2017-02-14 15:10:17+00:00 BTC 0.05845357 EUR 56.23233434 962.00000000 384 | 9 Poloniex 2017-02-17 06:24:48+00:00 BTC 0.04154643 EUR 40.91307679 984.75553242 385 | 10 Poloniex 2017-02-18 20:31:28+00:00 GNT 7259.18667370 EUR 262.06459394 0.03610110 386 | 387 | INFO : Totals: {'Poloniex': {'GNT': Decimal('7259.18667370'), 'BTC': Decimal('0.10000000'), 'XMR': Decimal('3.00000000')}} 388 | INFO : Gains: EUR 7.232257579673 389 | 390 | INFO : TRADE #18 391 | INFO : Exchange on 2017-04-30 17:18:31+00:00: Acquired 1.28802254 BTC, disposed of 7259.18667370 GNT for a fee of 0.00193493 BTC on Poloniex (Sell) [2776700001] 392 | INFO : Processing trade: Exchange, 2017-04-30 17:18:31+00:00, BTC, 1.28802254, GNT, 7259.18667370, BTC, 0.00193493, Poloniex, Sell, 2776700001 393 | INFO : Selling 7259.18667370 GNT for 1.28802254 BTC at Poloniex (2017-04-30 17:18:31+00:00, fee: 0.00193493 BTC) 394 | INFO : Fetched historical price data with request: https://poloniex.com/public?start=1493510400&end=1493596800&command=returnTradeHistory¤cyPair=BTC_GNT 395 | INFO : Successfully fetched 50000 trades 396 | INFO : Fetched historical price data with request: https://poloniex.com/public?start=1493510400&end=1493542799&command=returnTradeHistory¤cyPair=BTC_GNT 397 | INFO : Successfully fetched 9179 trades 398 | INFO : Paying 7259.18667370 GNT from Poloniex (including 10.88874509 GNT fees) 399 | INFO : Paying with bag from 2017-02-18 20:31:28+00:00, containing 7259.18667370 GNT 400 | INFO : Contents of bag after payment: 0.00000000 GNT (spent 7259.18667370 GNT) 401 | INFO : Profits in this transaction: 402 | Original bag cost: 262.065 EUR (Price 0.03610110 EUR/GNT) 403 | Proceeds : 1508.511 EUR (Price 0.20780715 EUR/GNT) 404 | Proceeds w/o fees: 1506.248 EUR 405 | Profit : 1244.184 EUR 406 | Taxable? : yes (held for less than a year) 407 | INFO : State of bags: 408 | exchange date currency amount costcur cost price 409 | id 410 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 3.00000000 EUR 35.22225250 11.74075083 411 | 7 Poloniex 2017-02-14 15:10:17+00:00 BTC 0.05845357 EUR 56.23233434 962.00000000 412 | 9 Poloniex 2017-02-17 06:24:48+00:00 BTC 0.04154643 EUR 40.91307679 984.75553242 413 | 11 Poloniex 2017-04-30 17:18:31+00:00 BTC 1.28802254 EUR 1506.24816753 1169.42687007 414 | 415 | INFO : Totals: {'Poloniex': {'BTC': Decimal('1.38802254'), 'XMR': Decimal('3.00000000')}} 416 | INFO : Gains: EUR 1251.415831163435 417 | 418 | INFO : TRADE #19 419 | INFO : Exchange on 2017-05-16 22:25:40+00:00: Acquired 92.57377540 XMR, disposed of 1.38802254 BTC for a fee of 0.13906926 XMR on Poloniex (Buy) [174231700001] 420 | INFO : Processing trade: Exchange, 2017-05-16 22:25:40+00:00, XMR, 92.57377540, BTC, 1.38802254, XMR, 0.13906926, Poloniex, Buy, 174231700001 421 | INFO : Selling 1.38802254 BTC for 92.57377540 XMR at Poloniex (2017-05-16 22:25:40+00:00, fee: 0.13906926 XMR) 422 | INFO : Paying 1.38802254 BTC from Poloniex (including 0.00208203 BTC fees) 423 | INFO : Paying with bag from 2017-02-14 15:10:17+00:00, containing 0.05845357 BTC 424 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 0.05845357 BTC) 425 | INFO : Profits in this transaction: 426 | Original bag cost: 56.232 EUR (Price 962.00000000 EUR/BTC) 427 | Proceeds : 93.411 EUR (Price 1598.03384878 EUR/BTC) 428 | Proceeds w/o fees: 93.271 EUR 429 | Profit : 37.038 EUR 430 | Taxable? : yes (held for less than a year) 431 | INFO : Still to be paid with another bag: 1.32956897 BTC 432 | INFO : Paying with bag from 2017-02-17 06:24:48+00:00, containing 0.04154643 BTC 433 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 0.04154643 BTC) 434 | INFO : Profits in this transaction: 435 | Original bag cost: 40.913 EUR (Price 984.75553242 EUR/BTC) 436 | Proceeds : 66.393 EUR (Price 1598.03384878 EUR/BTC) 437 | Proceeds w/o fees: 66.293 EUR 438 | Profit : 25.380 EUR 439 | Taxable? : yes (held for less than a year) 440 | INFO : Still to be paid with another bag: 1.28802254 BTC 441 | INFO : Paying with bag from 2017-04-30 17:18:31+00:00, containing 1.28802254 BTC 442 | INFO : Contents of bag after payment: 0.00000000 BTC (spent 1.28802254 BTC) 443 | INFO : Profits in this transaction: 444 | Original bag cost: 1506.248 EUR (Price 1169.42687007 EUR/BTC) 445 | Proceeds : 2058.304 EUR (Price 1598.03384878 EUR/BTC) 446 | Proceeds w/o fees: 2055.216 EUR 447 | Profit : 548.968 EUR 448 | Taxable? : yes (held for less than a year) 449 | INFO : State of bags: 450 | exchange date currency amount costcur cost price 451 | id 452 | 2 Poloniex 2017-02-11 20:21:21+00:00 XMR 3.00000000 EUR 35.22225250 11.74075083 453 | 12 Poloniex 2017-05-16 22:25:40+00:00 XMR 92.57377540 EUR 2214.77984145 23.92448436 454 | 455 | INFO : Totals: {'Poloniex': {'XMR': Decimal('95.57377540')}} 456 | INFO : Gains: EUR 1862.802093954361 457 | 458 | INFO : Saved bags' state to status2017.json 459 | INFO : Saved short capital gains report for year 2017 to Report2017.pdf 460 | INFO : Saved short capital gains report for year 2017 to Report2017_de.pdf 461 | INFO : Exported capital gains report data for year 2017 to report_2017.csv 462 | INFO : Saved detailed capital gains report for year 2017 to Details_2017.pdf 463 | INFO : Saved detailed capital gains report for year 2017 to Details_2017_de.pdf 464 | --------------------------------------------------------------------------------