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.contrib.auth.models import Group, User
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(User)
@ -17,8 +17,8 @@ class ProfileInline(admin.StackedInline):
@admin.register(User)
class EUserAdmin(UserAdmin):
inlines = [ProfileInline]
list_display = ('avatar_tag',) + UserAdmin.list_display
list_display_links = ['username']
list_display = ('avatar_tag','id','username','last_login','date_joined','is_staff')
list_display_links = ('id','username')
def avatar_tag(self, obj):
return obj.profile.avatar_tag()
@ -27,12 +27,14 @@ 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_links = ('id','type')
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)
class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display = ('id','concept','status','num_satoshis','type','invoice','expires_at','sender_link','receiver_link')
list_display_links = ('id','concept')
change_links = ('sender','receiver')
list_filter = ('type','concept','status')
@admin.register(Profile)
class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
@ -40,3 +42,9 @@ class UserProfileAdmin(AdminChangeLinksMixin, admin.ModelAdmin):
list_display_links = ('avatar_tag','id')
change_links =['user']
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 django.utils import timezone
import requests
from .lightning import LNNode
from .models import Order, LNPayment, User
from .models import Order, LNPayment, MarketTick, User
from decouple import config
from .utils import get_exchange_rate
FEE = float(config('FEE'))
BOND_SIZE = float(config('BOND_SIZE'))
@ -61,11 +61,9 @@ class Logics():
if order.is_explicit:
satoshis_now = order.satoshis
else:
# 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(MARKET_PRICE_API).json()
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
exchange_rate = get_exchange_rate(Order.Currencies(order.currency).label)
premium_rate = exchange_rate * (1+float(order.premium)/100)
satoshis_now = (float(order.amount) / premium_rate) * 100*1000*1000
return int(satoshis_now)

View File

@ -7,18 +7,14 @@ from django.utils.html import mark_safe
from decouple import config
from pathlib import Path
from .utils import get_exchange_rate
#############################
# TODO
# Load hparams from .env file
MIN_TRADE = int(config('MIN_TRADE'))
MAX_TRADE = int(config('MAX_TRADE'))
FEE = float(config('FEE'))
BOND_SIZE = float(config('BOND_SIZE'))
class LNPayment(models.Model):
class Types(models.IntegerChoices):
@ -192,3 +188,48 @@ class Profile(models.Model):
def avatar_tag(self):
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)
context['found'] = 'Bad luck, this nickname is taken'
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):
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
# However it might be a long time recovered user
# Only delete if user live is < 5 minutes
# Only delete if user life is shorter than 30 minutes. Helps deleting users by mistake
if user.date_joined < (timezone.now() - timedelta(minutes=30)):
return Response(status.HTTP_400_BAD_REQUEST)
# TODO check if user exists AND it is not a maker or taker!
if user is not None:
logout(request)
user.delete()
# Check if it is not a maker or taker!
if not Logics.validate_already_maker_or_taker(user):
return Response({'bad_request':'User cannot be deleted while he is part of an order'}, status.HTTP_400_BAD_REQUEST)
logout(request)
user.delete()
return Response({'user_deleted':'User deleted permanently'}, status.HTTP_301_MOVED_PERMANENTLY)
return Response({'user_deleted':'User deleted permanently'},status=status.HTTP_302_FOUND)
return Response(status=status.HTTP_403_FORBIDDEN)
class BookView(ListAPIView):
serializer_class = ListOrderSerializer
@ -367,14 +371,16 @@ class BookView(ListAPIView):
class InfoView(ListAPIView):
def get(self, request, format = None):
def get(self):
context = {}
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_active_robots'] = None # Todo
context['last_day_avg_btc_premium'] = None # Todo
context['num_active_robots'] = None
context['total_volume'] = None
return Response(context, status.HTTP_200_ok)
return Response(context, status.HTTP_200_OK)