123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- 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 util import my_tabulate, yn_dialog
- exiting = False
- def login(username=None, password=None):
- if connection.session_id is not None:
- _fake_loading_bar('Signing out', duration=0.7)
- connection.session_id = None
- if username is None:
- username = input('Username: ')
- if password is None:
- if sys.stdin.isatty():
- password = getpass('Password: ')
- else:
- password = input('Password: ')
- _fake_loading_bar('Signing in', duration=2.3)
- response = client_request('login', {"username": username, "password": password})
- success = 'session_id' in response
- if success:
- connection.session_id = response['session_id']
- print('Login successful.')
- else:
- if 'error' in response:
- print('Login failed with message:', response['error'])
- else:
- print('Login failed.')
- 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)
- if username is None:
- username = input('Username: ')
- if password is None:
- if sys.stdin.isatty():
- password = getpass('New password: ')
- retype_pw = getpass('Retype password: ')
- else:
- password = input('New password: ')
- retype_pw = input('Retype password: ')
- if password != retype_pw:
- print('Passwords do not match.')
- return
- elif retype_pw is None:
- if sys.stdin.isatty():
- retype_pw = getpass('Retype password: ')
- else:
- retype_pw = input('Retype password: ')
- if password != retype_pw:
- print('Passwords do not match.')
- return
- _fake_loading_bar('Validating Registration', duration=5.2)
- response = client_request('register', {"username": username, "password": password})
- if 'error' in response:
- print('Registration failed with message:', response['error'])
- def cancel_order(order_no=None):
- if order_no is None:
- order_no = input('Order No.: ')
- _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:
- print('Order cancelling failed with message:', response['error'])
- def change_pw(password=None, retype_pw=None):
- if password != retype_pw:
- password = None
- if password is None:
- if sys.stdin.isatty():
- password = getpass('New password: ')
- retype_pw = getpass('Retype password: ')
- else:
- password = input('New password: ')
- retype_pw = input('Retype password: ')
- if password != retype_pw:
- print('Passwords do not match.')
- return
- elif retype_pw is None:
- if sys.stdin.isatty():
- retype_pw = getpass('Retype password: ')
- else:
- retype_pw = input('Retype password: ')
- if password != retype_pw:
- print('Passwords do not match.')
- return
- _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)
- connection.session_id = None
- # noinspection PyShadowingBuiltins
- def help(command=None):
- print('Allowed commands:')
- command_table = []
- if command is None:
- for cmd in client_commands:
- this_module = sys.modules[__name__]
- method = getattr(this_module, cmd)
- params = signature(method).parameters
- command_table.append([cmd] + [p for p in params])
- print(my_tabulate(command_table, tablefmt='pipe', headers=['command',
- 'param 1',
- 'param 2',
- 'param 3',
- 'param 4',
- 'param 5',
- 'param 6',
- ]))
- print('NOTE:')
- print(' All parameters for all commands are optional!')
- print(' Commands can be combined in one line with ; between them.')
- print(' Parameters are separated with whitespace.')
- print(' Use . as a decimal separator.')
- print(' Use 0/1 for boolean parameters (other strings might work).')
- else:
- for cmd in client_commands:
- if cmd.strip() == command.strip():
- this_module = sys.modules[__name__]
- method = getattr(this_module, cmd)
- params = signature(method).parameters
- command_table.append([cmd] + [p for p in params])
- break
- else:
- print('Command not found:', command)
- return
- print(my_tabulate(command_table, tablefmt='pipe', headers=['command',
- *[f'param {idx}' for idx in range(len(params))],
- ]))
- def depot():
- _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:
- data = response['data']
- for row in data:
- row.append(row[1] * row[2])
- print(my_tabulate(data,
- 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 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}{CURRENCY_SYMBOL} '
- f'at the central bank as a security for your credits.')
- print('This minimum reserve has already been subtracted from the displayed amount.')
- else:
- if 'error' in response:
- print('Depot access failed with message:', response['error'])
- else:
- print('Depot access failed.')
- def leaderboard():
- _fake_loading_bar('Loading data', duration=1.3)
- response = client_request('leaderboard', {})
- success = 'data' in response
- if success:
- print(my_tabulate(response['data'], headers=['Trader', 'Wealth'], tablefmt="pipe"))
- # print('Remember that the goal is to be as rich as possible, not to be richer than other traders!')
- else:
- if 'error' in response:
- print('Leaderboard access failed with message:', response['error'])
- else:
- print('Leaderboard access failed.')
- def _order(is_buy_order, obj_name=None, amount=None, limit=None, stop_loss=None, expiry=None, ioc=None):
- if obj_name is None: # TODO list some available objects
- obj_name = input('Name of object to sell: ')
- obj_name = obj_name.strip()
- if obj_name == '':
- return
- if stop_loss is not None and stop_loss.strip() not in ['1', '0']:
- print('Invalid value for flag stop loss (only 0 or 1 allowed).')
- return
- elif stop_loss is not None:
- stop_loss = bool(int(stop_loss))
- if obj_name == CURRENCY_NAME.replace(CURRENCY_SYMBOL, 'K'):
- obj_name = CURRENCY_NAME
- if amount is None:
- amount = input('Amount: ')
- if amount == '':
- return
- if limit is None:
- set_limit = yn_dialog('Do you want to place a limit?')
- if set_limit:
- limit = input('Limit: ')
- stop_loss = yn_dialog('Is this a stop-loss limit?')
- if limit is not None:
- try:
- limit = float(limit)
- except ValueError:
- print('Invalid limit.')
- return
- if stop_loss is None:
- stop_loss = yn_dialog('Is this a stop-loss limit?')
- question = 'Are you sure you want to use such a low limit (limit=' + str(limit) + ')?:'
- if not is_buy_order and limit <= 0 and not yn_dialog(question):
- print('Order was not placed.')
- return
- if expiry is None:
- expiry = input('Time until order expires (minutes, default ' + str(DEFAULT_ORDER_EXPIRY) + '):')
- if expiry == '':
- expiry = DEFAULT_ORDER_EXPIRY
- try:
- expiry = float(expiry)
- except ValueError:
- print('Invalid expiration time.')
- return
- if ioc is None and not stop_loss:
- ioc = yn_dialog('Is this an IOC (immediate-or-cancel) order?')
- if str(ioc).strip() not in ['1', '0', 'True', 'False']:
- print('Invalid value for flag IOC (only 0 or 1 allowed).')
- return
- else:
- ioc = bool(int(ioc))
- _fake_loading_bar('Sending Data', duration=1.3)
- response = client_request('order', {
- "buy": is_buy_order,
- "session_id": connection.session_id,
- "amount": amount,
- "ownable": obj_name,
- "limit": limit,
- "stop_loss": stop_loss,
- "time_until_expiration": expiry,
- "ioc": ioc,
- })
- if 'error' in response:
- print('Order placement failed with message:', response['error'])
- else:
- print('You might want to use the `trades` or `depot` commands',
- 'to see if the order has been executed already.')
- def sell(obj_name=None, amount=None, limit=None, stop_loss=None, expiry=None, ioc=None):
- _order(is_buy_order=False,
- obj_name=obj_name,
- amount=amount,
- limit=limit,
- stop_loss=stop_loss,
- expiry=expiry,
- ioc=ioc, )
- def buy(obj_name=None, amount=None, limit=None, stop_loss=None, expiry=None, ioc=None):
- _order(is_buy_order=True,
- obj_name=obj_name,
- amount=amount,
- limit=limit,
- stop_loss=stop_loss,
- expiry=expiry,
- ioc=ioc, )
- def orders():
- _fake_loading_bar('Loading Data', duration=0.9)
- response = client_request('orders', {"session_id": connection.session_id})
- success = 'data' in response
- if success:
- print(my_tabulate(response['data'],
- headers=['Buy?', 'Name', 'Size', 'Limit', 'stop-loss', 'Expires', 'No.'],
- tablefmt="pipe"))
- else:
- if 'error' in response:
- print('Order access failed with message:', response['error'])
- else:
- print('Order access failed.')
- 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)
- response = client_request('buy_banking_license', {"session_id": connection.session_id})
- success = 'message' in response and 'error' not in response
- if success:
- print('Success. You are now a bank.')
- print()
- summarize_bank_rules()
- # print('Remember that the goal is to be as rich as possible, not to be richer than other traders!')
- else:
- if 'error' in response:
- print('Banking license application failed with message:', response['error'])
- else:
- print('Banking license application access failed.')
- def summarize_bank_rules():
- variables = _global_variables()
- banking_license_price = variables['banking_license_price']
- 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']
- print(f'A bank is by definition anyone who has a banking license.')
- 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 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.')
- 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():
- variables = _global_variables()
- personal_loan_interest_rate = variables['personal_loan_interest_rate']
- print(f'You can take personal loans at an interest rate of {personal_loan_interest_rate * 100}% p.a.')
- print(f'You can repay personal loans at any time if you have the liquidity.')
- print(f'Interest rates will be automatically be debited from your account.')
- print(f'Note that this debit may be delayed by up to {MIN_INTEREST_INTERVAL} seconds.')
- print(f'If you have no {CURRENCY_NAME} available (or negative account balance), you can still')
- print(f' - pay any further interests (account will move further into the negative)')
- print(f' - take a new loan (if we think that you will be able to repay it)')
- print(f' - sell any securities you own')
- print(f'However you can not')
- print(f' - spend money to buy securities')
- print(f' - spend money to buy a banking license')
- def take_out_personal_loan(amount=None):
- if amount is None:
- summarize_loan_rules()
- print()
- amount = input('Please enter your desired loan volume: ')
- try:
- amount = float(amount)
- except ValueError:
- print('Amount must be a number larger than 0.')
- 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)
- 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:
- print(f'You took out a personal loan of {amount} {CURRENCY_NAME}.')
- else:
- if 'error' in response:
- print('Taking out a personal loan failed with message:', response['error'])
- else:
- print('Taking out a personal loan failed.')
- def loans():
- _fake_loading_bar('Loading Data', duration=0.9)
- response = client_request('loans', {"session_id": connection.session_id})
- success = 'data' in response
- if success:
- for row in response['data']:
- row[-1] = f'{row[-1] * 100:.2f}%'
- print(my_tabulate(response['data'],
- headers=['Loan ID', 'Total amount', 'Remaining', 'Interest p.a.', ],
- floatfmt='.2f',
- tablefmt="pipe"))
- else:
- if 'error' in response:
- print('Order access failed with message:', response['error'])
- else:
- print('Order access failed.')
- def credits():
- _fake_loading_bar('Loading Data', duration=1.6)
- response = client_request('credits', {"session_id": connection.session_id})
- success = 'data' in response
- if success:
- _print_credits_table(response)
- else:
- if 'error' in response:
- print('Listing credits failed with message:', response['error'])
- else:
- print('Listing credits failed.')
- def mro_qualified_credits():
- print('This command lists credits that you can sell to the central bank during the next mean refinancing operations.')
- print('The schedule is called tender calendar and visible through by command `tender_calendar`.')
- _fake_loading_bar('Loading Data', duration=1.6)
- response = client_request('credits', {"session_id": connection.session_id, 'only_next_mro_qualified': True})
- success = 'data' in response
- if success:
- _print_credits_table(response)
- else:
- if 'error' in response:
- print('Listing credits failed with message:', response['error'])
- else:
- print('Listing credits failed.')
- def _print_credits_table(response):
- for row in response['data']:
- row[1] = f'{row[1] * 100:.4f}%'
- print(my_tabulate(response['data'],
- headers=['Bond', 'Coupon', 'Maturity', 'Issuer', ],
- floatfmt='.2f',
- tablefmt="pipe"))
- def repay_loan(loan_id=None, amount=None):
- if loan_id is None:
- loans()
- print('Which loan would you like to pay back?')
- loan_id = input('Loan id:')
- if amount is None:
- print('How much would you like to pay back?')
- amount = input('Amount (type `all` for the remaining loan):')
- if amount != 'all':
- try:
- amount = float(amount) # this can also raise a ValueError
- if amount <= 0:
- raise ValueError
- 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)
- 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:
- print(f'You repayed the specified amount of {CURRENCY_NAME}.')
- else:
- if 'error' in response:
- print('Repaying the loan failed with message:', response['error'])
- else:
- print('Repaying the loan failed.')
- def _global_variables():
- return client_request('global_variables')
- def orders_on(obj_name=None):
- if obj_name is None: # TODO list some available objects
- obj_name = input('Name of object to check: ')
- if obj_name == CURRENCY_NAME.replace(CURRENCY_SYMBOL, 'K'):
- obj_name = CURRENCY_NAME
- _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:
- print(my_tabulate(response['data'],
- headers=['My', 'Buy?', 'Name', 'Size', 'Limit', 'Expires', 'No.'],
- tablefmt="pipe"))
- else:
- if 'error' in response:
- print('Order access failed with message:', response['error'])
- else:
- print('Order access failed.')
- def gift(username=None, obj_name=None, amount=None):
- if username is None:
- username = input('Username of recipient: ')
- if obj_name is None:
- obj_name = input('Name of object to give: ')
- if obj_name == CURRENCY_NAME.replace(CURRENCY_SYMBOL, 'K'):
- obj_name = CURRENCY_NAME
- if amount is None:
- amount = input('How many?: ')
- _fake_loading_bar('Sending Gift', duration=4.2)
- response = client_request('gift',
- {"session_id": connection.session_id,
- "username": username,
- "object_name": obj_name,
- "amount": amount})
- if 'error' in response:
- print('Order access failed with message:', response['error'])
- elif 'message' in response:
- print(response['message'])
- def news():
- _fake_loading_bar('Loading Data', duration=0.76)
- response = client_request('news', {})
- success = 'data' in response
- if success:
- print(my_tabulate(response['data'],
- headers=['Date', 'Title'],
- tablefmt="pipe"))
- else:
- if 'error' in response:
- print('News access failed with message:', response['error'])
- else:
- 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)
- response = client_request('tradables', {})
- success = 'data' in response
- if success:
- print(my_tabulate(response['data'],
- headers=['Name', 'Course', 'Market Cap.'],
- tablefmt="pipe"))
- world_wealth = 0
- for row in response['data']:
- if row[2] is not None:
- world_wealth += row[2]
- print('Estimated worldwide wealth:', world_wealth)
- else:
- if 'error' in response:
- print('Data access failed with message:', response['error'])
- else:
- print('Data access failed.')
- def trades_on(obj_name=None, limit=5):
- limit = float(limit)
- if obj_name is None: # TODO list some available objects
- obj_name = input('Name of object to check: ')
- if obj_name == CURRENCY_NAME.replace(CURRENCY_SYMBOL, 'K'):
- obj_name = CURRENCY_NAME
- _fake_loading_bar('Loading Data', duration=0.34 * limit)
- response = client_request('trades_on', {"session_id": connection.session_id,
- "ownable": obj_name,
- "limit": limit})
- success = 'data' in response
- if success:
- print(my_tabulate(response['data'],
- headers=['Time', 'Volume', 'Price'],
- tablefmt="pipe"))
- else:
- if 'error' in response:
- print('Trades access failed with message:', response['error'])
- else:
- print('Trades access failed.')
- def trades(limit=10):
- limit = float(limit)
- _fake_loading_bar('Loading Data', duration=limit * 0.21)
- response = client_request('trades', {"session_id": connection.session_id,
- "limit": limit})
- success = 'data' in response
- if success:
- print(my_tabulate(response['data'],
- headers=['Buy?', 'Name', 'Volume', 'Price', 'Time'],
- tablefmt="pipe"))
- else:
- if 'error' in response:
- print('Trades access failed with message:', response['error'])
- else:
- print('Trades access failed.')
- def old_orders(include_canceled=None, include_executed=None, limit=10):
- limit = float(limit)
- if include_canceled is None:
- 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)
- response = client_request('old_orders', {"session_id": connection.session_id,
- "include_canceled": include_canceled,
- "include_executed": include_executed,
- "limit": limit})
- success = 'data' in response
- if success:
- print(my_tabulate(response['data'],
- headers=['Buy?', 'Name', 'Size', 'Limit', 'Expiry', 'No.', 'Status'],
- tablefmt="pipe"))
- else:
- if 'error' in response:
- print('Order access failed with message:', response['error'])
- else:
- print('Order access failed.')
- def issue_bond(name=None, coupon=None, run_time=None):
- if name is None:
- name = input('Name of the bond:')
- if not re.fullmatch(OWNABLE_NAME_PATTERN, name):
- print(f'Invalid name: {name}. Name must match {OWNABLE_NAME_PATTERN} and must not be copyright-protected '
- f'(yes, we have an upload filter for that).')
- if coupon is None:
- coupon = input('Coupon (e.g. 0.005 for 0.5% p.a.):')
- 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)
- response = client_request('issue_bond', {"session_id": connection.session_id,
- "name": name,
- "coupon": coupon,
- "run_time": run_time})
- if 'error' in response:
- print('Issuing bond failed with message:', response['error'])
- elif 'message' in response:
- print(response['message'])
- # noinspection PyShadowingBuiltins
- 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()
|