model.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507
  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
  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 to_user_id != bank_id_ and 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. execute('''
  206. SELECT
  207. ownables.name,
  208. ownership.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.ownable_id = ?)
  233. AND ownership.ownable_id = ownables.rowid
  234. ORDER BY ownables.rowid ASC
  235. ''', (currency_id(), user_id, currency_id(),))
  236. return current_cursor.fetchall()
  237. def bank_id():
  238. execute('''
  239. SELECT users.rowid
  240. FROM users
  241. WHERE username = ?
  242. ''', (BANK_NAME,))
  243. return current_cursor.fetchone()[0]
  244. def valid_session_id(session_id):
  245. execute('''
  246. SELECT rowid
  247. FROM sessions
  248. WHERE session_id = ?
  249. ''', (session_id,))
  250. if current_cursor.fetchone():
  251. return True
  252. else:
  253. return False
  254. def get_user_orders(user_id):
  255. execute('''
  256. SELECT
  257. CASE
  258. WHEN orders.buy THEN 'Buy'
  259. ELSE 'Sell'
  260. END,
  261. ownables.name,
  262. (orders.ordered_amount - orders.executed_amount) || '/' || orders.ordered_amount,
  263. orders."limit",
  264. CASE
  265. WHEN orders."limit" IS NULL THEN NULL
  266. WHEN orders.stop_loss THEN 'Yes'
  267. ELSE 'No'
  268. END,
  269. datetime(orders.expiry_dt, 'localtime'),
  270. orders.rowid
  271. FROM orders, ownables, ownership
  272. WHERE ownership.user_id = ?
  273. AND ownership.ownable_id = ownables.rowid
  274. AND orders.ownership_id = ownership.rowid
  275. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  276. ''', (user_id,))
  277. return current_cursor.fetchall()
  278. def get_user_loans(user_id):
  279. execute('''
  280. SELECT
  281. rowid,
  282. total_amount,
  283. amount,
  284. interest_rate
  285. FROM loans
  286. WHERE user_id is ?
  287. ORDER BY rowid ASC
  288. ''', (user_id,))
  289. return current_cursor.fetchall()
  290. def next_mro_dt(dt=None):
  291. if dt is None:
  292. dt = current_db_timestamp()
  293. return execute('''
  294. SELECT MIN(t.maturity_dt) FROM tender_calendar t WHERE t.maturity_dt > ?
  295. ''', (dt,)).fetchone()[0]
  296. def next_mro_interest(dt=None):
  297. return execute('''
  298. SELECT t.mro_interest FROM tender_calendar t WHERE t.maturity_dt = ?
  299. ''', (next_mro_dt(dt),)).fetchone()[0]
  300. def bonds(issuer_id=None, only_next_mro_qualified=False):
  301. if issuer_id is not None:
  302. issuer_condition = 'issuer.rowid = ?'
  303. issuer_params = (issuer_id,)
  304. else:
  305. issuer_condition = '1'
  306. issuer_params = ()
  307. if only_next_mro_qualified:
  308. only_next_mro_condition = ''' -- noinspection SqlResolve @ any/"bonds"
  309. SELECT EXISTS(
  310. SELECT *
  311. FROM banks b
  312. JOIN tender_calendar t ON t.maturity_dt = bonds.maturity_dt
  313. WHERE bonds.issuer_id = b.user_id
  314. AND bonds.coupon >= t.mro_interest
  315. AND t.maturity_dt = ?
  316. )
  317. '''
  318. only_next_mro_params = (next_mro_dt(),)
  319. else:
  320. only_next_mro_condition = '1'
  321. only_next_mro_params = ()
  322. execute(f'''
  323. SELECT
  324. name,
  325. coupon,
  326. datetime(maturity_dt, 'unixepoch', 'localtime'),
  327. username
  328. FROM bonds
  329. JOIN ownables o on bonds.ownable_id = o.rowid
  330. JOIN users issuer on bonds.issuer_id = issuer.rowid
  331. WHERE ({issuer_condition})
  332. AND ({only_next_mro_condition})
  333. ORDER BY coupon * (maturity_dt - ?) DESC
  334. ''', (*issuer_params, *only_next_mro_params, current_db_timestamp(),))
  335. return current_cursor.fetchall()
  336. def get_ownable_orders(user_id, ownable_id):
  337. execute('''
  338. SELECT
  339. CASE
  340. WHEN ownership.user_id = ? THEN 'X'
  341. ELSE NULL
  342. END,
  343. CASE
  344. WHEN orders.buy THEN 'Buy'
  345. ELSE 'Sell'
  346. END,
  347. ownables.name,
  348. orders.ordered_amount - orders.executed_amount,
  349. orders."limit",
  350. datetime(orders.expiry_dt, 'localtime'),
  351. orders.rowid
  352. FROM orders, ownables, ownership
  353. WHERE ownership.ownable_id = ?
  354. AND ownership.ownable_id = ownables.rowid
  355. AND orders.ownership_id = ownership.rowid
  356. AND (orders.stop_loss IS NULL OR NOT orders.stop_loss)
  357. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  358. ''', (user_id, ownable_id,))
  359. return current_cursor.fetchall()
  360. def sell_ordered_amount(user_id, ownable_id):
  361. execute('''
  362. SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  363. FROM orders, ownership
  364. WHERE ownership.rowid = orders.ownership_id
  365. AND ownership.user_id = ?
  366. AND ownership.ownable_id = ?
  367. AND NOT orders.buy
  368. ''', (user_id, ownable_id))
  369. return current_cursor.fetchone()[0]
  370. def available_amount(user_id, ownable_id):
  371. execute('''
  372. SELECT amount
  373. FROM ownership
  374. WHERE user_id = ?
  375. AND ownable_id = ?
  376. ''', (user_id, ownable_id))
  377. return current_cursor.fetchone()[0] - sell_ordered_amount(user_id, ownable_id)
  378. def is_bond_of_user(ownable_id, user_id):
  379. execute('''
  380. SELECT EXISTS(
  381. SELECT * FROM bonds
  382. WHERE ownable_id = ?
  383. AND issuer_id = ?
  384. )
  385. ''', (ownable_id, user_id,))
  386. return current_cursor.fetchone()[0]
  387. def user_has_at_least_available(amount, user_id, ownable_id):
  388. if is_bond_of_user(ownable_id, user_id):
  389. return True
  390. if not isinstance(amount, float) and not isinstance(amount, int):
  391. # comparison of float with strings does not work so well in sql
  392. raise AssertionError()
  393. execute('''
  394. SELECT rowid
  395. FROM ownership
  396. WHERE user_id = ?
  397. AND ownable_id = ?
  398. AND amount - ? >= ?
  399. ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
  400. if current_cursor.fetchone():
  401. return True
  402. else:
  403. return False
  404. def news():
  405. execute('''
  406. SELECT dt, title FROM
  407. (SELECT *, rowid
  408. FROM news
  409. ORDER BY news.rowid DESC -- equivalent to order by dt
  410. LIMIT 20) n
  411. ORDER BY rowid ASC -- equivalent to order by dt
  412. ''')
  413. return current_cursor.fetchall()
  414. def ownable_name_exists(name):
  415. execute('''
  416. SELECT rowid
  417. FROM ownables
  418. WHERE name = ?
  419. ''', (name,))
  420. if current_cursor.fetchone():
  421. return True
  422. else:
  423. return False
  424. def new_stock(expiry, name=None):
  425. name = new_random_ownable_name(name)
  426. execute('''
  427. INSERT INTO ownables(name)
  428. VALUES (?)
  429. ''', (name,))
  430. new_news('A new stock can now be bought: ' + name)
  431. if random.getrandbits(1):
  432. new_news('Experts expect the price of ' + name + ' to fall')
  433. else:
  434. new_news('Experts expect the price of ' + name + ' to rise')
  435. amount = random.randrange(100, 10000)
  436. price = random.randrange(10000, 20000) / amount
  437. ownable_id = ownable_id_by_name(name)
  438. own(bank_id(), name, amount)
  439. bank_order(False,
  440. ownable_id,
  441. price,
  442. amount,
  443. expiry,
  444. ioc=False)
  445. return name
  446. def new_random_ownable_name(name):
  447. while name is None:
  448. name = random_ownable_name()
  449. if ownable_name_exists(name):
  450. name = None
  451. return name
  452. def ownable_id_by_name(ownable_name):
  453. execute('''
  454. SELECT rowid
  455. FROM ownables
  456. WHERE name = ?
  457. ''', (ownable_name,))
  458. return current_cursor.fetchone()[0]
  459. def get_ownership_id(ownable_id, user_id):
  460. execute('''
  461. SELECT rowid
  462. FROM ownership
  463. WHERE ownable_id = ?
  464. AND user_id = ?
  465. ''', (ownable_id, user_id,))
  466. return current_cursor.fetchone()[0]
  467. def currency_id():
  468. execute('''
  469. SELECT rowid
  470. FROM ownables
  471. WHERE name = ?
  472. ''', (CURRENCY_NAME,))
  473. return current_cursor.fetchone()[0]
  474. def user_money(user_id):
  475. execute('''
  476. SELECT amount
  477. FROM ownership
  478. WHERE user_id = ?
  479. AND ownable_id = ?
  480. ''', (user_id, currency_id()))
  481. return current_cursor.fetchone()[0]
  482. def delete_order(order_id, new_order_status):
  483. execute('''
  484. INSERT INTO order_history
  485. (ownership_id, buy, "limit", ordered_amount, executed_amount, expiry_dt, status, order_id)
  486. SELECT
  487. ownership_id,
  488. buy,
  489. "limit",
  490. ordered_amount,
  491. executed_amount,
  492. expiry_dt,
  493. ?,
  494. rowid
  495. FROM orders
  496. WHERE rowid = ?
  497. ''', (new_order_status, order_id,))
  498. execute('''
  499. DELETE FROM orders
  500. WHERE rowid = ?
  501. ''', (order_id,))
  502. def current_value(ownable_id):
  503. if ownable_id == currency_id():
  504. return 1
  505. execute('''SELECT price
  506. FROM transactions
  507. WHERE ownable_id = ?
  508. ORDER BY rowid DESC -- equivalent to order by dt
  509. LIMIT 1
  510. ''', (ownable_id,))
  511. return current_cursor.fetchone()[0]
  512. def execute_orders(ownable_id):
  513. orders_traded = False
  514. while True:
  515. # find order to execute
  516. execute('''
  517. -- two best orders
  518. SELECT * FROM (
  519. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  520. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  521. WHERE buy_order.buy AND NOT sell_order.buy
  522. AND buyer.rowid = buy_order.ownership_id
  523. AND seller.rowid = sell_order.ownership_id
  524. AND buyer.ownable_id = ?
  525. AND seller.ownable_id = ?
  526. AND buy_order."limit" IS NULL
  527. AND sell_order."limit" IS NULL
  528. ORDER BY buy_order.rowid ASC,
  529. sell_order.rowid ASC
  530. LIMIT 1)
  531. UNION ALL -- best buy orders
  532. SELECT * FROM (
  533. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  534. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  535. WHERE buy_order.buy AND NOT sell_order.buy
  536. AND buyer.rowid = buy_order.ownership_id
  537. AND seller.rowid = sell_order.ownership_id
  538. AND buyer.ownable_id = ?
  539. AND seller.ownable_id = ?
  540. AND buy_order."limit" IS NULL
  541. AND sell_order."limit" IS NOT NULL
  542. AND NOT sell_order.stop_loss
  543. ORDER BY sell_order."limit" ASC,
  544. buy_order.rowid ASC,
  545. sell_order.rowid ASC
  546. LIMIT 1)
  547. UNION ALL -- best sell orders
  548. SELECT * FROM (
  549. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  550. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  551. WHERE buy_order.buy AND NOT sell_order.buy
  552. AND buyer.rowid = buy_order.ownership_id
  553. AND seller.rowid = sell_order.ownership_id
  554. AND buyer.ownable_id = ?
  555. AND seller.ownable_id = ?
  556. AND buy_order."limit" IS NOT NULL
  557. AND NOT buy_order.stop_loss
  558. AND sell_order."limit" IS NULL
  559. ORDER BY buy_order."limit" DESC,
  560. buy_order.rowid ASC,
  561. sell_order.rowid ASC
  562. LIMIT 1)
  563. UNION ALL -- both limit orders
  564. SELECT * FROM (
  565. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  566. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  567. WHERE buy_order.buy AND NOT sell_order.buy
  568. AND buyer.rowid = buy_order.ownership_id
  569. AND seller.rowid = sell_order.ownership_id
  570. AND buyer.ownable_id = ?
  571. AND seller.ownable_id = ?
  572. AND buy_order."limit" IS NOT NULL
  573. AND sell_order."limit" IS NOT NULL
  574. AND sell_order."limit" <= buy_order."limit"
  575. AND NOT sell_order.stop_loss
  576. AND NOT buy_order.stop_loss
  577. ORDER BY buy_order."limit" DESC,
  578. sell_order."limit" ASC,
  579. buy_order.rowid ASC,
  580. sell_order.rowid ASC
  581. LIMIT 1)
  582. LIMIT 1
  583. ''', tuple(ownable_id for _ in range(8)))
  584. matching_orders = current_cursor.fetchone()
  585. # return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  586. # ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  587. # user_id,user_id,rowid,rowid)
  588. if not matching_orders:
  589. break
  590. _, buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, _, \
  591. _, sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, _, \
  592. buyer_id, seller_id, buy_order_id, sell_order_id \
  593. = matching_orders
  594. if buy_limit is None and sell_limit is None:
  595. price = current_value(ownable_id)
  596. elif buy_limit is None:
  597. price = sell_limit
  598. elif sell_limit is None:
  599. price = buy_limit
  600. else: # both not NULL
  601. # the price of the older order is used, just like in the real exchange
  602. if buy_order_id < sell_order_id:
  603. price = buy_limit
  604. else:
  605. price = sell_limit
  606. buyer_money = user_money(buyer_id)
  607. def _my_division(x, y):
  608. try:
  609. return floor(x / y)
  610. except ZeroDivisionError:
  611. return float('Inf')
  612. amount = min(buy_order_amount - buy_executed_amount,
  613. sell_order_amount - sell_executed_amount,
  614. _my_division(buyer_money, price))
  615. if amount < 0:
  616. amount = 0
  617. if amount == 0: # probable because buyer has not enough money
  618. delete_order(buy_order_id, 'Unable to pay')
  619. continue
  620. buy_order_finished = (buy_order_amount - buy_executed_amount - amount <= 0) or (
  621. buyer_money - amount * price < price)
  622. sell_order_finished = (sell_order_amount - sell_executed_amount - amount <= 0)
  623. if price < 0 or amount <= 0: # price of 0 is possible though unlikely
  624. return AssertionError()
  625. # actually execute the order, but the bank does not send or receive anything
  626. send_ownable(buyer_id, seller_id, currency_id(), price * amount)
  627. send_ownable(seller_id, buyer_id, ownable_id, amount)
  628. # update order execution state
  629. execute('''
  630. UPDATE orders
  631. SET executed_amount = executed_amount + ?
  632. WHERE rowid = ?
  633. OR rowid = ?
  634. ''', (amount, buy_order_id, sell_order_id))
  635. if buy_order_finished:
  636. delete_order(buy_order_id, 'Executed')
  637. orders_traded = True
  638. if sell_order_finished:
  639. delete_order(sell_order_id, 'Executed')
  640. orders_traded = True
  641. if seller_id != buyer_id: # prevent showing self-transactions
  642. execute('''
  643. INSERT INTO transactions
  644. (price, ownable_id, amount, buyer_id, seller_id)
  645. VALUES(?, ?, ?, ?, ?)
  646. ''', (price, ownable_id, amount, buyer_id, seller_id))
  647. # trigger stop-loss orders
  648. if buyer_id != seller_id:
  649. execute('''
  650. UPDATE orders
  651. SET stop_loss = NULL,
  652. "limit" = NULL
  653. WHERE stop_loss IS NOT NULL
  654. AND stop_loss
  655. AND ? IN (SELECT ownable_id FROM ownership WHERE rowid = ownership_id)
  656. AND ((buy AND "limit" < ?) OR (NOT buy AND "limit" > ?))
  657. ''', (ownable_id, price, price,))
  658. def ownable_id_by_ownership_id(ownership_id):
  659. execute('''
  660. SELECT ownable_id
  661. FROM ownership
  662. WHERE rowid = ?
  663. ''', (ownership_id,))
  664. return current_cursor.fetchone()[0]
  665. def ownable_name_by_id(ownable_id):
  666. execute('''
  667. SELECT name
  668. FROM ownables
  669. WHERE rowid = ?
  670. ''', (ownable_id,))
  671. return current_cursor.fetchone()[0]
  672. def user_name_by_id(user_id):
  673. execute('''
  674. SELECT username
  675. FROM users
  676. WHERE rowid = ?
  677. ''', (user_id,))
  678. return current_cursor.fetchone()[0]
  679. def bank_order(buy, ownable_id, limit, amount, expiry, ioc):
  680. if not limit:
  681. raise AssertionError('The bank does not give away anything.')
  682. place_order(buy,
  683. get_ownership_id(ownable_id, bank_id()),
  684. limit,
  685. False,
  686. amount,
  687. expiry,
  688. ioc=ioc)
  689. ownable_name = ownable_name_by_id(ownable_id)
  690. new_news('External investors are selling ' + ownable_name + ' atm')
  691. def current_db_time(): # might differ from datetime.datetime.now() for time zone reasons
  692. connect()
  693. execute('''
  694. SELECT datetime('now')
  695. ''')
  696. return current_cursor.fetchone()[0]
  697. def current_db_timestamp():
  698. connect()
  699. execute('''
  700. SELECT CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)
  701. ''')
  702. return int(current_cursor.fetchone()[0])
  703. def place_order(buy, ownership_id, limit, stop_loss, amount, expiry, ioc: bool):
  704. if isinstance(expiry, datetime):
  705. expiry = expiry.timestamp()
  706. execute(''' INSERT INTO orders
  707. (buy, ownership_id, "limit", stop_loss, ordered_amount, expiry_dt, ioc)
  708. VALUES (?, ?, ?, ?, ?, ?, ?)
  709. ''', (buy, ownership_id, limit, stop_loss, amount, expiry, ioc))
  710. execute_orders(ownable_id_by_ownership_id(ownership_id))
  711. execute('''DELETE FROM orders WHERE ioc''')
  712. return True
  713. def trades_on(ownable_id, limit):
  714. execute('''
  715. SELECT datetime(dt,'localtime'), amount, price
  716. FROM transactions
  717. WHERE ownable_id = ?
  718. ORDER BY rowid DESC -- equivalent to order by dt
  719. LIMIT ?
  720. ''', (ownable_id, limit,))
  721. return current_cursor.fetchall()
  722. def trades(user_id, limit):
  723. execute('''
  724. SELECT
  725. (CASE WHEN seller_id = ? THEN 'Sell' ELSE 'Buy' END),
  726. (SELECT name FROM ownables WHERE rowid = transactions.ownable_id),
  727. amount,
  728. price,
  729. datetime(dt,'localtime')
  730. FROM transactions
  731. WHERE seller_id = ? OR buyer_id = ?
  732. ORDER BY rowid DESC -- equivalent to order by dt
  733. LIMIT ?
  734. ''', (user_id, user_id, user_id, limit,))
  735. return current_cursor.fetchall()
  736. def drop_expired_orders():
  737. execute('''
  738. SELECT rowid, ownership_id, * FROM orders
  739. WHERE expiry_dt < CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)
  740. ''')
  741. data = current_cursor.fetchall()
  742. for order in data:
  743. order_id = order[0]
  744. delete_order(order_id, 'Expired')
  745. return data
  746. def user_has_order_with_id(session_id, order_id):
  747. execute('''
  748. SELECT orders.rowid
  749. FROM orders, ownership, sessions
  750. WHERE orders.rowid = ?
  751. AND sessions.session_id = ?
  752. AND sessions.user_id = ownership.user_id
  753. AND ownership.rowid = orders.ownership_id
  754. ''', (order_id, session_id,))
  755. if current_cursor.fetchone():
  756. return True
  757. else:
  758. return False
  759. def leaderboard():
  760. score_expression = '''
  761. -- noinspection SqlResolve @ any/"users"
  762. SELECT (
  763. SELECT COALESCE(SUM(
  764. CASE -- sum score for each of the users ownables
  765. WHEN ownership.ownable_id = ? THEN ownership.amount
  766. ELSE ownership.amount * (SELECT price
  767. FROM transactions
  768. WHERE ownable_id = ownership.ownable_id
  769. ORDER BY rowid DESC -- equivalent to ordering by dt
  770. LIMIT 1)
  771. END
  772. ), 0)
  773. FROM ownership
  774. WHERE ownership.user_id = users.rowid)
  775. -
  776. ( SELECT COALESCE(SUM(
  777. amount
  778. ), 0)
  779. FROM loans
  780. WHERE loans.user_id = users.rowid)
  781. '''
  782. execute(f'''
  783. SELECT *
  784. FROM ( -- one score for each user
  785. SELECT
  786. username,
  787. ({score_expression}) AS score
  788. FROM users
  789. WHERE users.username != ?
  790. ) AS scores
  791. ORDER BY score DESC
  792. LIMIT 50
  793. ''', (currency_id(), BANK_NAME))
  794. return current_cursor.fetchall()
  795. def user_wealth(user_id):
  796. score_expression = '''
  797. SELECT (
  798. SELECT COALESCE(SUM(
  799. CASE -- sum score for each of the users ownables
  800. WHEN ownership.ownable_id = ? THEN ownership.amount
  801. ELSE ownership.amount * (SELECT price
  802. FROM transactions
  803. WHERE ownable_id = ownership.ownable_id
  804. ORDER BY rowid DESC -- equivalent to ordering by dt
  805. LIMIT 1)
  806. END
  807. ), 0)
  808. FROM ownership
  809. WHERE ownership.user_id = ?)
  810. -
  811. ( SELECT COALESCE(SUM(
  812. amount
  813. ), 0)
  814. FROM loans
  815. WHERE loans.user_id = ?)
  816. '''
  817. execute(f'''
  818. SELECT ({score_expression}) AS score
  819. ''', (currency_id(), user_id, user_id,))
  820. return current_cursor.fetchone()[0]
  821. def change_password(session_id, password, salt):
  822. execute('''
  823. UPDATE users
  824. SET password = ?, salt= ?
  825. WHERE rowid = (SELECT user_id FROM sessions WHERE sessions.session_id = ?)
  826. ''', (password, salt, session_id,))
  827. def sign_out_user(session_id):
  828. execute('''
  829. DELETE FROM sessions
  830. WHERE user_id = (SELECT user_id FROM sessions s2 WHERE s2.session_id = ?)
  831. ''', (session_id,))
  832. def delete_user(user_id):
  833. execute('''
  834. DELETE FROM sessions
  835. WHERE user_id = ?
  836. ''', (user_id,))
  837. execute('''
  838. DELETE FROM orders
  839. WHERE ownership_id IN (
  840. SELECT rowid FROM ownership WHERE user_id = ?)
  841. ''', (user_id,))
  842. execute('''
  843. DELETE FROM ownership
  844. WHERE user_id = ?
  845. ''', (user_id,))
  846. execute('''
  847. DELETE FROM keys
  848. WHERE used_by_user_id = ?
  849. ''', (user_id,))
  850. execute('''
  851. INSERT INTO news(title)
  852. VALUES ((SELECT username FROM users WHERE rowid = ?) || ' retired.')
  853. ''', (user_id,))
  854. execute('''
  855. DELETE FROM users
  856. WHERE rowid = ?
  857. ''', (user_id,))
  858. def delete_ownable(ownable_id):
  859. execute('''
  860. DELETE FROM transactions
  861. WHERE ownable_id = ?
  862. ''', (ownable_id,))
  863. execute('''
  864. DELETE FROM orders
  865. WHERE ownership_id IN (
  866. SELECT rowid FROM ownership WHERE ownable_id = ?)
  867. ''', (ownable_id,))
  868. execute('''
  869. DELETE FROM order_history
  870. WHERE ownership_id IN (
  871. SELECT rowid FROM ownership WHERE ownable_id = ?)
  872. ''', (ownable_id,))
  873. # only delete empty ownerships
  874. execute('''
  875. DELETE FROM ownership
  876. WHERE ownable_id = ?
  877. AND amount = 0
  878. ''', (ownable_id,))
  879. execute('''
  880. INSERT INTO news(title)
  881. VALUES ((SELECT name FROM ownables WHERE rowid = ?) || ' can not be traded any more.')
  882. ''', (ownable_id,))
  883. execute('''
  884. DELETE FROM ownables
  885. WHERE rowid = ?
  886. ''', (ownable_id,))
  887. def hash_all_users_passwords():
  888. execute('''
  889. SELECT rowid, password, salt
  890. FROM users
  891. ''')
  892. users = current_cursor.fetchall()
  893. for user_id, pw, salt in users:
  894. valid_hash = True
  895. try:
  896. sha256_crypt.verify('password' + salt, pw)
  897. except ValueError:
  898. valid_hash = False
  899. if valid_hash:
  900. raise AssertionError('There is already a hashed password in the database! Be careful what you are doing!')
  901. pw = sha256_crypt.encrypt(pw + salt)
  902. execute('''
  903. UPDATE users
  904. SET password = ?
  905. WHERE rowid = ?
  906. ''', (pw, user_id,))
  907. def new_news(message):
  908. execute('''
  909. INSERT INTO news(title)
  910. VALUES (?)
  911. ''', (message,))
  912. def abs_spread(ownable_id):
  913. execute('''
  914. SELECT
  915. (SELECT MAX("limit")
  916. FROM orders, ownership
  917. WHERE ownership.rowid = orders.ownership_id
  918. AND ownership.ownable_id = ?
  919. AND buy
  920. AND NOT stop_loss) AS bid,
  921. (SELECT MIN("limit")
  922. FROM orders, ownership
  923. WHERE ownership.rowid = orders.ownership_id
  924. AND ownership.ownable_id = ?
  925. AND NOT buy
  926. AND NOT stop_loss) AS ask
  927. ''', (ownable_id, ownable_id,))
  928. return current_cursor.fetchone()
  929. def ownables():
  930. execute('''
  931. SELECT name, course,
  932. (SELECT SUM(amount)
  933. FROM ownership
  934. WHERE ownership.ownable_id = ownables_with_course.rowid) market_size
  935. FROM (SELECT
  936. name, ownables.rowid,
  937. CASE WHEN ownables.rowid = ?
  938. THEN 1
  939. ELSE (SELECT price
  940. FROM transactions
  941. WHERE ownable_id = ownables.rowid
  942. ORDER BY rowid DESC -- equivalent to ordering by dt
  943. LIMIT 1) END course
  944. FROM ownables) ownables_with_course
  945. ''', (currency_id(),))
  946. data = current_cursor.fetchall()
  947. for idx in range(len(data)):
  948. # compute market cap
  949. row = data[idx]
  950. if row[1] is None:
  951. market_cap = None
  952. elif row[2] is None:
  953. market_cap = None
  954. else:
  955. market_cap = row[1] * row[2]
  956. data[idx] = (row[0], row[1], market_cap)
  957. return data
  958. def reset_bank():
  959. execute('''
  960. DELETE FROM ownership
  961. WHERE user_id = ?
  962. ''', (bank_id(),))
  963. def cleanup():
  964. global connections
  965. global current_connection
  966. global current_cursor
  967. global current_db_name
  968. global current_user_id
  969. for name in connections:
  970. connections[name].rollback()
  971. connections[name].close()
  972. connections = []
  973. current_connection = None
  974. current_cursor = None
  975. current_db_name = None
  976. current_user_id = None
  977. def ownable_ids():
  978. execute('''
  979. SELECT rowid FROM ownables
  980. ''')
  981. return [ownable_id[0] for ownable_id in current_cursor.fetchall()]
  982. def get_old_orders(user_id, include_executed, include_canceled, limit):
  983. execute('''
  984. SELECT
  985. (CASE WHEN order_history.buy THEN 'Buy' ELSE 'Sell' END),
  986. ownables.name,
  987. (order_history.ordered_amount - order_history.executed_amount) || '/' || order_history.ordered_amount,
  988. order_history."limit",
  989. order_history.expiry_dt,
  990. order_history.order_id,
  991. order_history.status
  992. FROM order_history, ownership, ownables
  993. WHERE ownership.user_id = ?
  994. AND ownership.rowid = order_history.ownership_id
  995. AND ownables.rowid = ownership.ownable_id
  996. AND (
  997. (order_history.status = 'Executed' AND ?)
  998. OR
  999. ((order_history.status = 'Expired' OR order_history.status = 'Canceled') AND ?)
  1000. )
  1001. ORDER BY order_history.rowid DESC -- equivalent to ordering by creation time
  1002. LIMIT ?
  1003. ''', (user_id, include_executed, include_canceled, limit))
  1004. return current_cursor.fetchall()
  1005. def user_has_banking_license(user_id):
  1006. execute('''
  1007. SELECT EXISTS (SELECT * FROM banks WHERE user_id = ?)
  1008. ''', (user_id,))
  1009. return current_cursor.fetchone()[0]
  1010. def global_control_value(value_name):
  1011. execute('''
  1012. SELECT value
  1013. FROM global_control_values
  1014. WHERE value_name = ?
  1015. AND dt = (SELECT MAX(dt) FROM global_control_values WHERE value_name = ?)
  1016. ''', (value_name, value_name,))
  1017. return current_cursor.fetchone()[0]
  1018. def global_control_values():
  1019. execute('''
  1020. SELECT value_name, value
  1021. FROM global_control_values v1
  1022. WHERE dt IN (SELECT MAX(dt) FROM global_control_values v2 GROUP BY v2.value_name)
  1023. ''')
  1024. return {
  1025. row[0]: row[1] for row in current_cursor.fetchall()
  1026. }
  1027. def assign_banking_licence(user_id):
  1028. execute('''
  1029. INSERT INTO banks(user_id)
  1030. VALUES (?)
  1031. ''', (user_id,))
  1032. def pay_bond_interest(until=None):
  1033. if until is None:
  1034. current_dt = current_db_timestamp()
  1035. else:
  1036. current_dt = until
  1037. sec_per_year = 3600 * 24 * 365
  1038. interests = execute('''
  1039. SELECT
  1040. SUM(amount * coupon * (MIN(CAST(? AS FLOAT), maturity_dt) - last_interest_pay_dt) / ?) AS interest_since_last_pay,
  1041. o.user_id AS to_user_id,
  1042. bonds.issuer_id AS from_user_id
  1043. FROM bonds
  1044. JOIN ownership o on bonds.ownable_id = o.ownable_id
  1045. WHERE ? - last_interest_pay_dt > ? OR ? > maturity_dt -- every interval or when the bond expired
  1046. AND amount != 0
  1047. GROUP BY o.user_id, bonds.issuer_id
  1048. ''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL, current_dt)).fetchall()
  1049. matured_bonds = execute('''
  1050. SELECT
  1051. amount,
  1052. o.user_id AS to_user_id,
  1053. bonds.issuer_id AS from_user_id
  1054. FROM bonds
  1055. JOIN ownership o on bonds.ownable_id = o.ownable_id
  1056. WHERE ? > maturity_dt
  1057. ''', (current_dt,)).fetchall()
  1058. # transfer the interest money
  1059. for amount, to_user_id, from_user_id in interests:
  1060. send_ownable(from_user_id, to_user_id, currency_id(), amount)
  1061. # pay back matured bonds
  1062. for amount, to_user_id, from_user_id in matured_bonds:
  1063. send_ownable(from_user_id, to_user_id, currency_id(), amount)
  1064. execute('''
  1065. UPDATE bonds
  1066. SET last_interest_pay_dt = ?
  1067. WHERE ? - last_interest_pay_dt > ?''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1068. # delete matured bonds
  1069. execute('''
  1070. DELETE FROM transactions
  1071. WHERE ownable_id IN (
  1072. SELECT ownable_id
  1073. FROM bonds
  1074. WHERE ? > maturity_dt
  1075. )
  1076. ''', (current_dt,))
  1077. execute('''
  1078. DELETE FROM orders
  1079. WHERE ownership_id IN (
  1080. SELECT o2.rowid
  1081. FROM bonds
  1082. JOIN ownables o on bonds.ownable_id = o.rowid
  1083. JOIN ownership o2 on o.rowid = o2.ownable_id
  1084. WHERE ? > maturity_dt
  1085. )
  1086. ''', (current_dt,))
  1087. execute('''
  1088. DELETE FROM order_history
  1089. WHERE ownership_id IN (
  1090. SELECT o2.rowid
  1091. FROM bonds
  1092. JOIN ownables o on bonds.ownable_id = o.rowid
  1093. JOIN ownership o2 on o.rowid = o2.ownable_id
  1094. WHERE ? > maturity_dt
  1095. )
  1096. ''', (current_dt,))
  1097. execute('''
  1098. DELETE FROM ownership
  1099. WHERE ownable_id IN (
  1100. SELECT ownable_id
  1101. FROM bonds
  1102. WHERE ? > maturity_dt
  1103. )
  1104. ''', (current_dt,))
  1105. execute('''
  1106. DELETE FROM ownables
  1107. WHERE rowid IN (
  1108. SELECT ownable_id
  1109. FROM bonds
  1110. WHERE ? > maturity_dt
  1111. )
  1112. ''', (current_dt,))
  1113. execute('''
  1114. DELETE FROM bonds
  1115. WHERE ? > maturity_dt
  1116. ''', (current_dt,))
  1117. def pay_loan_interest(until=None):
  1118. if until is None:
  1119. current_dt = current_db_timestamp()
  1120. else:
  1121. current_dt = until
  1122. sec_per_year = 3600 * 24 * 365
  1123. interests = execute('''
  1124. SELECT
  1125. SUM(amount * interest_rate * (CAST(? AS FLOAT) - last_interest_pay_dt) / ?) AS interest_since_last_pay,
  1126. user_id
  1127. FROM loans
  1128. WHERE ? - last_interest_pay_dt > ?
  1129. GROUP BY user_id
  1130. ''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL)).fetchall()
  1131. executemany(f'''
  1132. UPDATE ownership
  1133. SET amount = amount - ?
  1134. WHERE ownable_id = {currency_id()}
  1135. AND user_id = ?
  1136. ''', interests)
  1137. # noinspection SqlWithoutWhere
  1138. execute('''
  1139. UPDATE loans
  1140. SET last_interest_pay_dt = ?
  1141. WHERE ? - last_interest_pay_dt > ?
  1142. ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1143. def triggered_mros():
  1144. return execute('''
  1145. SELECT
  1146. rowid AS mro_id,
  1147. maturity_dt AS expiry,
  1148. mro_interest AS min_interest,
  1149. dt AS mro_dt
  1150. FROM tender_calendar
  1151. WHERE NOT executed
  1152. AND dt < ?
  1153. ''', (current_db_timestamp(),)).fetchall()
  1154. def mro(mro_id, expiry, min_interest):
  1155. qualified_bonds = execute('''
  1156. SELECT bonds.ownable_id
  1157. FROM bonds
  1158. JOIN banks b ON bonds.issuer_id = b.user_id
  1159. JOIN ownership o ON o.ownable_id = bonds.ownable_id -- AND bonds.issuer_id = o.user_id
  1160. JOIN orders o2 ON o.rowid = o2.ownership_id AND NOT o2.buy
  1161. WHERE maturity_dt = ?
  1162. AND coupon >= ?
  1163. AND "limit" IS NULL or "limit" <= 1
  1164. ''', (expiry, min_interest)).fetchall()
  1165. for ownable_id, amount in qualified_bonds:
  1166. bank_order(buy=True,
  1167. ownable_id=ownable_id,
  1168. limit=1,
  1169. amount=amount,
  1170. expiry=expiry,
  1171. ioc=True)
  1172. execute('''
  1173. UPDATE tender_calendar
  1174. SET executed = TRUE
  1175. WHERE rowid = ?''', (mro_id,)) # TODO set mro to executed
  1176. def loan_recipient_id(loan_id):
  1177. execute('''
  1178. SELECT user_id
  1179. FROM loans
  1180. WHERE rowid = ?
  1181. ''', (loan_id,))
  1182. return current_cursor.fetchone()[0]
  1183. def loan_remaining_amount(loan_id):
  1184. execute('''
  1185. SELECT amount
  1186. FROM loans
  1187. WHERE rowid = ?
  1188. ''', (loan_id,))
  1189. return current_cursor.fetchone()[0]
  1190. def repay_loan(loan_id, amount, known_user_id=None):
  1191. if known_user_id is None:
  1192. user_id = loan_recipient_id(loan_id)
  1193. else:
  1194. user_id = known_user_id
  1195. send_ownable(user_id, bank_id(), currency_id(), amount)
  1196. execute('''
  1197. UPDATE loans
  1198. SET amount = amount - ?
  1199. WHERE rowid = ?
  1200. ''', (amount, loan_id,))
  1201. if loan_remaining_amount(loan_id) == 0:
  1202. execute('''
  1203. DELETE FROM loans
  1204. WHERE rowid = ?
  1205. ''', (loan_id,))
  1206. def take_out_personal_loan(user_id, amount):
  1207. execute('''
  1208. INSERT INTO loans(user_id, total_amount, amount, interest_rate)
  1209. VALUES (?, ?, ?, ?)
  1210. ''', (user_id, amount, amount, global_control_value('personal_loan_interest_rate')))
  1211. send_ownable(bank_id(), user_id, currency_id(), amount)
  1212. def loan_id_exists(loan_id):
  1213. execute('''
  1214. SELECT EXISTS (SELECT * FROM loans WHERE rowid = ?)
  1215. ''', (loan_id,))
  1216. return current_cursor.fetchone()[0]
  1217. def tender_calendar():
  1218. return execute('''
  1219. SELECT dt, mro_interest, maturity_dt
  1220. FROM tender_calendar
  1221. ''', ).fetchall()
  1222. def issue_bond(user_id, ownable_name, coupon, maturity_dt):
  1223. execute('''
  1224. INSERT INTO ownables(name)
  1225. VALUES (?)
  1226. ''', (ownable_name,))
  1227. execute('''
  1228. INSERT INTO bonds(issuer_id, ownable_id, coupon, maturity_dt)
  1229. VALUES (?, (SELECT MAX(rowid) FROM ownables), ?, ?)
  1230. ''', (user_id, coupon, maturity_dt))
  1231. def update_tender_calendar():
  1232. last_mro_dt = execute('''
  1233. SELECT COALESCE((SELECT dt
  1234. FROM tender_calendar
  1235. ORDER BY dt DESC
  1236. LIMIT 1), ?)
  1237. ''', (current_db_timestamp(),)).fetchone()[0]
  1238. one_day = 24 * 3600
  1239. while last_mro_dt < current_db_timestamp() + one_day:
  1240. last_mro_dt += MRO_INTERVAL
  1241. maturity_dt = last_mro_dt + MRO_RUNNING_TIME
  1242. execute('''
  1243. INSERT INTO tender_calendar(dt, mro_interest, maturity_dt)
  1244. VALUES (?, ?, ?)
  1245. ''', (last_mro_dt, global_control_value('main_refinancing_operations'), maturity_dt))