|
@@ -162,6 +162,13 @@ def own(user_id, ownable_name, amount=0):
|
|
''', (user_id, ownable_name, amount))
|
|
''', (user_id, ownable_name, amount))
|
|
|
|
|
|
|
|
|
|
|
|
+def own_id(user_id, ownable_id, amount=0):
|
|
|
|
+ execute('''
|
|
|
|
+ INSERT OR IGNORE INTO ownership (user_id, ownable_id, amount)
|
|
|
|
+ SELECT ?, ?, ?
|
|
|
|
+ ''', (user_id, ownable_id, amount))
|
|
|
|
+
|
|
|
|
+
|
|
def send_ownable(from_user_id, to_user_id, ownable_id, amount):
|
|
def send_ownable(from_user_id, to_user_id, ownable_id, amount):
|
|
if amount < 0:
|
|
if amount < 0:
|
|
raise AssertionError('Can not send negative amount')
|
|
raise AssertionError('Can not send negative amount')
|
|
@@ -369,6 +376,12 @@ def next_mro_interest(dt=None):
|
|
''', (next_mro_dt(dt),)).fetchone()[0]
|
|
''', (next_mro_dt(dt),)).fetchone()[0]
|
|
|
|
|
|
|
|
|
|
|
|
+def next_mro_maturity(dt=None):
|
|
|
|
+ return execute('''
|
|
|
|
+ SELECT t.maturity_dt FROM tender_calendar t WHERE t.dt = ?
|
|
|
|
+ ''', (next_mro_dt(dt),)).fetchone()[0]
|
|
|
|
+
|
|
|
|
+
|
|
def credits(issuer_id=None, only_next_mro_qualified=False):
|
|
def credits(issuer_id=None, only_next_mro_qualified=False):
|
|
if issuer_id is not None:
|
|
if issuer_id is not None:
|
|
issuer_condition = 'issuer.rowid = ?'
|
|
issuer_condition = 'issuer.rowid = ?'
|
|
@@ -463,6 +476,8 @@ def is_bond_of_user(ownable_id, user_id):
|
|
def user_available_ownable(user_id, ownable_id):
|
|
def user_available_ownable(user_id, ownable_id):
|
|
if is_bond_of_user(ownable_id, user_id):
|
|
if is_bond_of_user(ownable_id, user_id):
|
|
return inf
|
|
return inf
|
|
|
|
+ if user_id == bank_id() and ownable_id == currency_id():
|
|
|
|
+ return inf
|
|
|
|
|
|
if ownable_id == currency_id() and user_has_banking_license(user_id):
|
|
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)
|
|
minimum_reserve = required_minimum_reserve(user_id) + sell_ordered_amount(user_id, ownable_id)
|
|
@@ -620,7 +635,6 @@ def current_value(ownable_id):
|
|
|
|
|
|
|
|
|
|
def execute_orders(ownable_id):
|
|
def execute_orders(ownable_id):
|
|
- orders_traded = False
|
|
|
|
while True:
|
|
while True:
|
|
# find order to execute
|
|
# find order to execute
|
|
execute('''
|
|
execute('''
|
|
@@ -692,18 +706,18 @@ def execute_orders(ownable_id):
|
|
LIMIT 1
|
|
LIMIT 1
|
|
''', tuple(ownable_id for _ in range(8)))
|
|
''', tuple(ownable_id for _ in range(8)))
|
|
|
|
|
|
- matching_orders = current_cursor.fetchone()
|
|
|
|
|
|
+ matching_order = current_cursor.fetchone()
|
|
# return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
|
|
# return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
|
|
# ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
|
|
# ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
|
|
# user_id,user_id,rowid,rowid)
|
|
# user_id,user_id,rowid,rowid)
|
|
|
|
|
|
- if not matching_orders:
|
|
|
|
|
|
+ if not matching_order:
|
|
break
|
|
break
|
|
|
|
|
|
_, buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, _, \
|
|
_, buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, _, \
|
|
_, sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, _, \
|
|
_, sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, _, \
|
|
buyer_id, seller_id, buy_order_id, sell_order_id \
|
|
buyer_id, seller_id, buy_order_id, sell_order_id \
|
|
- = matching_orders
|
|
|
|
|
|
+ = matching_order
|
|
|
|
|
|
if buy_limit is None and sell_limit is None:
|
|
if buy_limit is None and sell_limit is None:
|
|
price = current_value(ownable_id)
|
|
price = current_value(ownable_id)
|
|
@@ -720,15 +734,15 @@ def execute_orders(ownable_id):
|
|
|
|
|
|
buyer_money = user_available_money(buyer_id)
|
|
buyer_money = user_available_money(buyer_id)
|
|
|
|
|
|
- def _my_division(x, y):
|
|
|
|
- try:
|
|
|
|
- return floor(x / y)
|
|
|
|
- except ZeroDivisionError:
|
|
|
|
- return float('Inf')
|
|
|
|
|
|
+ def affordable_nominal(money, price_per_nominal):
|
|
|
|
+ if money == inf or price_per_nominal <= 0:
|
|
|
|
+ return inf
|
|
|
|
+ else:
|
|
|
|
+ return floor(money / price_per_nominal)
|
|
|
|
|
|
amount = min(buy_order_amount - buy_executed_amount,
|
|
amount = min(buy_order_amount - buy_executed_amount,
|
|
sell_order_amount - sell_executed_amount,
|
|
sell_order_amount - sell_executed_amount,
|
|
- _my_division(buyer_money, price))
|
|
|
|
|
|
+ affordable_nominal(buyer_money, price))
|
|
|
|
|
|
if amount < 0:
|
|
if amount < 0:
|
|
amount = 0
|
|
amount = 0
|
|
@@ -758,10 +772,8 @@ def execute_orders(ownable_id):
|
|
|
|
|
|
if buy_order_finished:
|
|
if buy_order_finished:
|
|
delete_order(buy_order_id, 'Executed')
|
|
delete_order(buy_order_id, 'Executed')
|
|
- orders_traded = True
|
|
|
|
if sell_order_finished:
|
|
if sell_order_finished:
|
|
delete_order(sell_order_id, 'Executed')
|
|
delete_order(sell_order_id, 'Executed')
|
|
- orders_traded = True
|
|
|
|
|
|
|
|
if seller_id != buyer_id: # prevent showing self-transactions
|
|
if seller_id != buyer_id: # prevent showing self-transactions
|
|
execute('''
|
|
execute('''
|
|
@@ -816,6 +828,7 @@ def user_name_by_id(user_id):
|
|
def bank_order(buy, ownable_id, limit, amount, expiry, ioc):
|
|
def bank_order(buy, ownable_id, limit, amount, expiry, ioc):
|
|
if not limit:
|
|
if not limit:
|
|
raise AssertionError('The bank does not give away anything.')
|
|
raise AssertionError('The bank does not give away anything.')
|
|
|
|
+ own_id(bank_id(), ownable_id)
|
|
place_order(buy,
|
|
place_order(buy,
|
|
get_ownership_id(ownable_id, bank_id()),
|
|
get_ownership_id(ownable_id, bank_id()),
|
|
limit,
|
|
limit,
|
|
@@ -823,8 +836,6 @@ def bank_order(buy, ownable_id, limit, amount, expiry, ioc):
|
|
amount,
|
|
amount,
|
|
expiry,
|
|
expiry,
|
|
ioc=ioc)
|
|
ioc=ioc)
|
|
- ownable_name = ownable_name_by_id(ownable_id)
|
|
|
|
- new_news('External investors are selling ' + ownable_name + ' atm')
|
|
|
|
|
|
|
|
|
|
|
|
def current_db_time(): # might differ from datetime.datetime.now() for time zone reasons
|
|
def current_db_time(): # might differ from datetime.datetime.now() for time zone reasons
|
|
@@ -1182,7 +1193,7 @@ def cleanup():
|
|
for name in connections:
|
|
for name in connections:
|
|
connections[name].rollback()
|
|
connections[name].rollback()
|
|
connections[name].close()
|
|
connections[name].close()
|
|
- connections = []
|
|
|
|
|
|
+ connections = {}
|
|
current_connection = None
|
|
current_connection = None
|
|
current_cursor = None
|
|
current_cursor = None
|
|
current_db_name = None
|
|
current_db_name = None
|
|
@@ -1274,7 +1285,7 @@ def pay_bond_interest(until=None):
|
|
credits.issuer_id AS from_user_id
|
|
credits.issuer_id AS from_user_id
|
|
FROM credits
|
|
FROM credits
|
|
JOIN ownership o on credits.ownable_id = o.ownable_id
|
|
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
|
|
|
|
|
|
+ WHERE (? - last_interest_pay_dt > ? OR ? > maturity_dt) -- every interval or when the bond expired
|
|
AND amount != 0
|
|
AND amount != 0
|
|
GROUP BY o.user_id, credits.issuer_id
|
|
GROUP BY o.user_id, credits.issuer_id
|
|
''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL, current_dt)).fetchall()
|
|
''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL, current_dt)).fetchall()
|
|
@@ -1285,7 +1296,7 @@ def pay_bond_interest(until=None):
|
|
o.user_id AS to_user_id,
|
|
o.user_id AS to_user_id,
|
|
credits.issuer_id AS from_user_id
|
|
credits.issuer_id AS from_user_id
|
|
FROM credits
|
|
FROM credits
|
|
- JOIN ownership o on credits.ownable_id = o.ownable_id
|
|
|
|
|
|
+ JOIN ownership o ON credits.ownable_id = o.ownable_id
|
|
WHERE ? > maturity_dt
|
|
WHERE ? > maturity_dt
|
|
''', (current_dt,)).fetchall()
|
|
''', (current_dt,)).fetchall()
|
|
|
|
|
|
@@ -1303,6 +1314,10 @@ def pay_bond_interest(until=None):
|
|
WHERE ? - last_interest_pay_dt > ?''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
|
|
WHERE ? - last_interest_pay_dt > ?''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
|
|
|
|
|
|
# delete matured credits
|
|
# delete matured credits
|
|
|
|
+ delete_matured_credits(current_dt)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def delete_matured_credits(current_dt):
|
|
execute('''
|
|
execute('''
|
|
DELETE FROM transactions
|
|
DELETE FROM transactions
|
|
WHERE ownable_id IN (
|
|
WHERE ownable_id IN (
|
|
@@ -1411,27 +1426,28 @@ def triggered_mros():
|
|
return execute('''
|
|
return execute('''
|
|
SELECT
|
|
SELECT
|
|
rowid AS mro_id,
|
|
rowid AS mro_id,
|
|
- maturity_dt AS expiry,
|
|
|
|
|
|
+ maturity_dt,
|
|
mro_interest AS min_interest,
|
|
mro_interest AS min_interest,
|
|
dt AS mro_dt
|
|
dt AS mro_dt
|
|
FROM tender_calendar
|
|
FROM tender_calendar
|
|
WHERE NOT executed
|
|
WHERE NOT executed
|
|
AND dt < ?
|
|
AND dt < ?
|
|
|
|
+ ORDER BY dt ASC
|
|
''', (current_db_timestamp(),)).fetchall()
|
|
''', (current_db_timestamp(),)).fetchall()
|
|
|
|
|
|
|
|
|
|
-def mro(mro_id, expiry, min_interest):
|
|
|
|
|
|
+def mro(mro_id, maturity_dt, min_interest):
|
|
qualified_credits = execute('''
|
|
qualified_credits = execute('''
|
|
- SELECT credits.ownable_id, SUM(amount)
|
|
|
|
|
|
+ SELECT credits.ownable_id, SUM(ordered_amount)
|
|
FROM credits
|
|
FROM credits
|
|
JOIN banks b ON credits.issuer_id = b.user_id
|
|
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 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
|
|
JOIN orders o2 ON o.rowid = o2.ownership_id AND NOT o2.buy
|
|
WHERE maturity_dt = ?
|
|
WHERE maturity_dt = ?
|
|
AND coupon >= ?
|
|
AND coupon >= ?
|
|
- AND "limit" IS NULL or "limit" <= 1
|
|
|
|
|
|
+ AND ("limit" IS NULL OR "limit" <= 1)
|
|
GROUP BY credits.ownable_id
|
|
GROUP BY credits.ownable_id
|
|
- ''', (expiry, min_interest)).fetchall()
|
|
|
|
|
|
+ ''', (maturity_dt, min_interest)).fetchall()
|
|
for ownable_id, amount in qualified_credits:
|
|
for ownable_id, amount in qualified_credits:
|
|
if amount == 0:
|
|
if amount == 0:
|
|
continue
|
|
continue
|
|
@@ -1440,12 +1456,12 @@ def mro(mro_id, expiry, min_interest):
|
|
ownable_id=ownable_id,
|
|
ownable_id=ownable_id,
|
|
limit=1,
|
|
limit=1,
|
|
amount=amount,
|
|
amount=amount,
|
|
- expiry=expiry,
|
|
|
|
|
|
+ expiry=maturity_dt,
|
|
ioc=True)
|
|
ioc=True)
|
|
- execute('''
|
|
|
|
- UPDATE tender_calendar
|
|
|
|
- SET executed = TRUE
|
|
|
|
- WHERE rowid = ?''', (mro_id,)) # TODO set mro to executed
|
|
|
|
|
|
+ execute('''
|
|
|
|
+ UPDATE tender_calendar
|
|
|
|
+ SET executed = TRUE
|
|
|
|
+ WHERE rowid = ?''', (mro_id,))
|
|
|
|
|
|
|
|
|
|
def loan_recipient_id(loan_id):
|
|
def loan_recipient_id(loan_id):
|
|
@@ -1511,6 +1527,7 @@ def time_travel(delta_t):
|
|
Be careful with time travel into the past though.
|
|
Be careful with time travel into the past though.
|
|
:param delta_t: time in seconds to travel
|
|
:param delta_t: time in seconds to travel
|
|
"""
|
|
"""
|
|
|
|
+ print(f'DEBUG INFO: Time traveling {round(delta_t)}s into the future by reducing all timestamps by {round(delta_t)}...')
|
|
tables = execute('''
|
|
tables = execute('''
|
|
SELECT name
|
|
SELECT name
|
|
FROM sqlite_master
|
|
FROM sqlite_master
|
|
@@ -1532,7 +1549,7 @@ def time_travel(delta_t):
|
|
execute(f'''
|
|
execute(f'''
|
|
UPDATE {table}
|
|
UPDATE {table}
|
|
SET {updates}
|
|
SET {updates}
|
|
- ''', tuple(delta_t for _ in timestamp_columns))
|
|
|
|
|
|
+ ''', tuple(-delta_t for _ in timestamp_columns))
|
|
|
|
|
|
|
|
|
|
def user_has_loan_with_id(user_id, loan_id):
|
|
def user_has_loan_with_id(user_id, loan_id):
|
|
@@ -1555,7 +1572,7 @@ def tender_calendar():
|
|
def required_minimum_reserve(user_id):
|
|
def required_minimum_reserve(user_id):
|
|
assert user_has_banking_license(user_id)
|
|
assert user_has_banking_license(user_id)
|
|
borrowed_money = execute('''
|
|
borrowed_money = execute('''
|
|
- SELECT SUM(amount)
|
|
|
|
|
|
+ SELECT COALESCE(SUM(amount), 0)
|
|
FROM ownership
|
|
FROM ownership
|
|
JOIN credits b on ownership.ownable_id = b.ownable_id
|
|
JOIN credits b on ownership.ownable_id = b.ownable_id
|
|
WHERE b.issuer_id = ?
|
|
WHERE b.issuer_id = ?
|
|
@@ -1583,8 +1600,8 @@ def update_tender_calendar():
|
|
LIMIT 1), ?)
|
|
LIMIT 1), ?)
|
|
''', (current_db_timestamp(),)).fetchone()[0]
|
|
''', (current_db_timestamp(),)).fetchone()[0]
|
|
|
|
|
|
- one_day = 24 * 3600
|
|
|
|
- while last_mro_dt < current_db_timestamp() + one_day:
|
|
|
|
|
|
+ one_month = 30 * 24 * 3600
|
|
|
|
+ while last_mro_dt < current_db_timestamp() + one_month:
|
|
last_mro_dt += MRO_INTERVAL
|
|
last_mro_dt += MRO_INTERVAL
|
|
maturity_dt = last_mro_dt + MRO_RUNNING_TIME
|
|
maturity_dt = last_mro_dt + MRO_RUNNING_TIME
|
|
execute('''
|
|
execute('''
|