model.py 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593
  1. import json
  2. import os
  3. import random
  4. import re
  5. import sqlite3 as db
  6. import uuid
  7. from datetime import datetime
  8. from logging import INFO
  9. from math import floor, inf
  10. from shutil import copyfile
  11. from typing import Optional, Dict
  12. from passlib.handlers.sha2_crypt import sha256_crypt
  13. import db_setup
  14. from game import CURRENCY_NAME, logger, DB_NAME, MIN_INTEREST_INTERVAL, BANK_NAME, MRO_INTERVAL, MRO_RUNNING_TIME, random_ownable_name
  15. DBName = str
  16. connections: Dict[DBName, db.Connection] = {}
  17. current_connection: Optional[db.Connection] = None
  18. current_cursor: Optional[db.Cursor] = None
  19. current_db_name: Optional[DBName] = None
  20. current_user_id: Optional[int] = None
  21. def execute(sql, parameters=()):
  22. if not re.search(r"(?i)\s*SELECT", sql):
  23. logger.info(sql, 'sql_query', data=json.dumps(parameters))
  24. return current_cursor.execute(sql, parameters)
  25. def executemany(sql, parameters=()):
  26. if not re.search(r"(?i)\s*SELECT", sql):
  27. logger.info(sql, 'sql_query_many', data=json.dumps(parameters))
  28. return current_cursor.executemany(sql, parameters)
  29. def valid_db_name(name):
  30. return re.match(r"[a-z0-9.-]{0,20}", name)
  31. def query_save_name():
  32. while True:
  33. # save_name = input('Name of the database (You can also enter a new filename here): ')
  34. save_name = DB_NAME
  35. if valid_db_name(save_name):
  36. return save_name
  37. else:
  38. print('Must match "[a-z0-9.-]{0,20}"')
  39. def connect(db_name=None, create_if_not_exists=False):
  40. """
  41. connects to the database with the given name, if it exists
  42. if the database does not exist an exception is raised
  43. (unless create_if_not_exists is true, then the database is created)
  44. if there is already a connection to this database, that connection is used
  45. :return: the connection and the connections' cursor
  46. """
  47. if db_name is None:
  48. db_name = query_save_name()
  49. if not db_name.endswith('.db'):
  50. db_name += '.db'
  51. db_name = db_name.lower()
  52. if not os.path.isfile(db_name) and not create_if_not_exists:
  53. raise FileNotFoundError('There is no database with this name.')
  54. creating_new_db = not os.path.isfile(db_name)
  55. if db_name not in connections:
  56. try:
  57. db_connection = db.connect(db_name, check_same_thread=False)
  58. db_setup.create_functions(db_connection)
  59. db_setup.set_pragmas(db_connection.cursor())
  60. # connection.text_factory = lambda x: x.encode('latin-1')
  61. except db.Error as e:
  62. print("Database error %s:" % e.args[0])
  63. raise
  64. connections[db_name] = db_connection
  65. global current_connection
  66. global current_db_name
  67. global current_cursor
  68. current_connection = connections[db_name]
  69. current_cursor = connections[db_name].cursor()
  70. current_db_name = db_name
  71. if creating_new_db:
  72. try:
  73. if os.path.isfile('/test-db/' + db_name):
  74. print('Using test database containing fake data')
  75. copyfile('/test-db/' + db_name, db_name)
  76. else:
  77. logger.log('Creating database', INFO, 'database_creation')
  78. logger.commit()
  79. setup()
  80. except Exception:
  81. if current_connection is not None:
  82. current_connection.rollback()
  83. if db_name in connections:
  84. disconnect(db_name, rollback=True)
  85. os.remove(db_name)
  86. current_connection = None
  87. current_cursor = None
  88. current_db_name = None
  89. raise
  90. def disconnect(connection_name, rollback=True):
  91. global connections
  92. if connection_name not in connections:
  93. raise ValueError('Invalid connection')
  94. if rollback:
  95. connections[connection_name].rollback()
  96. else:
  97. connections[connection_name].commit()
  98. connections[connection_name].close()
  99. del connections[connection_name]
  100. def setup():
  101. db_setup.setup(current_cursor)
  102. def login(username, password):
  103. execute('''
  104. SELECT rowid, password, salt
  105. FROM users
  106. WHERE username = ?
  107. ''', (username,))
  108. data = current_cursor.fetchone()
  109. if not data:
  110. return None
  111. user_id, hashed_password, salt = data
  112. # if a ValueError occurs here, then most likely a password that was stored as plain text
  113. if sha256_crypt.verify(password + salt, hashed_password):
  114. return new_session(user_id)
  115. else:
  116. return None
  117. def register(username, password):
  118. salt = str(uuid.uuid4())
  119. hashed_password = sha256_crypt.using(rounds=100000).encrypt(str(password) + salt)
  120. connect()
  121. if username == '':
  122. return False
  123. if password == '':
  124. return False
  125. execute('''
  126. INSERT INTO users
  127. (username, password, salt)
  128. VALUES (? , ?, ?)
  129. ''', (username, hashed_password, salt))
  130. own(get_user_id_by_name(username), CURRENCY_NAME)
  131. return True
  132. def own(user_id, ownable_name, amount=0):
  133. if not isinstance(ownable_name, str):
  134. return AssertionError('A name must be a string.')
  135. execute('''
  136. INSERT OR IGNORE INTO ownership (user_id, ownable_id, amount)
  137. SELECT ?, (SELECT rowid FROM ownables WHERE name = ?), ?
  138. ''', (user_id, ownable_name, amount))
  139. def send_ownable(from_user_id, to_user_id, ownable_id, amount):
  140. if amount < 0:
  141. raise AssertionError('Can not send negative amount')
  142. bank_id_ = bank_id()
  143. if from_user_id != bank_id_ and not is_bond_of_user(ownable_id, from_user_id):
  144. execute('''
  145. UPDATE ownership
  146. SET amount = amount - ?
  147. WHERE user_id = ?
  148. AND ownable_id = ?
  149. ''', (amount, from_user_id, ownable_id,))
  150. own(to_user_id, ownable_name_by_id(ownable_id))
  151. if not is_bond_of_user(ownable_id, to_user_id):
  152. execute('''
  153. UPDATE ownership
  154. SET amount = amount + ?
  155. WHERE user_id = ?
  156. AND ownable_id = ?
  157. ''', (amount, to_user_id, ownable_id,))
  158. return True
  159. def new_session(user_id):
  160. session_id = str(uuid.uuid4())
  161. execute('''
  162. INSERT INTO SESSIONS
  163. (user_id, session_id)
  164. VALUES (? , ?)
  165. ''', (user_id, session_id))
  166. return session_id
  167. def drop_old_sessions():
  168. execute(''' -- no need to optimize this very well
  169. DELETE FROM sessions
  170. WHERE
  171. (SELECT COUNT(*) as newer
  172. FROM sessions s2
  173. WHERE user_id = s2.user_id
  174. AND rowid < s2.rowid) >= 10
  175. ''')
  176. def user_exists(username):
  177. execute('''
  178. SELECT rowid
  179. FROM users
  180. WHERE username = ?
  181. ''', (username,))
  182. if current_cursor.fetchone():
  183. return True
  184. else:
  185. return False
  186. def get_user_id_by_session_id(session_id):
  187. execute('''
  188. SELECT users.rowid
  189. FROM sessions, users
  190. WHERE sessions.session_id = ?
  191. AND users.rowid = sessions.user_id
  192. ''', (session_id,))
  193. ids = current_cursor.fetchone()
  194. if not ids:
  195. return False
  196. return ids[0]
  197. def get_user_id_by_name(username):
  198. execute('''
  199. SELECT users.rowid
  200. FROM users
  201. WHERE username = ?
  202. ''', (username,))
  203. return current_cursor.fetchone()[0]
  204. def get_user_ownership(user_id):
  205. data = execute('''
  206. SELECT
  207. ownables.name,
  208. ownable_id, -- this is used for computing the available amount
  209. COALESCE (
  210. CASE -- sum score for each of the users ownables
  211. WHEN ownership.ownable_id = ? THEN 1
  212. ELSE (SELECT price
  213. FROM transactions
  214. WHERE ownable_id = ownership.ownable_id
  215. ORDER BY rowid DESC -- equivalent to ordering by dt
  216. LIMIT 1)
  217. END, 0) AS price,
  218. (SELECT MAX("limit")
  219. FROM orders, ownership o2
  220. WHERE o2.rowid = orders.ownership_id
  221. AND o2.ownable_id = ownership.ownable_id
  222. AND buy
  223. AND NOT stop_loss) AS bid,
  224. (SELECT MIN("limit")
  225. FROM orders, ownership o2
  226. WHERE o2.rowid = orders.ownership_id
  227. AND o2.ownable_id = ownership.ownable_id
  228. AND NOT buy
  229. AND NOT stop_loss) AS ask
  230. FROM ownership, ownables
  231. WHERE user_id = ?
  232. AND (ownership.amount >= 0.01 OR ownership.amount <= -0.01 OR ownership.ownable_id = ?)
  233. AND ownership.ownable_id = ownables.rowid
  234. ORDER BY ownables.rowid ASC
  235. ''', (currency_id(), user_id, currency_id(),)).fetchall()
  236. data = [list(row) for row in data]
  237. for row in data:
  238. ownable_id = row[1]
  239. available_amount = user_available_ownable(user_id, ownable_id)
  240. row[1] = available_amount
  241. return data
  242. def bank_id():
  243. execute('''
  244. SELECT users.rowid
  245. FROM users
  246. WHERE username = ?
  247. ''', (BANK_NAME,))
  248. return current_cursor.fetchone()[0]
  249. def valid_session_id(session_id):
  250. execute('''
  251. SELECT rowid
  252. FROM sessions
  253. WHERE session_id = ?
  254. ''', (session_id,))
  255. if current_cursor.fetchone():
  256. return True
  257. else:
  258. return False
  259. def get_user_orders(user_id):
  260. execute('''
  261. SELECT
  262. CASE
  263. WHEN orders.buy THEN 'Buy'
  264. ELSE 'Sell'
  265. END,
  266. ownables.name,
  267. (orders.ordered_amount - orders.executed_amount) || '/' || orders.ordered_amount,
  268. orders."limit",
  269. CASE
  270. WHEN orders."limit" IS NULL THEN NULL
  271. WHEN orders.stop_loss THEN 'Yes'
  272. ELSE 'No'
  273. END,
  274. datetime(orders.expiry_dt, 'localtime'),
  275. orders.rowid
  276. FROM orders, ownables, ownership
  277. WHERE ownership.user_id = ?
  278. AND ownership.ownable_id = ownables.rowid
  279. AND orders.ownership_id = ownership.rowid
  280. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  281. ''', (user_id,))
  282. return current_cursor.fetchall()
  283. def get_user_loans(user_id):
  284. execute('''
  285. SELECT
  286. rowid,
  287. total_amount,
  288. amount,
  289. interest_rate
  290. FROM loans
  291. WHERE user_id is ?
  292. ORDER BY rowid ASC
  293. ''', (user_id,))
  294. return current_cursor.fetchall()
  295. def next_mro_dt(dt=None):
  296. if dt is None:
  297. dt = current_db_timestamp()
  298. return execute('''
  299. SELECT MIN(t.dt) FROM tender_calendar t WHERE t.dt > ?
  300. ''', (dt,)).fetchone()[0]
  301. def next_mro_interest(dt=None):
  302. return execute('''
  303. SELECT t.mro_interest FROM tender_calendar t WHERE t.dt = ?
  304. ''', (next_mro_dt(dt),)).fetchone()[0]
  305. def credits(issuer_id=None, only_next_mro_qualified=False):
  306. if issuer_id is not None:
  307. issuer_condition = 'issuer.rowid = ?'
  308. issuer_params = (issuer_id,)
  309. else:
  310. issuer_condition = '1'
  311. issuer_params = ()
  312. if only_next_mro_qualified:
  313. only_next_mro_condition = ''' -- noinspection SqlResolve @ any/"credits"
  314. SELECT EXISTS(
  315. SELECT *
  316. FROM banks b
  317. JOIN tender_calendar t ON t.maturity_dt = credits.maturity_dt
  318. WHERE credits.issuer_id = b.user_id
  319. AND credits.coupon >= t.mro_interest
  320. AND t.dt = ?
  321. )
  322. '''
  323. only_next_mro_params = (next_mro_dt(),)
  324. else:
  325. only_next_mro_condition = '1'
  326. only_next_mro_params = ()
  327. execute(f'''
  328. SELECT
  329. name,
  330. coupon,
  331. datetime(maturity_dt, 'unixepoch', 'localtime'),
  332. username
  333. FROM credits
  334. JOIN ownables o on credits.ownable_id = o.rowid
  335. JOIN users issuer on credits.issuer_id = issuer.rowid
  336. WHERE ({issuer_condition})
  337. AND ({only_next_mro_condition})
  338. ORDER BY coupon * (maturity_dt - ?) DESC
  339. ''', (*issuer_params, *only_next_mro_params, current_db_timestamp(),))
  340. return current_cursor.fetchall()
  341. def get_ownable_orders(user_id, ownable_id):
  342. execute('''
  343. SELECT
  344. CASE
  345. WHEN ownership.user_id = ? THEN 'X'
  346. ELSE NULL
  347. END,
  348. CASE
  349. WHEN orders.buy THEN 'Buy'
  350. ELSE 'Sell'
  351. END,
  352. ownables.name,
  353. orders.ordered_amount - orders.executed_amount,
  354. orders."limit",
  355. datetime(orders.expiry_dt, 'localtime'),
  356. orders.rowid
  357. FROM orders, ownables, ownership
  358. WHERE ownership.ownable_id = ?
  359. AND ownership.ownable_id = ownables.rowid
  360. AND orders.ownership_id = ownership.rowid
  361. AND (orders.stop_loss IS NULL OR NOT orders.stop_loss)
  362. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  363. ''', (user_id, ownable_id,))
  364. return current_cursor.fetchall()
  365. def sell_ordered_amount(user_id, ownable_id):
  366. execute('''
  367. SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  368. FROM orders, ownership
  369. WHERE ownership.rowid = orders.ownership_id
  370. AND ownership.user_id = ?
  371. AND ownership.ownable_id = ?
  372. AND NOT orders.buy
  373. ''', (user_id, ownable_id))
  374. return current_cursor.fetchone()[0]
  375. def is_bond_of_user(ownable_id, user_id):
  376. execute('''
  377. SELECT EXISTS(
  378. SELECT * FROM credits
  379. WHERE ownable_id = ?
  380. AND issuer_id = ?
  381. )
  382. ''', (ownable_id, user_id,))
  383. return current_cursor.fetchone()[0]
  384. def user_available_ownable(user_id, ownable_id):
  385. if is_bond_of_user(ownable_id, user_id):
  386. return inf
  387. if ownable_id == currency_id() and user_has_banking_license(user_id):
  388. minimum_reserve = required_minimum_reserve(user_id) + sell_ordered_amount(user_id, ownable_id)
  389. else:
  390. minimum_reserve = sell_ordered_amount(user_id, ownable_id)
  391. execute('''
  392. SELECT amount
  393. FROM ownership
  394. WHERE user_id = ?
  395. AND ownable_id = ?
  396. ''', (user_id, ownable_id))
  397. return current_cursor.fetchone()[0] - minimum_reserve
  398. def user_has_at_least_available(amount, user_id, ownable_id):
  399. if not isinstance(amount, float) and not isinstance(amount, int):
  400. # comparison of float with strings does not work so well in sql
  401. raise ValueError()
  402. return user_available_ownable(user_id, ownable_id) >= amount
  403. def news():
  404. execute('''
  405. SELECT dt, title FROM
  406. (SELECT *, rowid
  407. FROM news
  408. ORDER BY news.rowid DESC -- equivalent to order by dt
  409. LIMIT 20) n
  410. ORDER BY rowid ASC -- equivalent to order by dt
  411. ''')
  412. return current_cursor.fetchall()
  413. def ownable_name_exists(name):
  414. execute('''
  415. SELECT rowid
  416. FROM ownables
  417. WHERE name = ?
  418. ''', (name,))
  419. if current_cursor.fetchone():
  420. return True
  421. else:
  422. return False
  423. def new_stock(expiry, name=None):
  424. name = new_random_ownable_name(name)
  425. execute('''
  426. INSERT INTO ownables(name)
  427. VALUES (?)
  428. ''', (name,))
  429. new_news('A new stock can now be bought: ' + name)
  430. if random.getrandbits(1):
  431. new_news('Experts expect the price of ' + name + ' to fall')
  432. else:
  433. new_news('Experts expect the price of ' + name + ' to rise')
  434. amount = random.randrange(100, 10000)
  435. price = random.randrange(10000, 20000) / amount
  436. ownable_id = ownable_id_by_name(name)
  437. own(bank_id(), name, amount)
  438. bank_order(False,
  439. ownable_id,
  440. price,
  441. amount,
  442. expiry,
  443. ioc=False)
  444. return name
  445. def new_random_ownable_name(name):
  446. while name is None:
  447. name = random_ownable_name()
  448. if ownable_name_exists(name):
  449. name = None
  450. return name
  451. def ownable_id_by_name(ownable_name):
  452. execute('''
  453. SELECT rowid
  454. FROM ownables
  455. WHERE name = ?
  456. ''', (ownable_name,))
  457. return current_cursor.fetchone()[0]
  458. def get_ownership_id(ownable_id, user_id):
  459. execute('''
  460. SELECT rowid
  461. FROM ownership
  462. WHERE ownable_id = ?
  463. AND user_id = ?
  464. ''', (ownable_id, user_id,))
  465. return current_cursor.fetchone()[0]
  466. def currency_id():
  467. execute('''
  468. SELECT rowid
  469. FROM ownables
  470. WHERE name = ?
  471. ''', (CURRENCY_NAME,))
  472. return current_cursor.fetchone()[0]
  473. def user_available_money(user_id):
  474. return user_available_ownable(user_id, currency_id())
  475. def delete_order(order_id, new_order_status):
  476. execute('''
  477. INSERT INTO order_history
  478. (ownership_id, buy, "limit", ordered_amount, executed_amount, expiry_dt, status, order_id)
  479. SELECT
  480. ownership_id,
  481. buy,
  482. "limit",
  483. ordered_amount,
  484. executed_amount,
  485. expiry_dt,
  486. ?,
  487. rowid
  488. FROM orders
  489. WHERE rowid = ?
  490. ''', (new_order_status, order_id,))
  491. execute('''
  492. DELETE FROM orders
  493. WHERE rowid = ?
  494. ''', (order_id,))
  495. def current_value(ownable_id):
  496. if ownable_id == currency_id():
  497. return 1
  498. execute('''SELECT price
  499. FROM transactions
  500. WHERE ownable_id = ?
  501. ORDER BY rowid DESC -- equivalent to order by dt
  502. LIMIT 1
  503. ''', (ownable_id,))
  504. return current_cursor.fetchone()[0]
  505. def execute_orders(ownable_id):
  506. orders_traded = False
  507. while True:
  508. # find order to execute
  509. execute('''
  510. -- two best orders
  511. SELECT * FROM (
  512. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  513. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  514. WHERE buy_order.buy AND NOT sell_order.buy
  515. AND buyer.rowid = buy_order.ownership_id
  516. AND seller.rowid = sell_order.ownership_id
  517. AND buyer.ownable_id = ?
  518. AND seller.ownable_id = ?
  519. AND buy_order."limit" IS NULL
  520. AND sell_order."limit" IS NULL
  521. ORDER BY buy_order.rowid ASC,
  522. sell_order.rowid ASC
  523. LIMIT 1)
  524. UNION ALL -- best buy orders
  525. SELECT * FROM (
  526. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  527. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  528. WHERE buy_order.buy AND NOT sell_order.buy
  529. AND buyer.rowid = buy_order.ownership_id
  530. AND seller.rowid = sell_order.ownership_id
  531. AND buyer.ownable_id = ?
  532. AND seller.ownable_id = ?
  533. AND buy_order."limit" IS NULL
  534. AND sell_order."limit" IS NOT NULL
  535. AND NOT sell_order.stop_loss
  536. ORDER BY sell_order."limit" ASC,
  537. buy_order.rowid ASC,
  538. sell_order.rowid ASC
  539. LIMIT 1)
  540. UNION ALL -- best sell orders
  541. SELECT * FROM (
  542. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  543. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  544. WHERE buy_order.buy AND NOT sell_order.buy
  545. AND buyer.rowid = buy_order.ownership_id
  546. AND seller.rowid = sell_order.ownership_id
  547. AND buyer.ownable_id = ?
  548. AND seller.ownable_id = ?
  549. AND buy_order."limit" IS NOT NULL
  550. AND NOT buy_order.stop_loss
  551. AND sell_order."limit" IS NULL
  552. ORDER BY buy_order."limit" DESC,
  553. buy_order.rowid ASC,
  554. sell_order.rowid ASC
  555. LIMIT 1)
  556. UNION ALL -- both limit orders
  557. SELECT * FROM (
  558. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  559. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  560. WHERE buy_order.buy AND NOT sell_order.buy
  561. AND buyer.rowid = buy_order.ownership_id
  562. AND seller.rowid = sell_order.ownership_id
  563. AND buyer.ownable_id = ?
  564. AND seller.ownable_id = ?
  565. AND buy_order."limit" IS NOT NULL
  566. AND sell_order."limit" IS NOT NULL
  567. AND sell_order."limit" <= buy_order."limit"
  568. AND NOT sell_order.stop_loss
  569. AND NOT buy_order.stop_loss
  570. ORDER BY buy_order."limit" DESC,
  571. sell_order."limit" ASC,
  572. buy_order.rowid ASC,
  573. sell_order.rowid ASC
  574. LIMIT 1)
  575. LIMIT 1
  576. ''', tuple(ownable_id for _ in range(8)))
  577. matching_orders = current_cursor.fetchone()
  578. # return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  579. # ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  580. # user_id,user_id,rowid,rowid)
  581. if not matching_orders:
  582. break
  583. _, buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, _, \
  584. _, sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, _, \
  585. buyer_id, seller_id, buy_order_id, sell_order_id \
  586. = matching_orders
  587. if buy_limit is None and sell_limit is None:
  588. price = current_value(ownable_id)
  589. elif buy_limit is None:
  590. price = sell_limit
  591. elif sell_limit is None:
  592. price = buy_limit
  593. else: # both not NULL
  594. # the price of the older order is used, just like in the real exchange
  595. if buy_order_id < sell_order_id:
  596. price = buy_limit
  597. else:
  598. price = sell_limit
  599. buyer_money = user_available_money(buyer_id)
  600. def _my_division(x, y):
  601. try:
  602. return floor(x / y)
  603. except ZeroDivisionError:
  604. return float('Inf')
  605. amount = min(buy_order_amount - buy_executed_amount,
  606. sell_order_amount - sell_executed_amount,
  607. _my_division(buyer_money, price))
  608. if amount < 0:
  609. amount = 0
  610. if amount == 0: # probable because buyer has not enough money
  611. delete_order(buy_order_id, 'Unable to pay')
  612. continue
  613. buy_order_finished = (buy_order_amount - buy_executed_amount - amount <= 0) or (
  614. buyer_money - amount * price < price)
  615. sell_order_finished = (sell_order_amount - sell_executed_amount - amount <= 0)
  616. if price < 0 or amount <= 0: # price of 0 is possible though unlikely
  617. return AssertionError()
  618. # actually execute the order, but the bank does not send or receive anything
  619. send_ownable(buyer_id, seller_id, currency_id(), price * amount)
  620. send_ownable(seller_id, buyer_id, ownable_id, amount)
  621. # update order execution state
  622. execute('''
  623. UPDATE orders
  624. SET executed_amount = executed_amount + ?
  625. WHERE rowid = ?
  626. OR rowid = ?
  627. ''', (amount, buy_order_id, sell_order_id))
  628. if buy_order_finished:
  629. delete_order(buy_order_id, 'Executed')
  630. orders_traded = True
  631. if sell_order_finished:
  632. delete_order(sell_order_id, 'Executed')
  633. orders_traded = True
  634. if seller_id != buyer_id: # prevent showing self-transactions
  635. execute('''
  636. INSERT INTO transactions
  637. (price, ownable_id, amount, buyer_id, seller_id)
  638. VALUES(?, ?, ?, ?, ?)
  639. ''', (price, ownable_id, amount, buyer_id, seller_id))
  640. # trigger stop-loss orders
  641. if buyer_id != seller_id:
  642. execute('''
  643. UPDATE orders
  644. SET stop_loss = NULL,
  645. "limit" = NULL
  646. WHERE stop_loss IS NOT NULL
  647. AND stop_loss
  648. AND ? IN (SELECT ownable_id FROM ownership WHERE rowid = ownership_id)
  649. AND ((buy AND "limit" < ?) OR (NOT buy AND "limit" > ?))
  650. ''', (ownable_id, price, price,))
  651. def ownable_id_by_ownership_id(ownership_id):
  652. execute('''
  653. SELECT ownable_id
  654. FROM ownership
  655. WHERE rowid = ?
  656. ''', (ownership_id,))
  657. return current_cursor.fetchone()[0]
  658. def ownable_name_by_id(ownable_id):
  659. execute('''
  660. SELECT name
  661. FROM ownables
  662. WHERE rowid = ?
  663. ''', (ownable_id,))
  664. return current_cursor.fetchone()[0]
  665. def user_name_by_id(user_id):
  666. execute('''
  667. SELECT username
  668. FROM users
  669. WHERE rowid = ?
  670. ''', (user_id,))
  671. return current_cursor.fetchone()[0]
  672. def bank_order(buy, ownable_id, limit, amount, expiry, ioc):
  673. if not limit:
  674. raise AssertionError('The bank does not give away anything.')
  675. place_order(buy,
  676. get_ownership_id(ownable_id, bank_id()),
  677. limit,
  678. False,
  679. amount,
  680. expiry,
  681. ioc=ioc)
  682. ownable_name = ownable_name_by_id(ownable_id)
  683. new_news('External investors are selling ' + ownable_name + ' atm')
  684. def current_db_time(): # might differ from datetime.datetime.now() for time zone reasons
  685. connect()
  686. execute('''
  687. SELECT datetime('now')
  688. ''')
  689. return current_cursor.fetchone()[0]
  690. def current_db_timestamp():
  691. connect()
  692. execute('''
  693. SELECT CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)
  694. ''')
  695. return int(current_cursor.fetchone()[0])
  696. def place_order(buy, ownership_id, limit, stop_loss, amount, expiry, ioc: bool):
  697. if isinstance(expiry, datetime):
  698. expiry = expiry.timestamp()
  699. execute(''' INSERT INTO orders
  700. (buy, ownership_id, "limit", stop_loss, ordered_amount, expiry_dt, ioc)
  701. VALUES (?, ?, ?, ?, ?, ?, ?)
  702. ''', (buy, ownership_id, limit, stop_loss, amount, expiry, ioc))
  703. execute_orders(ownable_id_by_ownership_id(ownership_id))
  704. execute('''DELETE FROM orders WHERE ioc''')
  705. return True
  706. def trades_on(ownable_id, limit):
  707. execute('''
  708. SELECT datetime(dt,'localtime'), amount, price
  709. FROM transactions
  710. WHERE ownable_id = ?
  711. ORDER BY rowid DESC -- equivalent to order by dt
  712. LIMIT ?
  713. ''', (ownable_id, limit,))
  714. return current_cursor.fetchall()
  715. def trades(user_id, limit):
  716. execute('''
  717. SELECT
  718. (CASE WHEN seller_id = ? THEN 'Sell' ELSE 'Buy' END),
  719. (SELECT name FROM ownables WHERE rowid = transactions.ownable_id),
  720. amount,
  721. price,
  722. datetime(dt,'localtime')
  723. FROM transactions
  724. WHERE seller_id = ? OR buyer_id = ?
  725. ORDER BY rowid DESC -- equivalent to order by dt
  726. LIMIT ?
  727. ''', (user_id, user_id, user_id, limit,))
  728. return current_cursor.fetchall()
  729. def drop_expired_orders():
  730. execute('''
  731. SELECT rowid, ownership_id, * FROM orders
  732. WHERE expiry_dt < CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)
  733. ''')
  734. data = current_cursor.fetchall()
  735. for order in data:
  736. order_id = order[0]
  737. delete_order(order_id, 'Expired')
  738. return data
  739. def user_has_order_with_id(session_id, order_id):
  740. execute('''
  741. SELECT orders.rowid
  742. FROM orders, ownership, sessions
  743. WHERE orders.rowid = ?
  744. AND sessions.session_id = ?
  745. AND sessions.user_id = ownership.user_id
  746. AND ownership.rowid = orders.ownership_id
  747. ''', (order_id, session_id,))
  748. if current_cursor.fetchone():
  749. return True
  750. else:
  751. return False
  752. def leaderboard():
  753. score_expression = '''
  754. -- noinspection SqlResolve @ any/"users"
  755. SELECT (
  756. SELECT COALESCE(SUM(
  757. CASE -- sum score for each of the users ownables
  758. WHEN ownership.ownable_id = ? THEN ownership.amount
  759. ELSE ownership.amount * (SELECT price
  760. FROM transactions
  761. WHERE ownable_id = ownership.ownable_id
  762. ORDER BY rowid DESC -- equivalent to ordering by dt
  763. LIMIT 1)
  764. END
  765. ), 0)
  766. FROM ownership
  767. WHERE ownership.user_id = users.rowid)
  768. -
  769. ( SELECT COALESCE(SUM(
  770. amount
  771. ), 0)
  772. FROM loans
  773. WHERE loans.user_id = users.rowid)
  774. '''
  775. execute(f'''
  776. SELECT *
  777. FROM ( -- one score for each user
  778. SELECT
  779. username,
  780. ({score_expression}) AS score
  781. FROM users
  782. WHERE users.username != ?
  783. ) AS scores
  784. ORDER BY score DESC
  785. LIMIT 50
  786. ''', (currency_id(), BANK_NAME))
  787. return current_cursor.fetchall()
  788. def user_wealth(user_id):
  789. score_expression = '''
  790. SELECT (
  791. SELECT COALESCE(SUM(
  792. CASE -- sum score for each of the users ownables
  793. WHEN ownership.ownable_id = ? THEN ownership.amount
  794. ELSE ownership.amount * (SELECT price
  795. FROM transactions
  796. WHERE ownable_id = ownership.ownable_id
  797. ORDER BY rowid DESC -- equivalent to ordering by dt
  798. LIMIT 1)
  799. END
  800. ), 0)
  801. FROM ownership
  802. WHERE ownership.user_id = ?)
  803. -
  804. ( SELECT COALESCE(SUM(
  805. amount
  806. ), 0)
  807. FROM loans
  808. WHERE loans.user_id = ?)
  809. -
  810. ( SELECT COALESCE(SUM(
  811. amount
  812. ), 0)
  813. FROM credits
  814. JOIN ownership o on credits.ownable_id = o.ownable_id
  815. WHERE credits.issuer_id = ?
  816. AND o.user_id != ?
  817. )
  818. '''
  819. execute(f'''
  820. SELECT ({score_expression}) AS score
  821. ''', (currency_id(), user_id, user_id, user_id, user_id,))
  822. return current_cursor.fetchone()[0]
  823. def change_password(session_id, password, salt):
  824. execute('''
  825. UPDATE users
  826. SET password = ?, salt= ?
  827. WHERE rowid = (SELECT user_id FROM sessions WHERE sessions.session_id = ?)
  828. ''', (password, salt, session_id,))
  829. def sign_out_user(session_id):
  830. execute('''
  831. DELETE FROM sessions
  832. WHERE user_id = (SELECT user_id FROM sessions s2 WHERE s2.session_id = ?)
  833. ''', (session_id,))
  834. def delete_user(user_id):
  835. execute('''
  836. DELETE FROM sessions
  837. WHERE user_id = ?
  838. ''', (user_id,))
  839. execute('''
  840. DELETE FROM orders
  841. WHERE ownership_id IN (
  842. SELECT rowid FROM ownership WHERE user_id = ?)
  843. ''', (user_id,))
  844. execute('''
  845. DELETE FROM ownership
  846. WHERE user_id = ?
  847. ''', (user_id,))
  848. execute('''
  849. DELETE FROM keys
  850. WHERE used_by_user_id = ?
  851. ''', (user_id,))
  852. execute('''
  853. INSERT INTO news(title)
  854. VALUES ((SELECT username FROM users WHERE rowid = ?) || ' retired.')
  855. ''', (user_id,))
  856. execute('''
  857. DELETE FROM users
  858. WHERE rowid = ?
  859. ''', (user_id,))
  860. def delete_ownable(ownable_id):
  861. execute('''
  862. DELETE FROM transactions
  863. WHERE ownable_id = ?
  864. ''', (ownable_id,))
  865. execute('''
  866. DELETE FROM orders
  867. WHERE ownership_id IN (
  868. SELECT rowid FROM ownership WHERE ownable_id = ?)
  869. ''', (ownable_id,))
  870. execute('''
  871. DELETE FROM order_history
  872. WHERE ownership_id IN (
  873. SELECT rowid FROM ownership WHERE ownable_id = ?)
  874. ''', (ownable_id,))
  875. # only delete empty ownerships
  876. execute('''
  877. DELETE FROM ownership
  878. WHERE ownable_id = ?
  879. AND amount = 0
  880. ''', (ownable_id,))
  881. execute('''
  882. INSERT INTO news(title)
  883. VALUES ((SELECT name FROM ownables WHERE rowid = ?) || ' can not be traded any more.')
  884. ''', (ownable_id,))
  885. execute('''
  886. DELETE FROM ownables
  887. WHERE rowid = ?
  888. ''', (ownable_id,))
  889. def hash_all_users_passwords():
  890. execute('''
  891. SELECT rowid, password, salt
  892. FROM users
  893. ''')
  894. users = current_cursor.fetchall()
  895. for user_id, pw, salt in users:
  896. valid_hash = True
  897. try:
  898. sha256_crypt.verify('password' + salt, pw)
  899. except ValueError:
  900. valid_hash = False
  901. if valid_hash:
  902. raise AssertionError('There is already a hashed password in the database! Be careful what you are doing!')
  903. pw = sha256_crypt.encrypt(pw + salt)
  904. execute('''
  905. UPDATE users
  906. SET password = ?
  907. WHERE rowid = ?
  908. ''', (pw, user_id,))
  909. def new_news(message):
  910. execute('''
  911. INSERT INTO news(title)
  912. VALUES (?)
  913. ''', (message,))
  914. def abs_spread(ownable_id):
  915. execute('''
  916. SELECT
  917. (SELECT MAX("limit")
  918. FROM orders, ownership
  919. WHERE ownership.rowid = orders.ownership_id
  920. AND ownership.ownable_id = ?
  921. AND buy
  922. AND NOT stop_loss) AS bid,
  923. (SELECT MIN("limit")
  924. FROM orders, ownership
  925. WHERE ownership.rowid = orders.ownership_id
  926. AND ownership.ownable_id = ?
  927. AND NOT buy
  928. AND NOT stop_loss) AS ask
  929. ''', (ownable_id, ownable_id,))
  930. return current_cursor.fetchone()
  931. def ownables():
  932. execute('''
  933. SELECT name, course,
  934. (SELECT SUM(amount)
  935. FROM ownership
  936. WHERE ownership.ownable_id = ownables_with_course.rowid) market_size
  937. FROM (SELECT
  938. name, ownables.rowid,
  939. CASE WHEN ownables.rowid = ?
  940. THEN 1
  941. ELSE (SELECT price
  942. FROM transactions
  943. WHERE ownable_id = ownables.rowid
  944. ORDER BY rowid DESC -- equivalent to ordering by dt
  945. LIMIT 1) END course
  946. FROM ownables) ownables_with_course
  947. ''', (currency_id(),))
  948. data = current_cursor.fetchall()
  949. for idx in range(len(data)):
  950. # compute market cap
  951. row = data[idx]
  952. if row[1] is None:
  953. market_cap = None
  954. elif row[2] is None:
  955. market_cap = None
  956. else:
  957. market_cap = row[1] * row[2]
  958. data[idx] = (row[0], row[1], market_cap)
  959. return data
  960. def reset_bank():
  961. execute('''
  962. DELETE FROM ownership
  963. WHERE user_id = ?
  964. ''', (bank_id(),))
  965. def cleanup():
  966. global connections
  967. global current_connection
  968. global current_cursor
  969. global current_db_name
  970. global current_user_id
  971. for name in connections:
  972. connections[name].rollback()
  973. connections[name].close()
  974. connections = []
  975. current_connection = None
  976. current_cursor = None
  977. current_db_name = None
  978. current_user_id = None
  979. def ownable_ids():
  980. execute('''
  981. SELECT rowid FROM ownables
  982. ''')
  983. return [ownable_id[0] for ownable_id in current_cursor.fetchall()]
  984. def get_old_orders(user_id, include_executed, include_canceled, limit):
  985. execute('''
  986. SELECT
  987. (CASE WHEN order_history.buy THEN 'Buy' ELSE 'Sell' END),
  988. ownables.name,
  989. (order_history.ordered_amount - order_history.executed_amount) || '/' || order_history.ordered_amount,
  990. order_history."limit",
  991. order_history.expiry_dt,
  992. order_history.order_id,
  993. order_history.status
  994. FROM order_history, ownership, ownables
  995. WHERE ownership.user_id = ?
  996. AND ownership.rowid = order_history.ownership_id
  997. AND ownables.rowid = ownership.ownable_id
  998. AND (
  999. (order_history.status = 'Executed' AND ?)
  1000. OR
  1001. ((order_history.status = 'Expired' OR order_history.status = 'Canceled') AND ?)
  1002. )
  1003. ORDER BY order_history.rowid DESC -- equivalent to ordering by creation time
  1004. LIMIT ?
  1005. ''', (user_id, include_executed, include_canceled, limit))
  1006. return current_cursor.fetchall()
  1007. def user_has_banking_license(user_id):
  1008. execute('''
  1009. SELECT EXISTS (SELECT * FROM banks WHERE user_id = ?)
  1010. ''', (user_id,))
  1011. return current_cursor.fetchone()[0]
  1012. def global_control_value(value_name):
  1013. execute('''
  1014. SELECT value
  1015. FROM global_control_values
  1016. WHERE value_name = ?
  1017. AND dt = (SELECT MAX(dt) FROM global_control_values WHERE value_name = ?)
  1018. ''', (value_name, value_name,))
  1019. return current_cursor.fetchone()[0]
  1020. def global_control_values():
  1021. execute('''
  1022. SELECT value_name, value
  1023. FROM global_control_values v1
  1024. WHERE dt IN (SELECT MAX(dt) FROM global_control_values v2 GROUP BY v2.value_name)
  1025. ''')
  1026. return {
  1027. row[0]: row[1] for row in current_cursor.fetchall()
  1028. }
  1029. def assign_banking_licence(user_id):
  1030. execute('''
  1031. INSERT INTO banks(user_id)
  1032. VALUES (?)
  1033. ''', (user_id,))
  1034. def pay_bond_interest(until=None):
  1035. if until is None:
  1036. current_dt = current_db_timestamp()
  1037. else:
  1038. current_dt = until
  1039. sec_per_year = 3600 * 24 * 365
  1040. interests = execute('''
  1041. SELECT
  1042. SUM(amount * coupon * (MIN(CAST(? AS FLOAT), maturity_dt) - last_interest_pay_dt) / ?) AS interest_since_last_pay,
  1043. o.user_id AS to_user_id,
  1044. credits.issuer_id AS from_user_id
  1045. FROM credits
  1046. JOIN ownership o on credits.ownable_id = o.ownable_id
  1047. WHERE ? - last_interest_pay_dt > ? OR ? > maturity_dt -- every interval or when the bond expired
  1048. AND amount != 0
  1049. GROUP BY o.user_id, credits.issuer_id
  1050. ''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL, current_dt)).fetchall()
  1051. matured_credits = execute('''
  1052. SELECT
  1053. amount,
  1054. o.user_id AS to_user_id,
  1055. credits.issuer_id AS from_user_id
  1056. FROM credits
  1057. JOIN ownership o on credits.ownable_id = o.ownable_id
  1058. WHERE ? > maturity_dt
  1059. ''', (current_dt,)).fetchall()
  1060. # transfer the interest money
  1061. for amount, to_user_id, from_user_id in interests:
  1062. send_ownable(from_user_id, to_user_id, currency_id(), amount)
  1063. # pay back matured credits
  1064. for amount, to_user_id, from_user_id in matured_credits:
  1065. send_ownable(from_user_id, to_user_id, currency_id(), amount)
  1066. execute('''
  1067. UPDATE credits
  1068. SET last_interest_pay_dt = ?
  1069. WHERE ? - last_interest_pay_dt > ?''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1070. # delete matured credits
  1071. execute('''
  1072. DELETE FROM transactions
  1073. WHERE ownable_id IN (
  1074. SELECT ownable_id
  1075. FROM credits
  1076. WHERE ? > maturity_dt
  1077. )
  1078. ''', (current_dt,))
  1079. execute('''
  1080. DELETE FROM orders
  1081. WHERE ownership_id IN (
  1082. SELECT o2.rowid
  1083. FROM credits
  1084. JOIN ownables o on credits.ownable_id = o.rowid
  1085. JOIN ownership o2 on o.rowid = o2.ownable_id
  1086. WHERE ? > maturity_dt
  1087. )
  1088. ''', (current_dt,))
  1089. execute('''
  1090. DELETE FROM order_history
  1091. WHERE ownership_id IN (
  1092. SELECT o2.rowid
  1093. FROM credits
  1094. JOIN ownables o on credits.ownable_id = o.rowid
  1095. JOIN ownership o2 on o.rowid = o2.ownable_id
  1096. WHERE ? > maturity_dt
  1097. )
  1098. ''', (current_dt,))
  1099. execute('''
  1100. DELETE FROM ownership
  1101. WHERE ownable_id IN (
  1102. SELECT ownable_id
  1103. FROM credits
  1104. WHERE ? > maturity_dt
  1105. )
  1106. ''', (current_dt,))
  1107. execute('''
  1108. DELETE FROM credits
  1109. WHERE ? > maturity_dt
  1110. ''', (current_dt,))
  1111. execute('''
  1112. DELETE FROM ownables
  1113. WHERE rowid IN (
  1114. SELECT ownable_id
  1115. FROM credits
  1116. WHERE ? > maturity_dt
  1117. )
  1118. ''', (current_dt,))
  1119. def pay_loan_interest(until=None):
  1120. if until is None:
  1121. current_dt = current_db_timestamp()
  1122. else:
  1123. current_dt = until
  1124. sec_per_year = 3600 * 24 * 365
  1125. interests = execute('''
  1126. SELECT
  1127. SUM(amount * interest_rate * (CAST(? AS FLOAT) - last_interest_pay_dt) / ?) AS interest_since_last_pay,
  1128. user_id
  1129. FROM loans
  1130. WHERE ? - last_interest_pay_dt > ?
  1131. GROUP BY user_id
  1132. ''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL)).fetchall()
  1133. executemany(f'''
  1134. UPDATE ownership
  1135. SET amount = amount - ?
  1136. WHERE ownable_id = {currency_id()}
  1137. AND user_id = ?
  1138. ''', interests)
  1139. # noinspection SqlWithoutWhere
  1140. execute('''
  1141. UPDATE loans
  1142. SET last_interest_pay_dt = ?
  1143. WHERE ? - last_interest_pay_dt > ?
  1144. ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1145. def pay_deposit_facility(until=None):
  1146. if until is None:
  1147. current_dt = current_db_timestamp()
  1148. else:
  1149. current_dt = until
  1150. sec_per_year = 3600 * 24 * 365
  1151. interest_rate = global_control_value('deposit_facility')
  1152. banks = execute('''
  1153. SELECT
  1154. banks.user_id,
  1155. o.amount * ? * (CAST(? AS FLOAT) - last_deposit_facility_pay_dt) / ?
  1156. FROM banks
  1157. JOIN ownership o on banks.user_id = o.user_id
  1158. WHERE o.rowid = ?
  1159. AND ? - last_deposit_facility_pay_dt > ?''', (interest_rate, current_dt, sec_per_year, currency_id(), current_dt, MIN_INTEREST_INTERVAL)).fetchall()
  1160. for user_id, interest_amount in banks:
  1161. send_ownable(user_id, bank_id(), currency_id(), interest_amount)
  1162. execute('''
  1163. UPDATE banks
  1164. SET last_deposit_facility_pay_dt = ?
  1165. WHERE ? - last_deposit_facility_pay_dt > ?
  1166. ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1167. def triggered_mros():
  1168. return execute('''
  1169. SELECT
  1170. rowid AS mro_id,
  1171. maturity_dt AS expiry,
  1172. mro_interest AS min_interest,
  1173. dt AS mro_dt
  1174. FROM tender_calendar
  1175. WHERE NOT executed
  1176. AND dt < ?
  1177. ''', (current_db_timestamp(),)).fetchall()
  1178. def mro(mro_id, expiry, min_interest):
  1179. qualified_credits = execute('''
  1180. SELECT credits.ownable_id, SUM(amount)
  1181. FROM credits
  1182. JOIN banks b ON credits.issuer_id = b.user_id
  1183. JOIN ownership o ON o.ownable_id = credits.ownable_id -- AND credits.issuer_id = o.user_id
  1184. JOIN orders o2 ON o.rowid = o2.ownership_id AND NOT o2.buy
  1185. WHERE maturity_dt = ?
  1186. AND coupon >= ?
  1187. AND "limit" IS NULL or "limit" <= 1
  1188. GROUP BY credits.ownable_id
  1189. ''', (expiry, min_interest)).fetchall()
  1190. for ownable_id, amount in qualified_credits:
  1191. if amount == 0:
  1192. continue
  1193. assert amount > 0
  1194. bank_order(buy=True,
  1195. ownable_id=ownable_id,
  1196. limit=1,
  1197. amount=amount,
  1198. expiry=expiry,
  1199. ioc=True)
  1200. execute('''
  1201. UPDATE tender_calendar
  1202. SET executed = TRUE
  1203. WHERE rowid = ?''', (mro_id,)) # TODO set mro to executed
  1204. def loan_recipient_id(loan_id):
  1205. execute('''
  1206. SELECT user_id
  1207. FROM loans
  1208. WHERE rowid = ?
  1209. ''', (loan_id,))
  1210. return current_cursor.fetchone()[0]
  1211. def loan_remaining_amount(loan_id):
  1212. execute('''
  1213. SELECT amount
  1214. FROM loans
  1215. WHERE rowid = ?
  1216. ''', (loan_id,))
  1217. return current_cursor.fetchone()[0]
  1218. def repay_loan(loan_id, amount, known_user_id=None):
  1219. if known_user_id is None:
  1220. user_id = loan_recipient_id(loan_id)
  1221. else:
  1222. user_id = known_user_id
  1223. send_ownable(user_id, bank_id(), currency_id(), amount)
  1224. execute('''
  1225. UPDATE loans
  1226. SET amount = amount - ?
  1227. WHERE rowid = ?
  1228. ''', (amount, loan_id,))
  1229. if loan_remaining_amount(loan_id) == 0:
  1230. execute('''
  1231. DELETE FROM loans
  1232. WHERE rowid = ?
  1233. ''', (loan_id,))
  1234. def take_out_personal_loan(user_id, amount):
  1235. execute('''
  1236. INSERT INTO loans(user_id, total_amount, amount, interest_rate)
  1237. VALUES (?, ?, ?, ?)
  1238. ''', (user_id, amount, amount, global_control_value('personal_loan_interest_rate')))
  1239. send_ownable(bank_id(), user_id, currency_id(), amount)
  1240. def loan_id_exists(loan_id):
  1241. execute('''
  1242. SELECT EXISTS (SELECT * FROM loans WHERE rowid = ?)
  1243. ''', (loan_id,))
  1244. return current_cursor.fetchone()[0]
  1245. def time_travel(delta_t):
  1246. """
  1247. Modify all timestamps in the database by -delta_t.
  1248. A positive delta_t travels into the future, a negative delta_t to the past.
  1249. Be careful with time travel into the past though.
  1250. :param delta_t: time in seconds to travel
  1251. """
  1252. tables = execute('''
  1253. SELECT name
  1254. FROM sqlite_master
  1255. WHERE type = 'table'
  1256. ''').fetchall()
  1257. for (table,) in tables:
  1258. columns = execute(f'''
  1259. SELECT * FROM {table}
  1260. LIMIT 1
  1261. ''').description
  1262. timestamp_columns = []
  1263. for column in columns:
  1264. name = column[0]
  1265. if re.search(r'(?:^|_)dt(?:$|_)', name):
  1266. timestamp_columns.append(name)
  1267. if len(timestamp_columns) != 0:
  1268. updates = ',\n'.join(f'"{column}" = "{column}" + ?' for column in timestamp_columns)
  1269. execute(f'''
  1270. UPDATE {table}
  1271. SET {updates}
  1272. ''', tuple(delta_t for _ in timestamp_columns))
  1273. def user_has_loan_with_id(user_id, loan_id):
  1274. execute('''
  1275. SELECT EXISTS (SELECT * FROM loans WHERE rowid = ? AND user_id = ?)
  1276. ''', (loan_id, user_id))
  1277. return current_cursor.fetchone()[0]
  1278. def tender_calendar():
  1279. return execute('''
  1280. SELECT dt, mro_interest, maturity_dt
  1281. FROM tender_calendar
  1282. ORDER BY dt DESC
  1283. LIMIT 20
  1284. ''', ).fetchall()
  1285. def required_minimum_reserve(user_id):
  1286. assert user_has_banking_license(user_id)
  1287. borrowed_money = execute('''
  1288. SELECT SUM(amount)
  1289. FROM ownership
  1290. JOIN credits b on ownership.ownable_id = b.ownable_id
  1291. WHERE b.issuer_id = ?
  1292. AND ownership.user_id = ?
  1293. ''', (user_id, bank_id())).fetchone()[0]
  1294. return max(0, global_control_value('cash_reserve_ratio') * borrowed_money - global_control_value('cash_reserve_free_amount'))
  1295. def issue_bond(user_id, ownable_name, coupon, maturity_dt):
  1296. execute('''
  1297. INSERT INTO ownables(name)
  1298. VALUES (?)
  1299. ''', (ownable_name,))
  1300. execute('''
  1301. INSERT INTO credits(issuer_id, ownable_id, coupon, maturity_dt)
  1302. VALUES (?, (SELECT MAX(rowid) FROM ownables), ?, ?)
  1303. ''', (user_id, coupon, maturity_dt))
  1304. def update_tender_calendar():
  1305. last_mro_dt = execute('''
  1306. SELECT COALESCE((SELECT dt
  1307. FROM tender_calendar
  1308. ORDER BY dt DESC
  1309. LIMIT 1), ?)
  1310. ''', (current_db_timestamp(),)).fetchone()[0]
  1311. one_day = 24 * 3600
  1312. while last_mro_dt < current_db_timestamp() + one_day:
  1313. last_mro_dt += MRO_INTERVAL
  1314. maturity_dt = last_mro_dt + MRO_RUNNING_TIME
  1315. execute('''
  1316. INSERT INTO tender_calendar(dt, mro_interest, maturity_dt)
  1317. VALUES (?, ?, ?)
  1318. ''', (last_mro_dt, global_control_value('main_refinancing_operations'), maturity_dt))