|
@@ -6,7 +6,7 @@ import sqlite3 as db
|
|
|
import uuid
|
|
|
from datetime import datetime
|
|
|
from logging import INFO
|
|
|
-from math import floor
|
|
|
+from math import floor, inf
|
|
|
from shutil import copyfile
|
|
|
from typing import Optional, Dict
|
|
|
|
|
@@ -177,7 +177,7 @@ def send_ownable(from_user_id, to_user_id, ownable_id, amount):
|
|
|
|
|
|
own(to_user_id, ownable_name_by_id(ownable_id))
|
|
|
|
|
|
- if to_user_id != bank_id_ and not is_bond_of_user(ownable_id, to_user_id):
|
|
|
+ if not is_bond_of_user(ownable_id, to_user_id):
|
|
|
execute('''
|
|
|
UPDATE ownership
|
|
|
SET amount = amount + ?
|
|
@@ -352,17 +352,17 @@ def next_mro_dt(dt=None):
|
|
|
if dt is None:
|
|
|
dt = current_db_timestamp()
|
|
|
return execute('''
|
|
|
- SELECT MIN(t.maturity_dt) FROM tender_calendar t WHERE t.maturity_dt > ?
|
|
|
+ SELECT MIN(t.dt) FROM tender_calendar t WHERE t.dt > ?
|
|
|
''', (dt,)).fetchone()[0]
|
|
|
|
|
|
|
|
|
def next_mro_interest(dt=None):
|
|
|
return execute('''
|
|
|
- SELECT t.mro_interest FROM tender_calendar t WHERE t.maturity_dt = ?
|
|
|
+ SELECT t.mro_interest FROM tender_calendar t WHERE t.dt = ?
|
|
|
''', (next_mro_dt(dt),)).fetchone()[0]
|
|
|
|
|
|
|
|
|
-def bonds(issuer_id=None, only_next_mro_qualified=False):
|
|
|
+def credits(issuer_id=None, only_next_mro_qualified=False):
|
|
|
if issuer_id is not None:
|
|
|
issuer_condition = 'issuer.rowid = ?'
|
|
|
issuer_params = (issuer_id,)
|
|
@@ -370,14 +370,14 @@ def bonds(issuer_id=None, only_next_mro_qualified=False):
|
|
|
issuer_condition = '1'
|
|
|
issuer_params = ()
|
|
|
if only_next_mro_qualified:
|
|
|
- only_next_mro_condition = ''' -- noinspection SqlResolve @ any/"bonds"
|
|
|
+ only_next_mro_condition = ''' -- noinspection SqlResolve @ any/"credits"
|
|
|
SELECT EXISTS(
|
|
|
SELECT *
|
|
|
FROM banks b
|
|
|
- JOIN tender_calendar t ON t.maturity_dt = bonds.maturity_dt
|
|
|
- WHERE bonds.issuer_id = b.user_id
|
|
|
- AND bonds.coupon >= t.mro_interest
|
|
|
- AND t.maturity_dt = ?
|
|
|
+ JOIN tender_calendar t ON t.maturity_dt = credits.maturity_dt
|
|
|
+ WHERE credits.issuer_id = b.user_id
|
|
|
+ AND credits.coupon >= t.mro_interest
|
|
|
+ AND t.dt = ?
|
|
|
)
|
|
|
'''
|
|
|
only_next_mro_params = (next_mro_dt(),)
|
|
@@ -390,9 +390,9 @@ def bonds(issuer_id=None, only_next_mro_qualified=False):
|
|
|
coupon,
|
|
|
datetime(maturity_dt, 'unixepoch', 'localtime'),
|
|
|
username
|
|
|
- FROM bonds
|
|
|
- JOIN ownables o on bonds.ownable_id = o.rowid
|
|
|
- JOIN users issuer on bonds.issuer_id = issuer.rowid
|
|
|
+ FROM credits
|
|
|
+ JOIN ownables o on credits.ownable_id = o.rowid
|
|
|
+ JOIN users issuer on credits.issuer_id = issuer.rowid
|
|
|
WHERE ({issuer_condition})
|
|
|
AND ({only_next_mro_condition})
|
|
|
ORDER BY coupon * (maturity_dt - ?) DESC
|
|
@@ -455,7 +455,7 @@ def available_amount(user_id, ownable_id):
|
|
|
def is_bond_of_user(ownable_id, user_id):
|
|
|
execute('''
|
|
|
SELECT EXISTS(
|
|
|
- SELECT * FROM bonds
|
|
|
+ SELECT * FROM credits
|
|
|
WHERE ownable_id = ?
|
|
|
AND issuer_id = ?
|
|
|
)
|
|
@@ -464,26 +464,33 @@ def is_bond_of_user(ownable_id, user_id):
|
|
|
return current_cursor.fetchone()[0]
|
|
|
|
|
|
|
|
|
-def user_has_at_least_available(amount, user_id, ownable_id):
|
|
|
+def user_available_ownable(user_id, ownable_id):
|
|
|
if is_bond_of_user(ownable_id, user_id):
|
|
|
- return True
|
|
|
+ return inf
|
|
|
|
|
|
- if not isinstance(amount, float) and not isinstance(amount, int):
|
|
|
- # comparison of float with strings does not work so well in sql
|
|
|
- raise AssertionError()
|
|
|
+ if ownable_id == currency_id() and user_has_banking_license(user_id):
|
|
|
+ minimum_reserve = required_minimum_reserve(user_id) + sell_ordered_amount(user_id, ownable_id)
|
|
|
+ else:
|
|
|
+ minimum_reserve = sell_ordered_amount(user_id, ownable_id)
|
|
|
|
|
|
execute('''
|
|
|
- SELECT rowid
|
|
|
+ SELECT amount
|
|
|
FROM ownership
|
|
|
WHERE user_id = ?
|
|
|
AND ownable_id = ?
|
|
|
- AND amount - ? >= ?
|
|
|
- ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
|
|
|
+ ''', (user_id, ownable_id))
|
|
|
+
|
|
|
+ return current_cursor.fetchone()[0] - minimum_reserve
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+def user_has_at_least_available(amount, user_id, ownable_id):
|
|
|
+ if not isinstance(amount, float) and not isinstance(amount, int):
|
|
|
+ # comparison of float with strings does not work so well in sql
|
|
|
+ raise ValueError()
|
|
|
+
|
|
|
+ return user_available_ownable(user_id, ownable_id) >= amount
|
|
|
|
|
|
- if current_cursor.fetchone():
|
|
|
- return True
|
|
|
- else:
|
|
|
- return False
|
|
|
|
|
|
|
|
|
def news():
|
|
@@ -579,14 +586,7 @@ def currency_id():
|
|
|
|
|
|
|
|
|
def user_money(user_id):
|
|
|
- execute('''
|
|
|
- SELECT amount
|
|
|
- FROM ownership
|
|
|
- WHERE user_id = ?
|
|
|
- AND ownable_id = ?
|
|
|
- ''', (user_id, currency_id()))
|
|
|
-
|
|
|
- return current_cursor.fetchone()[0]
|
|
|
+ return user_available_ownable(user_id, currency_id())
|
|
|
|
|
|
|
|
|
def delete_order(order_id, new_order_status):
|
|
@@ -1268,21 +1268,21 @@ def pay_bond_interest(until=None):
|
|
|
SELECT
|
|
|
SUM(amount * coupon * (MIN(CAST(? AS FLOAT), maturity_dt) - last_interest_pay_dt) / ?) AS interest_since_last_pay,
|
|
|
o.user_id AS to_user_id,
|
|
|
- bonds.issuer_id AS from_user_id
|
|
|
- FROM bonds
|
|
|
- JOIN ownership o on bonds.ownable_id = o.ownable_id
|
|
|
+ credits.issuer_id AS from_user_id
|
|
|
+ FROM credits
|
|
|
+ JOIN ownership o on credits.ownable_id = o.ownable_id
|
|
|
WHERE ? - last_interest_pay_dt > ? OR ? > maturity_dt -- every interval or when the bond expired
|
|
|
AND amount != 0
|
|
|
- GROUP BY o.user_id, bonds.issuer_id
|
|
|
+ GROUP BY o.user_id, credits.issuer_id
|
|
|
''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL, current_dt)).fetchall()
|
|
|
|
|
|
- matured_bonds = execute('''
|
|
|
+ matured_credits = execute('''
|
|
|
SELECT
|
|
|
amount,
|
|
|
o.user_id AS to_user_id,
|
|
|
- bonds.issuer_id AS from_user_id
|
|
|
- FROM bonds
|
|
|
- JOIN ownership o on bonds.ownable_id = o.ownable_id
|
|
|
+ credits.issuer_id AS from_user_id
|
|
|
+ FROM credits
|
|
|
+ JOIN ownership o on credits.ownable_id = o.ownable_id
|
|
|
WHERE ? > maturity_dt
|
|
|
''', (current_dt,)).fetchall()
|
|
|
|
|
@@ -1290,21 +1290,21 @@ def pay_bond_interest(until=None):
|
|
|
for amount, to_user_id, from_user_id in interests:
|
|
|
send_ownable(from_user_id, to_user_id, currency_id(), amount)
|
|
|
|
|
|
- # pay back matured bonds
|
|
|
- for amount, to_user_id, from_user_id in matured_bonds:
|
|
|
+ # pay back matured credits
|
|
|
+ for amount, to_user_id, from_user_id in matured_credits:
|
|
|
send_ownable(from_user_id, to_user_id, currency_id(), amount)
|
|
|
|
|
|
execute('''
|
|
|
- UPDATE bonds
|
|
|
+ UPDATE credits
|
|
|
SET last_interest_pay_dt = ?
|
|
|
WHERE ? - last_interest_pay_dt > ?''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
|
|
|
|
|
|
- # delete matured bonds
|
|
|
+ # delete matured credits
|
|
|
execute('''
|
|
|
DELETE FROM transactions
|
|
|
WHERE ownable_id IN (
|
|
|
SELECT ownable_id
|
|
|
- FROM bonds
|
|
|
+ FROM credits
|
|
|
WHERE ? > maturity_dt
|
|
|
)
|
|
|
''', (current_dt,))
|
|
@@ -1312,8 +1312,8 @@ def pay_bond_interest(until=None):
|
|
|
DELETE FROM orders
|
|
|
WHERE ownership_id IN (
|
|
|
SELECT o2.rowid
|
|
|
- FROM bonds
|
|
|
- JOIN ownables o on bonds.ownable_id = o.rowid
|
|
|
+ FROM credits
|
|
|
+ JOIN ownables o on credits.ownable_id = o.rowid
|
|
|
JOIN ownership o2 on o.rowid = o2.ownable_id
|
|
|
WHERE ? > maturity_dt
|
|
|
)
|
|
@@ -1322,8 +1322,8 @@ def pay_bond_interest(until=None):
|
|
|
DELETE FROM order_history
|
|
|
WHERE ownership_id IN (
|
|
|
SELECT o2.rowid
|
|
|
- FROM bonds
|
|
|
- JOIN ownables o on bonds.ownable_id = o.rowid
|
|
|
+ FROM credits
|
|
|
+ JOIN ownables o on credits.ownable_id = o.rowid
|
|
|
JOIN ownership o2 on o.rowid = o2.ownable_id
|
|
|
WHERE ? > maturity_dt
|
|
|
)
|
|
@@ -1332,7 +1332,7 @@ def pay_bond_interest(until=None):
|
|
|
DELETE FROM ownership
|
|
|
WHERE ownable_id IN (
|
|
|
SELECT ownable_id
|
|
|
- FROM bonds
|
|
|
+ FROM credits
|
|
|
WHERE ? > maturity_dt
|
|
|
)
|
|
|
''', (current_dt,))
|
|
@@ -1340,12 +1340,12 @@ def pay_bond_interest(until=None):
|
|
|
DELETE FROM ownables
|
|
|
WHERE rowid IN (
|
|
|
SELECT ownable_id
|
|
|
- FROM bonds
|
|
|
+ FROM credits
|
|
|
WHERE ? > maturity_dt
|
|
|
)
|
|
|
''', (current_dt,))
|
|
|
execute('''
|
|
|
- DELETE FROM bonds
|
|
|
+ DELETE FROM credits
|
|
|
WHERE ? > maturity_dt
|
|
|
''', (current_dt,))
|
|
|
|
|
@@ -1392,17 +1392,17 @@ def triggered_mros():
|
|
|
|
|
|
|
|
|
def mro(mro_id, expiry, min_interest):
|
|
|
- qualified_bonds = execute('''
|
|
|
- SELECT bonds.ownable_id
|
|
|
- FROM bonds
|
|
|
- JOIN banks b ON bonds.issuer_id = b.user_id
|
|
|
- JOIN ownership o ON o.ownable_id = bonds.ownable_id -- AND bonds.issuer_id = o.user_id
|
|
|
+ qualified_credits = execute('''
|
|
|
+ SELECT credits.ownable_id
|
|
|
+ FROM credits
|
|
|
+ JOIN banks b ON credits.issuer_id = b.user_id
|
|
|
+ JOIN ownership o ON o.ownable_id = credits.ownable_id -- AND credits.issuer_id = o.user_id
|
|
|
JOIN orders o2 ON o.rowid = o2.ownership_id AND NOT o2.buy
|
|
|
WHERE maturity_dt = ?
|
|
|
AND coupon >= ?
|
|
|
AND "limit" IS NULL or "limit" <= 1
|
|
|
''', (expiry, min_interest)).fetchall()
|
|
|
- for ownable_id, amount in qualified_bonds:
|
|
|
+ for ownable_id, amount in qualified_credits:
|
|
|
bank_order(buy=True,
|
|
|
ownable_id=ownable_id,
|
|
|
limit=1,
|
|
@@ -1475,16 +1475,30 @@ def tender_calendar():
|
|
|
return execute('''
|
|
|
SELECT dt, mro_interest, maturity_dt
|
|
|
FROM tender_calendar
|
|
|
+ ORDER BY dt DESC
|
|
|
+ LIMIT 20
|
|
|
''', ).fetchall()
|
|
|
|
|
|
|
|
|
+def required_minimum_reserve(user_id):
|
|
|
+ assert user_has_banking_license(user_id)
|
|
|
+ borrowed_money = execute('''
|
|
|
+ SELECT SUM(amount)
|
|
|
+ FROM ownership
|
|
|
+ JOIN credits b on ownership.ownable_id = b.ownable_id
|
|
|
+ WHERE b.issuer_id = ?
|
|
|
+ AND ownership.user_id = ?
|
|
|
+ ''', (user_id, bank_id())).fetchone()[0]
|
|
|
+ return min(0, global_control_value('cash_reserve_ratio') * borrowed_money - global_control_value('cash_reserve_free_amount'))
|
|
|
+
|
|
|
+
|
|
|
def issue_bond(user_id, ownable_name, coupon, maturity_dt):
|
|
|
execute('''
|
|
|
INSERT INTO ownables(name)
|
|
|
VALUES (?)
|
|
|
''', (ownable_name,))
|
|
|
execute('''
|
|
|
- INSERT INTO bonds(issuer_id, ownable_id, coupon, maturity_dt)
|
|
|
+ INSERT INTO credits(issuer_id, ownable_id, coupon, maturity_dt)
|
|
|
VALUES (?, (SELECT MAX(rowid) FROM ownables), ?, ?)
|
|
|
''', (user_id, coupon, maturity_dt))
|
|
|
|