Eren Yilmaz преди 5 години
родител
ревизия
c91f43ef78
променени са 6 файла, в които са добавени 90 реда и са изтрити 46 реда
  1. 14 7
      client_controller.py
  2. 0 1
      db_setup/seeds/__init__.py
  3. 2 1
      db_setup/tables.py
  4. 59 26
      model.py
  5. 10 7
      run_client.py
  6. 5 4
      server_controller.py

+ 14 - 7
client_controller.py

@@ -171,13 +171,15 @@ def depot():
         for row in data:
             row.append(row[1] * row[2])
         print(my_tabulate(data,
-                          headers=['Object', 'Amount', 'Course', 'Bid', 'Ask', 'Est. Value'],
+                          headers=['Object', 'Avail. Amount', 'Course', 'Bid', 'Ask', 'Est. Value'],
                           tablefmt="pipe",
                           floatfmt='.2f'))
         wealth = response['own_wealth']
-        print(f'Taking into account the amount of loans you have, this results in a wealth of roughly {wealth}.')
+        print(f'Taking into account the amount of debt you have, this results in a wealth of roughly {wealth}.')
         if response['banking_license']:
             print(f'Also, you have a banking license.')
+            minimum_reserve = response['minimum_reserve']
+            print(f'You are legally obligated to deposit a minimum cash reserve of {minimum_reserve} at the central bank as a security for your credits.')
     else:
         if 'error' in response:
             print('Depot access failed with message:', response['error'])
@@ -333,10 +335,10 @@ def buy_banking_license():
             print('Banking license application access failed.')
 
 
-def summarize_bank_rules():  # TODO rework this stuff, does not work like this in real life
+def summarize_bank_rules():
     variables = _global_variables()
     banking_license_price = variables['banking_license_price']
-    marginal_lending_facility = variables['marginal_lending_facility']
+    main_refinancing_operations = variables['main_refinancing_operations']
     cash_reserve_free_amount = variables['cash_reserve_free_amount']
     cash_reserve_ratio = variables['cash_reserve_ratio']
     deposit_facility = variables['deposit_facility']
@@ -344,11 +346,16 @@ def summarize_bank_rules():  # TODO rework this stuff, does not work like this i
     print(f'A banking license can be for {banking_license_price} {CURRENCY_NAME}.')
     print(f'This includes payment of lawyers and consultants to deal with the formal application.')
     print()
-    print(f'Banks are allowed to borrow money from the central bank at the marginal '
-          f'lending facility (currently {marginal_lending_facility * 100}% p.a.).')
+    print(f'Banks are allowed to borrow money from the central bank at an interest rate specified by the central bank'
+          f' (currently {main_refinancing_operations * 100}% p.a.).')
     print(f'For every {CURRENCY_NAME} above {cash_reserve_free_amount} banks have to '
           f'deposit a cash reserve of {cash_reserve_ratio * 100}% at the central bank.')
-    print(f'Banks receive a deposit facility of {deposit_facility * 100}% p.a. for cash reserves.')
+    receive_or_pay = 'receive' if deposit_facility >= 0 else 'receive (or pay)'
+    print(f'Banks {receive_or_pay} a deposit facility rate of {deposit_facility * 100}% p.a. for cash reserves at the central bank '
+          f'(for banks, any money in the depot is deposited at the central bank).')
+    print()
+    print(f'NOTE: The sign of interest rates matters.')
+    print(f'If an interest rate is negative, this actually means that the borrower get interest for borrowing money.')
 
 
 def summarize_loan_rules():

+ 0 - 1
db_setup/seeds/__init__.py

@@ -45,7 +45,6 @@ def seed(cursor: Cursor):
     ''', [('banking_license_price', 5e6),
           ('personal_loan_interest_rate', 0.1),  # may seem a lot but actually this is a credit that you get without any assessment involved
           ('deposit_facility', -0.005),  # ECB 2020
-          ('marginal_lending_facility', 0.0025),  # ECB 2020
           ('cash_reserve_ratio', 0.01),  # Eurozone 2020
           ('cash_reserve_free_amount', 1e5),  # Eurozone 2020
           ('main_refinancing_operations', 0.0000),  # ECB 2020

+ 2 - 1
db_setup/tables.py

@@ -90,7 +90,8 @@ def tables(cursor):
     cursor.execute('''
                 CREATE TABLE IF NOT EXISTS banks(
                     rowid INTEGER PRIMARY KEY,
-                    user_id INTEGER UNIQUE NOT NULL REFERENCES users(rowid)
+                    user_id INTEGER UNIQUE NOT NULL REFERENCES users(rowid),
+                    last_deposit_facility_pay_dt TIMESTAMP NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER))
                 )
                 ''')
     cursor.execute('''

+ 59 - 26
model.py

@@ -248,10 +248,10 @@ def get_user_id_by_name(username):
 
 
 def get_user_ownership(user_id):
-    execute('''
+    data = execute('''
         SELECT 
             ownables.name, 
-            ownership.amount, 
+            ownable_id, -- this is used for computing the available amount
             COALESCE (
             CASE -- sum score for each of the users ownables
             WHEN ownership.ownable_id = ? THEN 1
@@ -275,12 +275,19 @@ def get_user_ownership(user_id):
              AND NOT stop_loss) AS ask
         FROM ownership, ownables
         WHERE user_id = ?
-        AND (ownership.amount >= 0.01 OR ownership.ownable_id = ?)
+        AND (ownership.amount >= 0.01 OR ownership.amount <= -0.01 OR ownership.ownable_id = ?)
         AND ownership.ownable_id = ownables.rowid
         ORDER BY ownables.rowid ASC
-        ''', (currency_id(), user_id, currency_id(),))
+        ''', (currency_id(), user_id, currency_id(),)).fetchall()
 
-    return current_cursor.fetchall()
+    data = [list(row) for row in data]
+
+    for row in data:
+        ownable_id = row[1]
+        available_amount = user_available_ownable(user_id, ownable_id)
+        row[1] = available_amount
+
+    return data
 
 
 def bank_id():
@@ -441,17 +448,6 @@ def sell_ordered_amount(user_id, ownable_id):
     return current_cursor.fetchone()[0]
 
 
-def available_amount(user_id, ownable_id):
-    execute('''
-                SELECT amount
-                FROM ownership
-                WHERE user_id = ?
-                AND ownable_id = ?
-                ''', (user_id, ownable_id))
-
-    return current_cursor.fetchone()[0] - sell_ordered_amount(user_id, ownable_id)
-
-
 def is_bond_of_user(ownable_id, user_id):
     execute('''
     SELECT EXISTS(
@@ -483,7 +479,6 @@ def user_available_ownable(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
@@ -492,7 +487,6 @@ def user_has_at_least_available(amount, user_id, ownable_id):
     return user_available_ownable(user_id, ownable_id) >= amount
 
 
-
 def news():
     execute('''
         SELECT dt, title FROM
@@ -585,7 +579,7 @@ def currency_id():
     return current_cursor.fetchone()[0]
 
 
-def user_money(user_id):
+def user_available_money(user_id):
     return user_available_ownable(user_id, currency_id())
 
 
@@ -724,7 +718,7 @@ def execute_orders(ownable_id):
             else:
                 price = sell_limit
 
-        buyer_money = user_money(buyer_id)
+        buyer_money = user_available_money(buyer_id)
 
         def _my_division(x, y):
             try:
@@ -986,10 +980,19 @@ def user_wealth(user_id):
         ), 0)
         FROM loans
         WHERE loans.user_id = ?)
+        -
+        ( SELECT COALESCE(SUM(
+            amount
+        ), 0)
+        FROM credits
+        JOIN ownership o on credits.ownable_id = o.ownable_id
+        WHERE credits.issuer_id = ?
+        AND o.user_id != ?
+        )
     '''
     execute(f'''
     SELECT ({score_expression}) AS score
-        ''', (currency_id(), user_id, user_id,))
+        ''', (currency_id(), user_id, user_id, user_id, user_id,))
 
     return current_cursor.fetchone()[0]
 
@@ -1337,6 +1340,10 @@ def pay_bond_interest(until=None):
     )
     ''', (current_dt,))
     execute('''
+    DELETE FROM credits 
+    WHERE ? > maturity_dt
+    ''', (current_dt,))
+    execute('''
     DELETE FROM ownables 
     WHERE rowid IN (
         SELECT ownable_id
@@ -1344,10 +1351,6 @@ def pay_bond_interest(until=None):
         WHERE ? > maturity_dt
     )
     ''', (current_dt,))
-    execute('''
-    DELETE FROM credits 
-    WHERE ? > maturity_dt
-    ''', (current_dt,))
 
 
 def pay_loan_interest(until=None):
@@ -1378,6 +1381,32 @@ def pay_loan_interest(until=None):
     ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
 
 
+def pay_deposit_facility(until=None):
+    if until is None:
+        current_dt = current_db_timestamp()
+    else:
+        current_dt = until
+    sec_per_year = 3600 * 24 * 365
+    interest_rate = global_control_value('deposit_facility')
+    banks = execute('''
+    SELECT 
+        banks.user_id,
+        o.amount * ? * (CAST(? AS FLOAT) - last_deposit_facility_pay_dt) / ?
+    FROM banks
+    JOIN ownership o on banks.user_id = o.user_id
+    WHERE o.rowid = ?
+    AND ? - last_deposit_facility_pay_dt > ?''', (interest_rate, current_dt, sec_per_year, currency_id(), current_dt, MIN_INTEREST_INTERVAL)).fetchall()
+
+    for user_id, interest_amount in banks:
+        send_ownable(user_id, bank_id(), currency_id(), interest_amount)
+
+    execute('''
+    UPDATE banks
+    SET last_deposit_facility_pay_dt = ?
+    WHERE ? - last_deposit_facility_pay_dt > ?
+    ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
+
+
 def triggered_mros():
     return execute('''
     SELECT 
@@ -1393,7 +1422,7 @@ def triggered_mros():
 
 def mro(mro_id, expiry, min_interest):
     qualified_credits = execute('''
-    SELECT credits.ownable_id
+    SELECT credits.ownable_id, SUM(amount)
     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
@@ -1401,8 +1430,12 @@ def mro(mro_id, expiry, min_interest):
     WHERE maturity_dt = ?
     AND coupon >= ?
     AND "limit" IS NULL or "limit" <= 1
+    GROUP BY credits.ownable_id
     ''', (expiry, min_interest)).fetchall()
     for ownable_id, amount in qualified_credits:
+        if amount == 0:
+            continue
+        assert amount > 0
         bank_order(buy=True,
                    ownable_id=ownable_id,
                    limit=1,

+ 10 - 7
run_client.py

@@ -15,13 +15,16 @@ assert all(getattr(client_controller, route) for route in client_commands)
 
 
 def check_for_updates():
-    server_version = client_request('server_version')['version']
-    client_version = version.__version__
-    if v.parse(server_version) != v.parse(client_version):
-        print(f'WARNING: You have Orderer version {client_version} installed while the server is running version {server_version}.')
-        print(f'         This may or may not lead to problems.')
-        print(f'         A recent client version should be available for download at '
-              f'         {host}/orderer.zip')
+    try:
+        server_version = client_request('server_version')['version']
+        client_version = version.__version__
+        if v.parse(server_version) != v.parse(client_version):
+            print(f'WARNING: You have Orderer version {client_version} installed while the server is running version {server_version}.')
+            print(f'         This may or may not lead to problems.')
+            print(f'         A recent client version should be available for download at '
+                  f'         {host}/orderer.zip')
+    except Exception:
+        print('Unknown error while checking for updates.')
 
 
 def load():

+ 5 - 4
server_controller.py

@@ -27,6 +27,7 @@ def depot(json_request):
     user_id = model.get_user_id_by_session_id(json_request['session_id'])
     return {'data': model.get_user_ownership(user_id),
             'own_wealth': f'{model.user_wealth(user_id):.2f}',
+            'minimum_reserve': model.required_minimum_reserve(user_id) if model.user_has_banking_license(user_id) else None,
             'banking_license': model.user_has_banking_license(user_id)}
 
 
@@ -147,11 +148,11 @@ def gift(json_request):
     ownable_id = model.ownable_id_by_name(json_request['object_name'])
     sender_id = model.get_user_id_by_session_id(json_request['session_id'])
 
-    if model.available_amount(sender_id, ownable_id) == 0:
+    if model.user_available_ownable(sender_id, ownable_id) == 0:
         return BadRequest('You do not own any of these.')
     if not model.user_has_at_least_available(amount, sender_id, ownable_id):
         # for example if you have a 1.23532143213 Kollar and want to give them all away
-        amount = model.available_amount(sender_id, ownable_id)
+        amount = model.user_available_ownable(sender_id, ownable_id)
 
     recipient_id = model.get_user_id_by_name(json_request['username'])
 
@@ -239,7 +240,7 @@ def buy_banking_license(json_request):
     if model.user_has_banking_license(user_id):
         raise PreconditionFailed('You already have a banking license.')
     price = model.global_control_value('banking_license_price')
-    if model.user_money(user_id) < price:
+    if model.user_available_money(user_id) < price:
         raise PreconditionFailed('You do not have enough money.')
     model.send_ownable(user_id, model.bank_id(), model.currency_id(), price)
     model.assign_banking_licence(user_id)
@@ -326,7 +327,7 @@ def repay_loan(json_request):
         amount = model.loan_remaining_amount(loan_id)
     if amount < 0:
         raise BadRequest('You can not repay negative amounts.')
-    if model.user_money(user_id) < amount:
+    if model.user_available_money(user_id) < amount:
         if model.user_has_banking_license(user_id):
             raise PreconditionFailed('You do not have enough money. '
                                      'If you are a bank this also takes into account the minimum reserve you need to keep at the central bank.')