diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ac129dc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+VENV := . .venv/bin/activate &&
+
+create_venv:
+ python3 -m venv .venv
+
+create_db:
+ ${VENV} python manage.py migrate
+ ${VENV} echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.filter(is_superuser=True).exists() or User.objects.create_superuser('admin', 'admin@example.com', 'admin')" | python manage.py shell
+ if [ -f transactions.sql ]; then cat transactions.sql | sqlite3 db.sqlite3; fi
+ if [ -f exchangerates.sql ]; then cat exchangerates.sql | sqlite3 db.sqlite3; fi
+
+clean:
+ rm db.sqlite3
+
+run:
+ ${VENV} python manage.py runserver 0.0.0.0:8000
diff --git a/apps/monitor/calc.py b/apps/monitor/calc.py
index b96952a..42dade8 100644
--- a/apps/monitor/calc.py
+++ b/apps/monitor/calc.py
@@ -64,7 +64,7 @@ class Calc:
out["crypto"] = crypto
with connection.cursor() as cursor:
- sql = """SELECT SUM(amount_after_fee) as after_fee, SUM(amount) as amount FROM `transaction` WHERE amount_after_fee > 0 and crypto_currency=%s and owner_id=%s"""
+ sql = """SELECT SUM(crypto_amount) as crypto_amount, SUM(fiat_amount) as fiat_amount FROM `transaction` WHERE transaction_type = 'B' and crypto_currency=%s and owner_id=%s"""
cursor.execute(sql, [crypto, userid])
bought = cursor.fetchone()
if bought[0]:
@@ -75,24 +75,24 @@ class Calc:
out["bought"] = None
with connection.cursor() as cursor:
- sql = """SELECT SUM(amount_before_fee) as before_fee, SUM(amount) as amount FROM `transaction` WHERE amount_before_fee < 0 and crypto_currency=%s and owner_id=%s"""
+ sql = """SELECT SUM(crypto_amount) as crypto_amount, SUM(fiat_amount) as fiat_amount FROM `transaction` WHERE transaction_type = 'S' and crypto_currency=%s and owner_id=%s"""
cursor.execute(sql, [crypto, userid])
sold = cursor.fetchone()
if sold[0]:
out["sold"] = sold[1]
- total_investment += Decimal(sold[1])
- amount_kept += Decimal(sold[0])
+ total_investment -= Decimal(sold[1])
+ amount_kept -= Decimal(sold[0])
else:
out["sold"] = None
- # moving coins to other wallet with fees.
+ # calculate all fees.
with connection.cursor() as cursor:
- sql = """SELECT SUM(amount_after_fee) as after_fee FROM `transaction` WHERE amount_before_fee = 0 and crypto_currency=%s and owner_id=%s"""
+ sql = """SELECT SUM(crypto_fee) as crypto_fee, SUM(fiat_fee) as fiat_fee FROM `transaction` WHERE crypto_currency=%s and owner_id=%s"""
cursor.execute(sql, [crypto, userid])
- sold = cursor.fetchone()
- if sold[0]:
- # total_investment -= Decimal(sold[1])
- amount_kept += Decimal(sold[0])
+ fees = cursor.fetchone()
+ if fees[0]:
+ total_investment -= Decimal(fees[1])
+ amount_kept -= Decimal(fees[0])
rateEUR = ExchangeRate.objects.filter(crypto_currency=crypto, fiat_currency='EUR').order_by('-datetime_valid').first()
rateUSD = ExchangeRate.objects.filter(crypto_currency=crypto, fiat_currency='USD').order_by('-datetime_valid').first()
@@ -130,14 +130,17 @@ class Calc:
amount_within_past_year = 0
with connection.cursor() as cursor:
- sql = """SELECT SUM(amount_after_fee) as amount FROM `transaction` WHERE amount > 0 and crypto_currency=%s and owner_id=%s and date_valid>%s"""
+ sql = """SELECT SUM(crypto_amount) as amount FROM `transaction` WHERE transaction_type='B' and crypto_currency=%s and owner_id=%s and date_valid>%s"""
cursor.execute(sql, [crypto, userid, datetime.datetime.now() - datetime.timedelta(days=365)])
bought = cursor.fetchone()
out["bought_recently"] = None
if bought[0]:
out["bought_recently"] = bought[0]
amount_within_past_year = Decimal(bought[0])
- amount_available_to_sell = amount_kept - amount_within_past_year
+ if amount_kept > amount_within_past_year:
+ amount_available_to_sell = amount_kept - amount_within_past_year
+ else:
+ amount_available_to_sell = 0
out["bought_tax_free"] = None
out["value_tax_free"] = None
if rateEUR:
diff --git a/apps/monitor/templates/monitor.html b/apps/monitor/templates/monitor.html
index 0e7463c..7c58c1a 100644
--- a/apps/monitor/templates/monitor.html
+++ b/apps/monitor/templates/monitor.html
@@ -64,7 +64,7 @@
We have sold {{ c.crypto }} for {{ c.sold|floatformat:2 }} EUR
{% endif %}
{% if c.amount_kept %}
- Current value of our {{ c.amount_kept|formatcurrency }} {{ c.crypto }} is {{ c.current_value|floatformat:2 }} EUR
+ Current value of our {{ c.amount_kept|formatcurrency }} {{ c.crypto }} is {{ c.current_value|floatformat:2 }} EUR
{% endif %}
{% if c.bought_recently %}
We have bought within the last 12 months: {{ c.bought_recently|formatcurrency }} {{ c.crypto }}
diff --git a/apps/monitor/templatetags/filter.py b/apps/monitor/templatetags/filter.py
index 8445b99..e882f68 100644
--- a/apps/monitor/templatetags/filter.py
+++ b/apps/monitor/templatetags/filter.py
@@ -4,6 +4,8 @@ register = template.Library()
@register.filter
def formatcurrency(value):
+ if not value:
+ return "0.00"
if value > 100:
return "%0.0f" % (value,)
if value < 0.01:
diff --git a/apps/transactions/forms.py b/apps/transactions/forms.py
index 3da3c0a..4c766ba 100644
--- a/apps/transactions/forms.py
+++ b/apps/transactions/forms.py
@@ -1,4 +1,5 @@
from django import forms
+import sys
import datetime
from apps.transactions.models import Transaction
from apps.rates.models import ExchangeRate
@@ -16,15 +17,16 @@ class TransactionForm(forms.ModelForm):
model = Transaction
fields = "__all__"
- exchangerates = ExchangeRate.objects.filter(datetime_valid__gt = datetime.datetime.today() - datetime.timedelta(days=7))
- crypto_currencies = [(x, x) for x in sorted({ex.crypto_currency for ex in exchangerates})]
- fiat_currencies = [(x, x) for x in sorted({ex.fiat_currency for ex in exchangerates})]
-
- widgets = {
- 'fiat_currency': forms.Select(choices = fiat_currencies),
- 'crypto_currency': forms.Select(choices = crypto_currencies),
- }
+ # do not run this code in initial migration, because the database has not been built yet
+ if not "migrate" in sys.argv:
+ exchangerates = ExchangeRate.objects.filter(datetime_valid__gt = datetime.datetime.today() - datetime.timedelta(days=7))
+ crypto_currencies = [(x, x) for x in sorted({ex.crypto_currency for ex in exchangerates})]
+ fiat_currencies = [(x, x) for x in sorted({ex.fiat_currency for ex in exchangerates})]
+ widgets = {
+ 'fiat_currency': forms.Select(choices = fiat_currencies),
+ 'crypto_currency': forms.Select(choices = crypto_currencies),
+ }
class ImportForm(forms.Form):
api_key = forms.CharField(label='API Key', widget=forms.PasswordInput, max_length=100)
diff --git a/apps/transactions/importbtcde.py b/apps/transactions/importbtcde.py
index 39ac107..2c7bef0 100644
--- a/apps/transactions/importbtcde.py
+++ b/apps/transactions/importbtcde.py
@@ -18,7 +18,7 @@ class ImportBtcDe:
break
for trade in trades:
- # TODO: check if this trade_id does not exist in the database for this user
+ # check if this trade_id does not exist in the database for this user
with connection.cursor() as cursor:
sql = """SELECT trade_id FROM `transaction` WHERE trade_id=%s and owner_id=%s"""
cursor.execute(sql, [trade['trade_id'], Owner.id])
@@ -37,14 +37,18 @@ class ImportBtcDe:
if trade['trading_pair'].upper().endswith(f):
t.fiat_currency = f
t.owner = Owner
- t.amount_before_fee = Decimal(trade['amount'])
- t.amount_after_fee = Decimal(trade['amount']) - Decimal(trade['fee_currency'])
- t.amount = Decimal(trade['volume'])
if trade['type'] == 'sell':
- t.amount -= Decimal(trade['fee_eur'])
- t.amount_before_fee *= -1
- t.amount_after_fee *= -1
- t.amount *= -1
+ t.crypto_amount = Decimal(trade['amount_currency_to_trade'])-Decimal(trade['fee_currency_to_trade'])
+ else:
+ t.crypto_amount = Decimal(trade['amount_currency_to_trade'])
+ t.crypto_fee = Decimal(trade['fee_currency_to_trade'])
+ t.fiat_amount = Decimal(trade['volume_currency_to_pay'])
+ t.fiat_fee = Decimal(trade['fee_currency_to_pay'])
+ if trade['type'] == 'sell':
+ t.transaction_type = 'S'
+ else:
+ t.transaction_type = 'B'
+ t.description = 'Bitcoin.de Trade'
t.exchange_rate = trade['price']
t.date_valid = trade['created_at']
t.save()
diff --git a/apps/transactions/migrations/0003_auto_20210511_0703.py b/apps/transactions/migrations/0003_auto_20210511_0703.py
new file mode 100644
index 0000000..5bad782
--- /dev/null
+++ b/apps/transactions/migrations/0003_auto_20210511_0703.py
@@ -0,0 +1,38 @@
+# Generated by Django 3.2.2 on 2021-05-11 05:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('transactions', '0002_transaction_trade_id'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='transaction',
+ name='crypto_amount',
+ field=models.DecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ migrations.AddField(
+ model_name='transaction',
+ name='crypto_fee',
+ field=models.DecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ migrations.AddField(
+ model_name='transaction',
+ name='fiat_amount',
+ field=models.DecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ migrations.AddField(
+ model_name='transaction',
+ name='fiat_fee',
+ field=models.DecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ migrations.AddField(
+ model_name='transaction',
+ name='transaction_type',
+ field=models.CharField(choices=[('B', 'Buy Crypto'), ('S', 'Sell Crypto'), ('T', 'Transfer Crypto')], default='B', max_length=1),
+ ),
+ ]
diff --git a/apps/transactions/migrations/0004_auto_20210511_0703.py b/apps/transactions/migrations/0004_auto_20210511_0703.py
new file mode 100644
index 0000000..e0715fa
--- /dev/null
+++ b/apps/transactions/migrations/0004_auto_20210511_0703.py
@@ -0,0 +1,37 @@
+# Generated by Django 3.2 on 2021-05-11 05:03
+
+from django.db import migrations
+
+def separate_fees(apps, schema_editor):
+
+ Transaction = apps.get_model('transactions', 'Transaction')
+ for tr in Transaction.objects.all():
+ if tr.amount_before_fee > 0:
+ tr.crypto_fee = tr.amount_before_fee - tr.amount_after_fee
+ tr.crypto_amount = tr.amount_before_fee
+ tr.transaction_type = 'B'
+ elif tr.amount_before_fee < 0:
+ tr.crypto_fee = -1 * tr.amount_before_fee + tr.amount_after_fee
+ tr.crypto_amount = -1 * tr.amount_before_fee
+ tr.transaction_type = 'S'
+ else:
+ tr.crypto_fee = 0
+ tr.crypto_amount = 0
+ tr.transaction_type = 'T'
+
+ tr.fiat_amount = tr.amount
+ if tr.fiat_amount < 0:
+ tr.fiat_amount = -1 * tr.fiat_amount
+ tr.fiat_fee = 0
+
+ tr.save()
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('transactions', '0003_auto_20210511_0703'),
+ ]
+
+ operations = [
+ migrations.RunPython(separate_fees),
+ ]
diff --git a/apps/transactions/migrations/0005_auto_20210511_1638.py b/apps/transactions/migrations/0005_auto_20210511_1638.py
new file mode 100644
index 0000000..9fe2dae
--- /dev/null
+++ b/apps/transactions/migrations/0005_auto_20210511_1638.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.2 on 2021-05-11 16:38
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('transactions', '0004_auto_20210511_0703'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='transaction',
+ name='amount',
+ ),
+ migrations.RemoveField(
+ model_name='transaction',
+ name='amount_after_fee',
+ ),
+ migrations.RemoveField(
+ model_name='transaction',
+ name='amount_before_fee',
+ ),
+ ]
diff --git a/apps/transactions/migrations/0006_auto_20210511_2230.py b/apps/transactions/migrations/0006_auto_20210511_2230.py
new file mode 100644
index 0000000..2763be6
--- /dev/null
+++ b/apps/transactions/migrations/0006_auto_20210511_2230.py
@@ -0,0 +1,44 @@
+# Generated by Django 3.2.2 on 2021-05-11 20:30
+
+import apps.transactions.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('transactions', '0005_auto_20210511_1638'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='transaction',
+ name='description',
+ field=models.CharField(blank=True, max_length=100, null=True),
+ ),
+ migrations.AlterField(
+ model_name='transaction',
+ name='crypto_amount',
+ field=apps.transactions.models.NonscientificDecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ migrations.AlterField(
+ model_name='transaction',
+ name='crypto_fee',
+ field=apps.transactions.models.NonscientificDecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ migrations.AlterField(
+ model_name='transaction',
+ name='exchange_rate',
+ field=apps.transactions.models.NonscientificDecimalField(decimal_places=10, max_digits=24),
+ ),
+ migrations.AlterField(
+ model_name='transaction',
+ name='fiat_amount',
+ field=apps.transactions.models.NonscientificDecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ migrations.AlterField(
+ model_name='transaction',
+ name='fiat_fee',
+ field=apps.transactions.models.NonscientificDecimalField(blank=True, decimal_places=10, max_digits=24, null=True),
+ ),
+ ]
diff --git a/apps/transactions/models.py b/apps/transactions/models.py
index d4a82d5..dcee8a9 100644
--- a/apps/transactions/models.py
+++ b/apps/transactions/models.py
@@ -1,16 +1,46 @@
from django.db import models
from django.contrib.auth.models import User
+from decimal import Decimal, Context
+
+class NonscientificDecimalField(models.DecimalField):
+ """ Prevents values from being displayed with E notation, with trailing 0's
+ after the decimal place truncated. (This causes precision to be lost in
+ many cases, but is more user friendly and consistent for non-scientist
+ users)
+ """
+ def value_from_object(self, obj):
+ def remove_exponent(val):
+ """Remove exponent and trailing zeros.
+ >>> remove_exponent(Decimal('5E+3'))
+ Decimal('5000')
+ """
+ context = Context(prec=self.max_digits)
+ return val.quantize(Decimal(1), context=context) if val == val.to_integral() else val.normalize(context)
+
+ val = super(NonscientificDecimalField, self).value_from_object(obj)
+ if isinstance(val, Decimal):
+ return remove_exponent(val)
# Create your models here.
class Transaction(models.Model):
+ TRANSACTION_TYPES = (
+ ('B', 'Buy Crypto'),
+ ('S', 'Sell Crypto'),
+ ('T', 'Transfer Crypto'),
+ )
+
trade_id = models.CharField(max_length=20, default='MANUAL')
- crypto_currency = models.CharField(max_length=10)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
- amount_before_fee = models.DecimalField(max_digits=24, decimal_places=10)
- amount_after_fee = models.DecimalField(max_digits=24, decimal_places=10)
- exchange_rate = models.DecimalField(max_digits=24, decimal_places=10)
+ crypto_currency = models.CharField(max_length=10)
+ crypto_amount = NonscientificDecimalField(max_digits=24, decimal_places=10, null=True, blank=True)
+ crypto_fee = NonscientificDecimalField(max_digits=24, decimal_places=10, null=True, blank=True)
+ exchange_rate = NonscientificDecimalField(max_digits=24, decimal_places=10)
fiat_currency = models.CharField(max_length=10)
- amount = models.DecimalField(max_digits=24, decimal_places=10)
+ fiat_amount = NonscientificDecimalField(max_digits=24, decimal_places=10, null=True, blank=True)
+ fiat_fee = NonscientificDecimalField(max_digits=24, decimal_places=10, null=True, blank=True)
date_valid = models.DateTimeField('date transfered')
+ transaction_type = models.CharField(max_length=1, choices=TRANSACTION_TYPES, default='B')
+ description = models.CharField(max_length=100, null=True, blank=True)
+
class Meta:
db_table = "transaction"
diff --git a/apps/transactions/templates/add.html b/apps/transactions/templates/add.html
index faeb569..74d01d4 100644
--- a/apps/transactions/templates/add.html
+++ b/apps/transactions/templates/add.html
@@ -28,6 +28,13 @@
+