Add Non-KYC Bitcoin price historical records

This commit is contained in:
Reckless_Satoshi 2022-01-07 14:46:30 -08:00
parent b472b4928c
commit eb9042eaa4
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
5 changed files with 96 additions and 31 deletions

View File

@ -2,7 +2,7 @@ from django.contrib import admin
from django_admin_relation_links import AdminChangeLinksMixin from django_admin_relation_links import AdminChangeLinksMixin
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from .models import Order, LNPayment, Profile from .models import Order, LNPayment, Profile, MarketTick
admin.site.unregister(Group) admin.site.unregister(Group)
admin.site.unregister(User) admin.site.unregister(User)
@ -17,8 +17,8 @@ class ProfileInline(admin.StackedInline):
@admin.register(User) @admin.register(User)
class EUserAdmin(UserAdmin): class EUserAdmin(UserAdmin):
inlines = [ProfileInline] inlines = [ProfileInline]
list_display = ('avatar_tag',) + UserAdmin.list_display list_display = ('avatar_tag','id','username','last_login','date_joined','is_staff')
list_display_links = ['username'] list_display_links = ('id','username')
def avatar_tag(self, obj): def avatar_tag(self, obj):
return obj.profile.avatar_tag() return obj.profile.avatar_tag()
@ -27,16 +27,24 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ('id','type','maker_link','taker_link','status','amount','currency','t0_satoshis','is_disputed','is_fiat_sent','created_at','expires_at', 'buyer_invoice_link','maker_bond_link','taker_bond_link','trade_escrow_link') list_display = ('id','type','maker_link','taker_link','status','amount','currency','t0_satoshis','is_disputed','is_fiat_sent','created_at','expires_at', 'buyer_invoice_link','maker_bond_link','taker_bond_link','trade_escrow_link')
list_display_links = ('id','type') list_display_links = ('id','type')
change_links = ('maker','taker','buyer_invoice','maker_bond','taker_invoice','taker_bond','trade_escrow') change_links = ('maker','taker','buyer_invoice','maker_bond','taker_invoice','taker_bond','trade_escrow')
list_filter = ('is_disputed','is_fiat_sent','type','currency','status')
@admin.register(LNPayment) @admin.register(LNPayment)
class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin): class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ('id','concept','status','num_satoshis','type','invoice','expires_at','sender_link','receiver_link') list_display = ('id','concept','status','num_satoshis','type','invoice','expires_at','sender_link','receiver_link')
list_display_links = ('id','concept') list_display_links = ('id','concept')
change_links = ('sender','receiver') change_links = ('sender','receiver')
list_filter = ('type','concept','status')
@admin.register(Profile) @admin.register(Profile)
class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin): class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ('avatar_tag','id','user_link','total_ratings','avg_rating','num_disputes','lost_disputes') list_display = ('avatar_tag','id','user_link','total_ratings','avg_rating','num_disputes','lost_disputes')
list_display_links = ('avatar_tag','id') list_display_links = ('avatar_tag','id')
change_links =['user'] change_links =['user']
readonly_fields = ['avatar_tag'] readonly_fields = ['avatar_tag']
@admin.register(MarketTick)
class MarketTickAdmin(admin.ModelAdmin):
list_display = ('timestamp','price','volume','premium','currency','fee')
readonly_fields = ('timestamp','price','volume','premium','currency','fee')
list_filter = ['currency']

View File

@ -1,10 +1,10 @@
from datetime import timedelta from datetime import timedelta
from django.utils import timezone from django.utils import timezone
import requests
from .lightning import LNNode from .lightning import LNNode
from .models import Order, LNPayment, User from .models import Order, LNPayment, MarketTick, User
from decouple import config from decouple import config
from .utils import get_exchange_rate
FEE = float(config('FEE')) FEE = float(config('FEE'))
BOND_SIZE = float(config('BOND_SIZE')) BOND_SIZE = float(config('BOND_SIZE'))
@ -61,11 +61,9 @@ class Logics():
if order.is_explicit: if order.is_explicit:
satoshis_now = order.satoshis satoshis_now = order.satoshis
else: else:
# TODO Add fallback Public APIs and error handling exchange_rate = get_exchange_rate(Order.Currencies(order.currency).label)
# Think about polling price data in a different way (e.g. store locally every t seconds) premium_rate = exchange_rate * (1+float(order.premium)/100)
market_prices = requests.get(MARKET_PRICE_API).json() satoshis_now = (float(order.amount) / premium_rate) * 100*1000*1000
exchange_rate = float(market_prices[Order.Currencies(order.currency).label]['last'])
satoshis_now = ((float(order.amount) * 1+float(order.premium)) / exchange_rate) * 100*1000*1000
return int(satoshis_now) return int(satoshis_now)
@ -306,7 +304,7 @@ class Logics():
order.save() order.save()
return True, {'escrow_invoice':invoice,'escrow_satoshis': escrow_satoshis} return True, {'escrow_invoice':invoice,'escrow_satoshis': escrow_satoshis}
def settle_escrow(order): def settle_escrow(order):
''' Settles the trade escrow HTLC''' ''' Settles the trade escrow HTLC'''
# TODO ERROR HANDLING # TODO ERROR HANDLING

View File

@ -7,18 +7,14 @@ from django.utils.html import mark_safe
from decouple import config from decouple import config
from pathlib import Path from pathlib import Path
from .utils import get_exchange_rate
#############################
# TODO
# Load hparams from .env file
MIN_TRADE = int(config('MIN_TRADE')) MIN_TRADE = int(config('MIN_TRADE'))
MAX_TRADE = int(config('MAX_TRADE')) MAX_TRADE = int(config('MAX_TRADE'))
FEE = float(config('FEE')) FEE = float(config('FEE'))
BOND_SIZE = float(config('BOND_SIZE')) BOND_SIZE = float(config('BOND_SIZE'))
class LNPayment(models.Model): class LNPayment(models.Model):
class Types(models.IntegerChoices): class Types(models.IntegerChoices):
@ -192,3 +188,48 @@ class Profile(models.Model):
def avatar_tag(self): def avatar_tag(self):
return mark_safe('<img src="%s" width="50" height="50" />' % self.get_avatar()) return mark_safe('<img src="%s" width="50" height="50" />' % self.get_avatar())
class MarketTick(models.Model):
'''
Records tick by tick Non-KYC Bitcoin price.
Data to be aggregated and offered via public API.
It is checked against current cex prices for nice
insight on the historical premium of Non-KYC BTC
Price is set when both taker bond is locked. Both
maker and taker are commited with bonds (contract
is finished and cancellation has a cost)
'''
price = models.DecimalField(max_digits=10, decimal_places=2, default=None, null=True, validators=[MinValueValidator(0)])
volume = models.DecimalField(max_digits=8, decimal_places=8, default=None, null=True, validators=[MinValueValidator(0)])
premium = models.DecimalField(max_digits=5, decimal_places=2, default=None, null=True, validators=[MinValueValidator(-100), MaxValueValidator(999)], blank=True)
currency = models.PositiveSmallIntegerField(choices=Order.Currencies.choices, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
# Relevant to keep record of the historical fee, so the insight on the premium can be better analyzed
fee = models.DecimalField(max_digits=4, decimal_places=4, default=FEE, validators=[MinValueValidator(0), MaxValueValidator(1)])
def log_a_tick(order):
'''
Adds a new tick
'''
if not order.taker_bond:
return None
elif order.taker_bond.status == LNPayment.Status.LOCKED:
volume = order.last_satoshis / 100000000
price = float(order.amount) / volume # Amount Fiat / Amount BTC
premium = 100 * (price / get_exchange_rate(Order.Currencies(order.currency).label) - 1)
tick = MarketTick.objects.create(
price=price,
volume=volume,
premium=premium,
currency=order.currency)
tick.save()
def __str__(self):
return f'Tick: {self.id}'

12
api/utils.py Normal file
View File

@ -0,0 +1,12 @@
from decouple import config
import requests
def get_exchange_rate(currency):
# TODO Add fallback Public APIs and error handling
# Think about polling price data in a different way (e.g. store locally every t seconds)
market_prices = requests.get(config('MARKET_PRICE_API')).json()
exchange_rate = float(market_prices[currency]['last'])
return exchange_rate

View File

@ -322,23 +322,27 @@ class UserView(APIView):
# It is unlikely, but maybe the nickname is taken (1 in 20 Billion change) # It is unlikely, but maybe the nickname is taken (1 in 20 Billion change)
context['found'] = 'Bad luck, this nickname is taken' context['found'] = 'Bad luck, this nickname is taken'
context['bad_request'] = 'Enter a different token' context['bad_request'] = 'Enter a different token'
return Response(context, status=status.HTTP_403_FORBIDDEN) return Response(context, status.HTTP_403_FORBIDDEN)
def delete(self,request): def delete(self,request):
user = User.objects.get(id = request.user.id) ''' Pressing "give me another" deletes the logged in user '''
user = request.user
if not user:
return Response(status.HTTP_403_FORBIDDEN)
# TO DO. Pressing "give me another" deletes the logged in user # Only delete if user life is shorter than 30 minutes. Helps deleting users by mistake
# However it might be a long time recovered user if user.date_joined < (timezone.now() - timedelta(minutes=30)):
# Only delete if user live is < 5 minutes return Response(status.HTTP_400_BAD_REQUEST)
# TODO check if user exists AND it is not a maker or taker! # Check if it is not a maker or taker!
if user is not None: if not Logics.validate_already_maker_or_taker(user):
logout(request) return Response({'bad_request':'User cannot be deleted while he is part of an order'}, status.HTTP_400_BAD_REQUEST)
user.delete()
return Response({'user_deleted':'User deleted permanently'},status=status.HTTP_302_FOUND) logout(request)
user.delete()
return Response({'user_deleted':'User deleted permanently'}, status.HTTP_301_MOVED_PERMANENTLY)
return Response(status=status.HTTP_403_FORBIDDEN)
class BookView(ListAPIView): class BookView(ListAPIView):
serializer_class = ListOrderSerializer serializer_class = ListOrderSerializer
@ -367,14 +371,16 @@ class BookView(ListAPIView):
class InfoView(ListAPIView): class InfoView(ListAPIView):
def get(self, request, format = None): def get(self):
context = {} context = {}
context['num_public_buy_orders'] = len(Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB)) context['num_public_buy_orders'] = len(Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB))
context['num_public_sell_orders'] = len(Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB)) context['num_public_sell_orders'] = len(Order.objects.filter(type=Order.Types.BUY, status=Order.Status.PUB))
context['num_active_robots'] = None # Todo context['last_day_avg_btc_premium'] = None # Todo
context['num_active_robots'] = None
context['total_volume'] = None context['total_volume'] = None
return Response(context, status.HTTP_200_ok) return Response(context, status.HTTP_200_OK)