123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- import re
- import uuid
- from datetime import timedelta
- from math import ceil, floor
- from passlib.hash import sha256_crypt
- import model
- import version
- from connection import check_missing_attributes, BadRequest, Forbidden, PreconditionFailed, NotFound
- from game import OWNABLE_NAME_PATTERN, BANK_NAME
- def login(json_request):
- check_missing_attributes(json_request, ['username', 'password'])
- username = json_request['username']
- password = json_request['password']
- session_id = model.login(username, password)
- if session_id:
- return {'session_id': session_id}
- else:
- return Forbidden('Invalid login data')
- def depot(json_request):
- check_missing_attributes(json_request, ['session_id'])
- user_id = model.get_user_id_by_session_id(json_request['session_id'])
- return {'data': model.get_user_ownership(user_id),
- 'own_wealth': float(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)}
- def global_variables(_json_request):
- return model.global_control_values()
- def register(json_request):
- check_missing_attributes(json_request, ['username', 'password'])
- username = json_request['username'].strip()
- if username == '':
- return BadRequest('Username can not be empty.')
- if model.user_exists(username):
- return BadRequest('User already exists.')
- if model.register(username, json_request['password']):
- return {'message': "successfully registered user"}
- else:
- return BadRequest('Registration not successful')
- def order(json_request):
- check_missing_attributes(json_request, ['buy', 'session_id', 'amount', 'ownable', 'time_until_expiration'])
- if not model.ownable_name_exists(json_request['ownable']):
- return BadRequest('This kind of object can not be ordered.')
- buy = json_request['buy']
- sell = not buy
- if not isinstance(buy, bool):
- return BadRequest('`buy` must be a boolean')
- if 'ioc' in json_request:
- ioc = json_request['ioc']
- if not isinstance(ioc, bool):
- raise BadRequest('IOC must be a boolean.')
- else:
- ioc = False
- session_id = json_request['session_id']
- user_id = model.get_user_id_by_session_id(session_id)
- amount = json_request['amount']
- try:
- amount = float(amount) # so that something like 5e6 also works but only integers
- if amount != round(amount):
- raise ValueError
- amount = round(amount)
- except ValueError:
- return BadRequest('Invalid amount.')
- if amount < 0:
- return BadRequest('You can not order a negative amount.')
- if amount < 1:
- return BadRequest('The minimum order size is 1.')
- ownable_name = json_request['ownable']
- time_until_expiration = float(json_request['time_until_expiration'])
- if time_until_expiration < 0:
- return BadRequest('Invalid expiration time.')
- ownable_id = model.ownable_id_by_name(ownable_name)
- model.own(user_id, ownable_name)
- ownership_id = model.get_ownership_id(ownable_id, user_id)
- if 'limit' in json_request and 'stop_loss' not in json_request:
- raise BadRequest('Need to set stop_loss to either True or False for limit orders.')
- try:
- if json_request['limit'] == '':
- limit = None
- elif json_request['limit'] is None:
- limit = None
- else:
- if buy:
- limit = floor(float(json_request['limit']) * 10000) / 10000
- else:
- limit = ceil(float(json_request['limit']) * 10000) / 10000
- except ValueError: # for example when float fails
- return BadRequest('Invalid limit.')
- except KeyError: # for example when limit was not specified
- limit = None
- if limit is not None and limit < 0:
- return BadRequest('Limit must not be negative.')
- if 'stop_loss' in json_request:
- if json_request['stop_loss'] == '':
- stop_loss = None
- elif json_request['stop_loss'] is None:
- stop_loss = None
- else:
- stop_loss = json_request['stop_loss']
- else:
- stop_loss = None
- if stop_loss and limit is None:
- return BadRequest('You need to specify a limit for stop-loss orders')
- if ioc and stop_loss:
- raise BadRequest('Stop loss orders can not be IOC orders.')
- if sell:
- if not model.user_has_at_least_available(amount, user_id, ownable_id):
- return BadRequest('You can not sell more than you own (this also takes into account existing '
- 'sell orders and, if you are a bank, required minimum reserves at the ).')
- try:
- expiry = model.current_db_timestamp() + timedelta(minutes=time_until_expiration).total_seconds()
- except OverflowError:
- return BadRequest('The expiration time is too far in the future.')
- model.place_order(buy, ownership_id, limit, stop_loss, amount, expiry, ioc)
- return {'message': "Order placed."}
- def gift(json_request):
- check_missing_attributes(json_request, ['session_id', 'amount', 'object_name', 'username'])
- if not model.ownable_name_exists(json_request['object_name']):
- return BadRequest('This kind of object can not be given away.')
- if json_request['username'] == BANK_NAME or not model.user_exists(json_request['username']):
- return BadRequest('There is no user with this name.')
- try:
- amount = float(json_request['amount'])
- except ValueError:
- return BadRequest('Invalid amount.')
- 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.user_available_ownable(sender_id, ownable_id) == 0:
- return BadRequest('You do not own any of these.')
- wealth = model.user_wealth(user_id=sender_id)
- if wealth < amount:
- raise PreconditionFailed(f'Your current wealth, computed from your owned equities minus debt, is {wealth}.\n'
- f'To protect you from giving away more than you can afford, you not gift any amounts larger than this to other players.'
- f'You can still try to emulate gifting by selling something at a low price and buying it back afterwards at a higher price.')
- recipient_id = model.get_user_id_by_name(json_request['username'])
- model.send_ownable(sender_id,
- recipient_id,
- ownable_id,
- amount)
- return {'message': f"Sent {amount} {model.ownable_name_by_id(ownable_id)} to {model.user_name_by_id(recipient_id)}."}
- def orders(json_request):
- check_missing_attributes(json_request, ['session_id'])
- data = model.get_user_orders(model.get_user_id_by_session_id(json_request['session_id']))
- return {'data': data}
- def loans(json_request):
- check_missing_attributes(json_request, ['session_id'])
- data = model.get_user_loans(model.get_user_id_by_session_id(json_request['session_id']))
- return {'data': data}
- def credits(json_request):
- if 'issuer' in json_request:
- issuer_id = model.get_user_id_by_name(json_request['issuer'])
- else:
- issuer_id = None
- if 'only_next_mro_qualified' in json_request:
- only_next_mro_qualified = json_request['only_next_mro_qualified']
- if isinstance(only_next_mro_qualified, str):
- raise BadRequest
- else:
- only_next_mro_qualified = False
- data = model.credits(issuer_id, only_next_mro_qualified)
- return {'data': data}
- def orders_on(json_request):
- check_missing_attributes(json_request, ['session_id', 'ownable'])
- if not model.ownable_name_exists(json_request['ownable']):
- return BadRequest('This kind of object can not be ordered.')
- user_id = model.get_user_id_by_session_id(json_request['session_id'])
- ownable_id = model.ownable_id_by_name(json_request['ownable'])
- data = model.get_ownable_orders(user_id, ownable_id)
- return {'data': data}
- def old_orders(json_request):
- check_missing_attributes(json_request, ['session_id', 'include_canceled', 'include_executed', 'limit'])
- include_executed = json_request['include_executed']
- include_canceled = json_request['include_canceled']
- user_id = model.get_user_id_by_session_id(json_request['session_id'])
- limit = json_request['limit']
- data = model.get_old_orders(user_id, include_executed, include_canceled, limit)
- return {'data': data}
- def cancel_order(json_request):
- check_missing_attributes(json_request, ['session_id', 'order_id'])
- if not model.user_has_order_with_id(json_request['session_id'], json_request['order_id']):
- return BadRequest('You do not have an order with that number.')
- model.delete_order(json_request['order_id'], 'Canceled')
- return {'message': "Successfully deleted order"}
- def change_password(json_request):
- check_missing_attributes(json_request, ['session_id', 'password'])
- salt = str(uuid.uuid4())
- hashed_password = sha256_crypt.encrypt(json_request['password'] + salt)
- model.change_password(json_request['session_id'], hashed_password, salt)
- model.sign_out_user(json_request['session_id'])
- return {'message': "Successfully changed password"}
- def logout(json_request):
- check_missing_attributes(json_request, ['session_id'])
- model.sign_out_user(json_request['session_id'])
- return {'message': "Successfully logged out"}
- def buy_banking_license(json_request):
- check_missing_attributes(json_request, ['session_id'])
- user_id = model.get_user_id_by_session_id(json_request['session_id'])
- 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_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)
- return {'message': "Successfully bought banking license"}
- 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()}
- def trades(json_request):
- check_missing_attributes(json_request, ['session_id', 'limit'])
- return {'data': model.trades(model.get_user_id_by_session_id(json_request['session_id']), json_request['limit'])}
- def trades_on(json_request):
- check_missing_attributes(json_request, ['session_id', 'ownable', 'limit'])
- if not model.ownable_name_exists(json_request['ownable']):
- return BadRequest('This kind of object can not have transactions.')
- return {'data': model.trades_on(model.ownable_id_by_name(json_request['ownable']), json_request['limit'])}
- def leaderboard(_json_request):
- return {'data': model.leaderboard()}
- def take_out_personal_loan(json_request):
- check_missing_attributes(json_request, ['session_id', 'amount', ])
- amount = json_request['amount']
- if not isinstance(amount, float) or amount <= 0:
- raise BadRequest('Amount must be a number larger than 0')
- user_id = model.get_user_id_by_session_id(json_request['session_id'])
- model.take_out_personal_loan(user_id, amount)
- return {'message': "Successfully took out personal loan"}
- 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']
- 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']
- if run_time == 'next_mro':
- maturity_dt = model.next_mro_maturity()
- 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"}
- def repay_loan(json_request):
- check_missing_attributes(json_request, ['session_id', 'amount', 'loan_id'])
- amount = json_request['amount']
- user_id = model.get_user_id_by_session_id(json_request['session_id'])
- loan_id = json_request['loan_id']
- if not model.user_has_loan_with_id(user_id, loan_id, ):
- raise NotFound('Unknown loan ID.')
- if amount == 'all':
- amount = model.loan_remaining_amount(loan_id)
- if amount < 0:
- raise BadRequest('You can not repay negative amounts.')
- 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.')
- else:
- raise PreconditionFailed('You do not have enough money.')
- if not model.loan_id_exists(loan_id) or model.loan_recipient_id(loan_id) != user_id:
- raise NotFound(f'You do not have a loan with that id.')
- loan_volume = model.loan_remaining_amount(loan_id)
- if loan_volume < amount:
- raise PreconditionFailed(f'You can not repay more than the remaining loan volume of {loan_volume}.')
- model.repay_loan(loan_id, amount, known_user_id=user_id)
- return {'message': "Successfully repayed loan"}
- def server_version(_json_request):
- return {'version': version.__version__}
- def _before_request(_json_request):
- # update tender calendar
- model.update_tender_calendar()
- for mro_id, maturity_dt, min_interest, mro_dt in model.triggered_mros():
- assert maturity_dt > mro_dt
- # pay interest rates for loans until this mro
- model.pay_loan_interest(until=mro_dt)
- # pay interest rates for credits until this mro
- model.pay_bond_interest(until=mro_dt)
- # pay deposit facility for minimum reserves until this mro
- model.pay_deposit_facility(until=mro_dt)
- # handle MROs
- model.mro(mro_id, maturity_dt, min_interest)
- # pay interest rates for loans until current time
- model.pay_loan_interest()
- # pay interest rates for credits until current time
- model.pay_bond_interest()
- # pay deposit facility for minimum reserves until current time
- model.pay_deposit_facility()
|