Skip to content

Commit

Permalink
Implement fixed fee
Browse files Browse the repository at this point in the history
  • Loading branch information
dmytro-samoylenko committed Aug 6, 2024
1 parent 72f8c29 commit ff96ec9
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 254 deletions.
34 changes: 34 additions & 0 deletions migrations/versions/cd6076e578ca_add_fee_policies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Add fee policies
Revision ID: cd6076e578ca
Revises: 0319bf7b9426
Create Date: 2024-07-31 10:39:30.170808
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'cd6076e578ca'
down_revision = '0319bf7b9426'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('exchange_rate', schema=None) as batch_op:
batch_op.add_column(sa.Column('fixed_fee', sa.Numeric(), nullable=True))
batch_op.add_column(sa.Column('fee_policy', sa.Enum('NO_FEE', 'PERCENT_FEE', 'FIXED_FEE', 'PERCENT_OR_MINIMAL_FIXED_FEE', name='feecalculationpolicy'), nullable=True))

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('exchange_rate', schema=None) as batch_op:
batch_op.drop_column('fee_policy')
batch_op.drop_column('fixed_fee')

# ### end Alembic commands ###
20 changes: 18 additions & 2 deletions shkeeper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from flask import Flask
import requests

from shkeeper.wallet_encryption import WalletEncryptionRuntimeStatus

from .utils import format_decimal

from flask_apscheduler import APScheduler
Expand Down Expand Up @@ -45,6 +47,8 @@ def create_app(test_config=None):
UNCONFIRMED_TX_NOTIFICATION=bool(os.environ.get('UNCONFIRMED_TX_NOTIFICATION')),
REQUESTS_TIMEOUT=int(os.environ.get('REQUESTS_TIMEOUT', 10)),
REQUESTS_NOTIFICATION_TIMEOUT=int(os.environ.get('REQUESTS_NOTIFICATION_TIMEOUT', 30)),
DEV_MODE=bool(os.environ.get('DEV_MODE', False)),
DEV_MODE_ENC_PW=os.environ.get('DEV_MODE_ENC_PW'),
)

if test_config is None:
Expand All @@ -62,7 +66,10 @@ def create_app(test_config=None):

# clear all session on app restart
if sess_dir := app.config.get('SESSION_FILE_DIR'):
shutil.rmtree(sess_dir, ignore_errors=True)
if app.config.get('DEV_MODE'):
pass
else:
shutil.rmtree(sess_dir, ignore_errors=True)
from flask_session import Session
Session(app)

Expand Down Expand Up @@ -100,7 +107,6 @@ def default(self, obj):
from .models import Wallet, User, PayoutDestination, Invoice, ExchangeRate, Setting
db.create_all()

flask_migrate.upgrade()

# Create default user
default_user = 'admin'
Expand All @@ -109,6 +115,10 @@ def default(self, obj):
db.session.add(admin)
db.session.commit()

flask_migrate.stamp(revision='head')
else:
flask_migrate.upgrade()

# Register rate sources
import shkeeper.modules.rates

Expand Down Expand Up @@ -136,6 +146,12 @@ def default(self, obj):
db.session.commit()
app.logger.info(f'WalletEncryptionPersistentStatus is set to {WalletEncryptionPersistentStatus(int(setting.value))}')

if app.config.get('DEV_MODE'):
if wallet_encryption.wallet_encryption.persistent_status() is WalletEncryptionPersistentStatus.enabled:
if key := app.config.get('DEV_MODE_ENC_PW'):
wallet_encryption.wallet_encryption.set_key(key)
wallet_encryption.wallet_encryption.set_runtime_status(WalletEncryptionRuntimeStatus.success)

from . import tasks
scheduler.start()

Expand Down
32 changes: 30 additions & 2 deletions shkeeper/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import namedtuple
import enum
from datetime import datetime, timedelta
from decimal import Decimal
Expand Down Expand Up @@ -102,13 +103,26 @@ def do_payout(self):
return res


class FeeCalculationPolicy(namedtuple('FeeCalculationPolicy', 'name desc'), enum.Enum):
NO_FEE = 'NO_FEE', 'No fee'
PERCENT_FEE = 'PERCENT_FEE', 'Percent'
FIXED_FEE = 'FIXED_FEE', 'Fixed fee'
PERCENT_OR_MINIMAL_FIXED_FEE = 'PERCENT_OR_MINIMAL_FIXED_FEE', 'Percent but not less than a minimal fixed fee'

def __str__(self) -> str:
return self.name


class ExchangeRate(db.Model):
id = db.Column(db.Integer, primary_key=True)
source = db.Column(db.String, default='dynamic') # manual or dynamic (binance, etc)
crypto = db.Column(db.String)
fiat = db.Column(db.String)
rate = db.Column(db.Numeric, default=0) # crypto / fiat, only used is source is manual
fee = db.Column(db.Numeric, default=2)
fee = db.Column(db.Numeric, default=2) # percent
fixed_fee = db.Column(db.Numeric, default=0)
fee_policy = db.Column(db.Enum(FeeCalculationPolicy), default=FeeCalculationPolicy.PERCENT_FEE)

__table_args__ = (db.UniqueConstraint('crypto', 'fiat'), )

def get_rate(self):
Expand All @@ -118,9 +132,23 @@ def get_rate(self):
rs = RateSource.instances.get(self.source, RateSource.instances.get('binance'))
return rs.get_rate(self.fiat, self.crypto)

def get_fee(self, amount: Decimal) -> Decimal:
fcp = FeeCalculationPolicy
if self.fee_policy == fcp.NO_FEE:
return Decimal(0)
percent_fee = Decimal(amount * (self.fee / 100))
if self.fee_policy is None or self.fee_policy == fcp.PERCENT_FEE:
return percent_fee
if self.fee_policy == fcp.FIXED_FEE:
return self.fixed_fee
if self.fee_policy == fcp.PERCENT_OR_MINIMAL_FIXED_FEE:
return Decimal(max(percent_fee, self.fixed_fee))
raise Exception(f'Unexpected fee policy: {self.fee_policy}')

def convert(self, amount):
rate = self.get_rate()
converted = (amount / rate) * (1 + (self.fee / 100))
converted = amount / rate
converted += self.get_fee(converted) # add fee
crypto = Crypto.instances[self.crypto]
converted = round(converted, crypto.precision)
return (converted, rate)
Expand Down
4 changes: 2 additions & 2 deletions shkeeper/modules/classes/tron_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def balance(self):
).json(parse_float=Decimal)
balance = response['balance']
except Exception as e:
app.logger.warning(f"Response: {response} Error: {e}")
app.logger.exception('balance error')
balance = False

return Decimal(balance)
Expand Down Expand Up @@ -74,7 +74,7 @@ def getaddrbytx(self, txid):
auth=self.get_auth_creds(),
).json(parse_float=Decimal)
return [[response['address'], Decimal(response['amount']), response['confirmations'], response['category']]]

def get_confirmations_by_txid(self, txid):
transactions = self.getaddrbytx(txid)
_, _, confirmations, _ = transactions[0]
Expand Down
Loading

0 comments on commit ff96ec9

Please sign in to comment.