ソースを参照

Display tender calendar

Eren Yilmaz 5 年 前
コミット
feb9f1db23
6 ファイル変更154 行追加81 行削除
  1. 75 34
      client_controller.py
  2. 22 6
      model.py
  3. 5 3
      routes.py
  4. 7 26
      run_client.py
  5. 25 11
      server_controller.py
  6. 20 1
      test/do_some_requests/__init__.py

+ 75 - 34
client_controller.py

@@ -1,13 +1,15 @@
 import re
 import sys
+import time
+from datetime import datetime
 from getpass import getpass
 from inspect import signature
 
 import connection
 from connection import client_request
+from debug import debug
 from game import DEFAULT_ORDER_EXPIRY, CURRENCY_NAME, MIN_INTEREST_INTERVAL, CURRENCY_SYMBOL, OWNABLE_NAME_PATTERN
 from routes import client_commands
-from run_client import fake_loading_bar
 from util import my_tabulate, yn_dialog
 
 exiting = False
@@ -15,7 +17,7 @@ exiting = False
 
 def login(username=None, password=None):
     if connection.session_id is not None:
-        fake_loading_bar('Signing out', duration=0.7)
+        _fake_loading_bar('Signing out', duration=0.7)
         connection.session_id = None
 
     if username is None:
@@ -27,7 +29,7 @@ def login(username=None, password=None):
         else:
             password = input('Password: ')
 
-    fake_loading_bar('Signing in', duration=2.3)
+    _fake_loading_bar('Signing in', duration=2.3)
     response = client_request('login', {"username": username, "password": password})
     success = 'session_id' in response
     if success:
@@ -43,7 +45,7 @@ def login(username=None, password=None):
 def register(username=None, password=None, retype_pw=None):
     if connection.session_id is not None:
         connection.session_id = None
-        fake_loading_bar('Signing out', duration=0.7)
+        _fake_loading_bar('Signing out', duration=0.7)
 
     if username is None:
         username = input('Username: ')
@@ -67,7 +69,7 @@ def register(username=None, password=None, retype_pw=None):
             print('Passwords do not match.')
             return
 
-    fake_loading_bar('Validating Registration', duration=5.2)
+    _fake_loading_bar('Validating Registration', duration=5.2)
     response = client_request('register', {"username": username, "password": password})
 
     if 'error' in response:
@@ -78,7 +80,7 @@ def cancel_order(order_no=None):
     if order_no is None:
         order_no = input('Order No.: ')
 
-    fake_loading_bar('Validating Request', duration=0.6)
+    _fake_loading_bar('Validating Request', duration=0.6)
     response = client_request('cancel_order', {"session_id": connection.session_id, "order_id": order_no})
 
     if 'error' in response:
@@ -107,14 +109,14 @@ def change_pw(password=None, retype_pw=None):
             print('Passwords do not match.')
             return
 
-    fake_loading_bar('Validating password', duration=1.2)
-    fake_loading_bar('Changing password', duration=5.2)
+    _fake_loading_bar('Validating password', duration=1.2)
+    _fake_loading_bar('Changing password', duration=5.2)
     response = client_request('change_password', {"session_id": connection.session_id, "password": password})
 
     if 'error' in response:
         print('Changing password failed with message:', response['error'])
 
-    fake_loading_bar('Signing out', duration=0.7)
+    _fake_loading_bar('Signing out', duration=0.7)
     connection.session_id = None
 
 
@@ -161,7 +163,7 @@ def help(command=None):
 
 
 def depot():
-    fake_loading_bar('Loading data', duration=1.3)
+    _fake_loading_bar('Loading data', duration=1.3)
     response = client_request('depot', {"session_id": connection.session_id})
     success = 'data' in response and 'own_wealth' in response
     if success:
@@ -184,7 +186,7 @@ def depot():
 
 
 def leaderboard():
-    fake_loading_bar('Loading data', duration=1.3)
+    _fake_loading_bar('Loading data', duration=1.3)
     response = client_request('leaderboard', {})
     success = 'data' in response
     if success:
@@ -258,7 +260,7 @@ def _order(is_buy_order, obj_name=None, amount=None, limit=None, stop_loss=None,
     else:
         ioc = bool(int(ioc))
 
-    fake_loading_bar('Sending Data', duration=1.3)
+    _fake_loading_bar('Sending Data', duration=1.3)
     response = client_request('order', {
         "buy": is_buy_order,
         "session_id": connection.session_id,
@@ -297,7 +299,7 @@ def buy(obj_name=None, amount=None, limit=None, stop_loss=None, expiry=None, ioc
 
 
 def orders():
-    fake_loading_bar('Loading Data', duration=0.9)
+    _fake_loading_bar('Loading Data', duration=0.9)
     response = client_request('orders', {"session_id": connection.session_id})
     success = 'data' in response
     if success:
@@ -312,11 +314,11 @@ def orders():
 
 
 def buy_banking_license():
-    fake_loading_bar('Loading data', duration=2.3)
-    fake_loading_bar('Hiring some lawyers and consultants', duration=4.6)
-    fake_loading_bar('Overcoming some bureaucratic hurdles', duration=8.95)
-    fake_loading_bar('Filling the necessary forms', duration=14.4)
-    fake_loading_bar('Waiting for bank regulation\'s response', duration=26.8)
+    _fake_loading_bar('Loading data', duration=2.3)
+    _fake_loading_bar('Hiring some lawyers and consultants', duration=4.6)
+    _fake_loading_bar('Overcoming some bureaucratic hurdles', duration=8.95)
+    _fake_loading_bar('Filling the necessary forms', duration=14.4)
+    _fake_loading_bar('Waiting for bank regulation\'s response', duration=26.8)
     response = client_request('buy_banking_license', {"session_id": connection.session_id})
     success = 'message' in response and 'error' not in response
     if success:
@@ -377,9 +379,9 @@ def take_out_personal_loan(amount=None):
         return
     if amount <= 0:
         print('Amount must be a number larger than 0.')
-    fake_loading_bar('Checking if you are trustworthy', duration=0.0)
-    fake_loading_bar('Checking if you are credit-worthy', duration=0.0)
-    fake_loading_bar('Transferring the money', duration=1.6)
+    _fake_loading_bar('Checking if you are trustworthy', duration=0.0)
+    _fake_loading_bar('Checking if you are credit-worthy', duration=0.0)
+    _fake_loading_bar('Transferring the money', duration=1.6)
     response = client_request('take_out_personal_loan', {"session_id": connection.session_id, 'amount': amount})
     success = 'message' in response and 'error' not in response
     if success:
@@ -392,7 +394,7 @@ def take_out_personal_loan(amount=None):
 
 
 def loans():
-    fake_loading_bar('Loading Data', duration=0.9)
+    _fake_loading_bar('Loading Data', duration=0.9)
     response = client_request('loans', {"session_id": connection.session_id})
     success = 'data' in response
     if success:
@@ -410,14 +412,14 @@ def loans():
 
 
 def bonds():
-    fake_loading_bar('Loading Data', duration=1.6)
+    _fake_loading_bar('Loading Data', duration=1.6)
     response = client_request('bonds', {"session_id": connection.session_id})
     success = 'data' in response
     if success:
         for row in response['data']:
             row[1] = f'{row[1] * 100:.4f}%'
         print(my_tabulate(response['data'],
-                          headers=['Bond', 'Coupon', 'Maturity', 'Issuer',],
+                          headers=['Bond', 'Coupon', 'Maturity', 'Issuer', ],
                           floatfmt='.2f',
                           tablefmt="pipe"))
     else:
@@ -443,7 +445,7 @@ def repay_loan(loan_id=None, amount=None):
         except ValueError:
             print('Amount must be a number larger than 0 or \'all\' (for paying back the remaining loan).')
             return
-    fake_loading_bar('Transferring the money', duration=1.7)
+    _fake_loading_bar('Transferring the money', duration=1.7)
     response = client_request('repay_loan', {"session_id": connection.session_id, 'amount': amount, 'loan_id': loan_id})
     success = 'message' in response and 'error' not in response
     if success:
@@ -465,7 +467,7 @@ def orders_on(obj_name=None):
 
     if obj_name == CURRENCY_NAME.replace(CURRENCY_SYMBOL, 'K'):
         obj_name = CURRENCY_NAME
-    fake_loading_bar('Loading Data', duration=2.3)
+    _fake_loading_bar('Loading Data', duration=2.3)
     response = client_request('orders_on', {"session_id": connection.session_id, "ownable": obj_name})
     success = 'data' in response
     if success:
@@ -489,7 +491,7 @@ def gift(username=None, obj_name=None, amount=None):
         obj_name = CURRENCY_NAME
     if amount is None:
         amount = input('How many?: ')
-    fake_loading_bar('Sending Gift', duration=4.2)
+    _fake_loading_bar('Sending Gift', duration=4.2)
     response = client_request('gift',
                               {"session_id": connection.session_id,
                                "username": username,
@@ -502,7 +504,7 @@ def gift(username=None, obj_name=None, amount=None):
 
 
 def news():
-    fake_loading_bar('Loading Data', duration=0.76)
+    _fake_loading_bar('Loading Data', duration=0.76)
     response = client_request('news', {})
     success = 'data' in response
     if success:
@@ -516,8 +518,31 @@ def news():
             print('News access failed.')
 
 
+def tender_calendar():
+    _fake_loading_bar('Loading Data', duration=0.76)
+    response = client_request('tender_calendar', {})
+    success = 'data' in response
+
+    # preprocess
+    table = response['data']
+    for row in table:
+        row[0] = datetime.fromtimestamp(row[0]).strftime("%Y-%m-%d %H:%M:%S")
+        row[1] = f'{row[1]:8.2%}'
+        row[2] = datetime.fromtimestamp(row[2]).strftime("%Y-%m-%d %H:%M:%S")
+
+    if success:
+        print(my_tabulate(table,
+                          headers=['Date', 'MRO', 'Runs until'],
+                          tablefmt="pipe"))
+    else:
+        if 'error' in response:
+            print('Tender calendar access failed with message:', response['error'])
+        else:
+            print('Tender calendar access failed.')
+
+
 def tradables():
-    fake_loading_bar('Loading Data', duration=12.4)
+    _fake_loading_bar('Loading Data', duration=12.4)
     response = client_request('tradables', {})
     success = 'data' in response
     if success:
@@ -544,7 +569,7 @@ def trades_on(obj_name=None, limit=5):
 
     if obj_name == CURRENCY_NAME.replace(CURRENCY_SYMBOL, 'K'):
         obj_name = CURRENCY_NAME
-    fake_loading_bar('Loading Data', duration=0.34 * limit)
+    _fake_loading_bar('Loading Data', duration=0.34 * limit)
     response = client_request('trades_on', {"session_id": connection.session_id,
                                             "ownable": obj_name,
                                             "limit": limit})
@@ -562,7 +587,7 @@ def trades_on(obj_name=None, limit=5):
 
 def trades(limit=10):
     limit = float(limit)
-    fake_loading_bar('Loading Data', duration=limit * 0.21)
+    _fake_loading_bar('Loading Data', duration=limit * 0.21)
     response = client_request('trades', {"session_id": connection.session_id,
                                          "limit": limit})
     success = 'data' in response
@@ -583,7 +608,7 @@ def old_orders(include_canceled=None, include_executed=None, limit=10):
         include_canceled = yn_dialog('Include canceled/expired orders in list?')
     if include_executed is None:
         include_executed = yn_dialog('Include fully executed orders in list?')
-    fake_loading_bar('Loading Data', duration=limit * 0.27)
+    _fake_loading_bar('Loading Data', duration=limit * 0.27)
     response = client_request('old_orders', {"session_id": connection.session_id,
                                              "include_canceled": include_canceled,
                                              "include_executed": include_executed,
@@ -613,8 +638,8 @@ def issue_bond(name=None, coupon=None, run_time=None):
     if run_time is None:
         run_time = input('Run-time of the bond (minutes):')
 
-    fake_loading_bar('Checking name', duration=3.7)
-    fake_loading_bar('Publishing important information', duration=0.3)
+    _fake_loading_bar('Checking name', duration=3.7)
+    _fake_loading_bar('Publishing important information', duration=0.3)
     response = client_request('issue_bond', {"session_id": connection.session_id,
                                              "name": name,
                                              "coupon": coupon,
@@ -629,3 +654,19 @@ def issue_bond(name=None, coupon=None, run_time=None):
 def exit():
     global exiting
     exiting = True
+
+
+def _fake_loading_bar(msg, duration=5.):
+    if len(msg) >= 60:
+        raise AssertionError('Loading bar label too large')
+    msg += ': '
+    print(msg, end='')
+    sys.stdout.flush()
+    bar_length = 79 - len(msg)
+    for _ in range(bar_length):
+        if not debug:
+            time.sleep(duration / bar_length)
+        print('#', end='')
+        sys.stdout.flush()
+    print('\n', end='')
+    sys.stdout.flush()

+ 22 - 6
model.py

@@ -348,6 +348,20 @@ def get_user_loans(user_id):
     return current_cursor.fetchall()
 
 
+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 > ?
+    ''', (dt,)).fetchone()[0]
+
+
+def next_mro_interest(dt=None):
+    return execute('''
+    SELECT t.mro_interest FROM tender_calendar t WHERE t.maturity_dt = ?
+    ''', (next_mro_dt(dt),)).fetchone()[0]
+
+
 def bonds(issuer_id=None, only_next_mro_qualified=False):
     if issuer_id is not None:
         issuer_condition = 'issuer.rowid = ?'
@@ -360,14 +374,13 @@ def bonds(issuer_id=None, only_next_mro_qualified=False):
             SELECT EXISTS(
                 SELECT *
                 FROM banks b
-                JOIN ownership o ON o.ownable_id = bonds.ownable_id
                 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 = (SELECT MIN(t2.maturity_dt) FROM tender_calendar t2 WHERE t2.maturity_dt > ?)
+                AND t.maturity_dt = ?
             )
             '''
-        only_next_mro_params = (current_db_timestamp(),)
+        only_next_mro_params = (next_mro_dt(),)
     else:
         only_next_mro_condition = '1'
         only_next_mro_params = ()
@@ -383,7 +396,7 @@ def bonds(issuer_id=None, only_next_mro_qualified=False):
         WHERE ({issuer_condition})
         AND ({only_next_mro_condition})
         ORDER BY coupon * (maturity_dt - ?) DESC
-        ''', (*issuer_params, *only_next_mro_params, current_db_timestamp(), ))
+        ''', (*issuer_params, *only_next_mro_params, current_db_timestamp(),))
 
     return current_cursor.fetchall()
 
@@ -1458,8 +1471,11 @@ def loan_id_exists(loan_id):
     return current_cursor.fetchone()[0]
 
 
-def main_refinancing_operations():
-    ...  # TODO
+def tender_calendar():
+    return execute('''
+    SELECT dt, mro_interest, maturity_dt
+    FROM tender_calendar
+    ''', ).fetchall()
 
 
 def issue_bond(user_id, ownable_name, coupon, maturity_dt):

+ 5 - 3
routes.py

@@ -22,6 +22,7 @@ valid_post_routes = {
     'loans',
     'bonds',
     'server_version',
+    'tender_calendar',
 }
 
 push_message_types = set()
@@ -54,10 +55,11 @@ client_commands = ['help',
                    'trades_on',
                    'buy',
                    'sell',
+                   'gift',
                    'cancel_order',
                    'issue_bond',
-                   'gift',
+                   'buy_banking_license',
+                   'tender_calendar',
                    'leaderboard',
-                   'activate_key',
                    'exit',
-                   'buy_banking_license', ]
+                   ]

+ 7 - 26
run_client.py

@@ -1,8 +1,5 @@
 from __future__ import print_function
 
-import sys
-import time
-
 import requests
 from packaging import version as v
 
@@ -14,21 +11,7 @@ from lib.print_exc_plus import print_exc_plus
 from routes import client_commands
 from util import yn_dialog, main_wrapper
 
-
-def fake_loading_bar(msg, duration=5.):
-    if len(msg) >= 60:
-        raise AssertionError('Loading bar label too large')
-    msg += ': '
-    print(msg, end='')
-    sys.stdout.flush()
-    bar_length = 79 - len(msg)
-    for _ in range(bar_length):
-        if not debug:
-            time.sleep(duration / bar_length)
-        print('#', end='')
-        sys.stdout.flush()
-    print('\n', end='')
-    sys.stdout.flush()
+assert all(getattr(client_controller, route) for route in client_commands)
 
 
 def check_for_updates():
@@ -44,16 +27,16 @@ def check_for_updates():
 def load():
     print('Loading...')
 
-    fake_loading_bar('Initializing fake loading bars', duration=5)
-    fake_loading_bar('Loading data from disk', duration=1)
-    fake_loading_bar('Loading available commands', duration=3.5)
-    fake_loading_bar('Checking for updates', duration=0.4)
+    client_controller._fake_loading_bar('Initializing fake loading bars', duration=5)
+    client_controller._fake_loading_bar('Loading data from disk', duration=1)
+    client_controller._fake_loading_bar('Loading available commands', duration=3.5)
+    client_controller._fake_loading_bar('Checking for updates', duration=0.4)
     try:
         check_for_updates()
     except (ConnectionError, requests.exceptions.ConnectionError):
         print('WARNING: There has been a problem connecting when to the server.')
-    fake_loading_bar('Updating indices', duration=2)
-    fake_loading_bar('Waiting', duration=5)
+    client_controller._fake_loading_bar('Updating indices', duration=2)
+    client_controller._fake_loading_bar('Waiting', duration=5)
     print('Done.\n\n')
 
 
@@ -99,8 +82,6 @@ def one_command():
             # noinspection PyBroadException
             try:
                 method_to_call(*cmd[1:])
-            except TypeError:
-                print('Invalid command syntax.')
             except (ConnectionError, requests.exceptions.ConnectionError):
                 print('There has been a problem connecting when to the server.')
             except KeyboardInterrupt:

+ 25 - 11
server_controller.py

@@ -249,6 +249,9 @@ def buy_banking_license(json_request):
 def news(_json_request):
     return {'data': model.news()}
 
+def tender_calendar(_json_request):
+    return {'data': model.tender_calendar()}
+
 
 def tradables(_json_request):
     return {'data': model.ownables()}
@@ -283,22 +286,33 @@ def take_out_personal_loan(json_request):
 def issue_bond(json_request):
     check_missing_attributes(json_request, ['session_id', 'name', 'coupon', 'run_time'])
     user_id = model.get_user_id_by_session_id(json_request['session_id'])
+
     coupon = json_request['coupon']
-    try:
-        coupon = float(coupon)
-    except ValueError:
-        raise BadRequest('Coupon must be a number.')
+    if coupon == 'next_mro':
+        coupon = model.next_mro_interest()
+    else:
+        try:
+            coupon = float(coupon)
+        except ValueError:
+            raise BadRequest('Coupon must be a number.')
+
     ownable_name = json_request['name']
     if not re.fullmatch(OWNABLE_NAME_PATTERN, ownable_name):
         raise BadRequest('Invalid name.')
+
     run_time = json_request['run_time']
-    try:
-        run_time = int(run_time)
-    except ValueError:
-        raise BadRequest('Run-time must be a positive integer number.')
-    if run_time < 0:
-        raise BadRequest('Run-time must be a positive integer number.')
-    maturity_dt = model.current_db_timestamp() + 60 * run_time
+    if run_time == 'next_mro':
+        maturity_dt = model.next_mro_dt()
+    else:
+        try:
+            run_time = int(run_time)
+        except ValueError:
+            raise BadRequest('Run-time must be a positive integer number.')
+        if run_time < 0:
+            raise BadRequest('Run-time must be a positive integer number.')
+
+        maturity_dt = model.current_db_timestamp() + 60 * run_time
+
     model.issue_bond(user_id, ownable_name, coupon, maturity_dt)
     return {'message': "Successfully issued bond"}
 

+ 20 - 1
test/do_some_requests/__init__.py

@@ -191,7 +191,7 @@ def run_tests():
         default_request_method(route, message)
 
         message = {'session_id': session_ids[username],
-                   'coupon': 1.05,
+                   'coupon': 0.05,
                    'name': random_ownable_name(),
                    'run_time': 43200}
         route = 'issue_bond'
@@ -209,6 +209,25 @@ def run_tests():
         route = 'bonds'
         default_request_method(route, message)
 
+        message = {'session_id': session_ids[username],
+                   'coupon': 'next_mro',
+                   'name': random_ownable_name(),
+                   'run_time': 'next_mro'}
+        route = 'issue_bond'
+        default_request_method(route, message)
+
+        message = {'issuer': username}
+        route = 'bonds'
+        default_request_method(route, message)
+
+        message = {'issuer': username, 'only_next_mro_qualified': True}
+        route = 'bonds'
+        default_request_method(route, message)
+
+        message = {'issuer': username, 'only_next_mro_qualified': False}
+        route = 'bonds'
+        default_request_method(route, message)
+
     message = {}
     route = 'bonds'
     default_request_method(route, message)