model.py 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610
  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 own_id(user_id, ownable_id, amount=0):
  140. execute('''
  141. INSERT OR IGNORE INTO ownership (user_id, ownable_id, amount)
  142. SELECT ?, ?, ?
  143. ''', (user_id, ownable_id, amount))
  144. def send_ownable(from_user_id, to_user_id, ownable_id, amount):
  145. if amount < 0:
  146. raise AssertionError('Can not send negative amount')
  147. bank_id_ = bank_id()
  148. if from_user_id != bank_id_ and not is_bond_of_user(ownable_id, from_user_id):
  149. execute('''
  150. UPDATE ownership
  151. SET amount = amount - ?
  152. WHERE user_id = ?
  153. AND ownable_id = ?
  154. ''', (amount, from_user_id, ownable_id,))
  155. own(to_user_id, ownable_name_by_id(ownable_id))
  156. if not is_bond_of_user(ownable_id, to_user_id):
  157. execute('''
  158. UPDATE ownership
  159. SET amount = amount + ?
  160. WHERE user_id = ?
  161. AND ownable_id = ?
  162. ''', (amount, to_user_id, ownable_id,))
  163. return True
  164. def new_session(user_id):
  165. session_id = str(uuid.uuid4())
  166. execute('''
  167. INSERT INTO SESSIONS
  168. (user_id, session_id)
  169. VALUES (? , ?)
  170. ''', (user_id, session_id))
  171. return session_id
  172. def drop_old_sessions():
  173. execute(''' -- no need to optimize this very well
  174. DELETE FROM sessions
  175. WHERE
  176. (SELECT COUNT(*) as newer
  177. FROM sessions s2
  178. WHERE user_id = s2.user_id
  179. AND rowid < s2.rowid) >= 10
  180. ''')
  181. def user_exists(username):
  182. execute('''
  183. SELECT rowid
  184. FROM users
  185. WHERE username = ?
  186. ''', (username,))
  187. if current_cursor.fetchone():
  188. return True
  189. else:
  190. return False
  191. def get_user_id_by_session_id(session_id):
  192. execute('''
  193. SELECT users.rowid
  194. FROM sessions, users
  195. WHERE sessions.session_id = ?
  196. AND users.rowid = sessions.user_id
  197. ''', (session_id,))
  198. ids = current_cursor.fetchone()
  199. if not ids:
  200. return False
  201. return ids[0]
  202. def get_user_id_by_name(username):
  203. execute('''
  204. SELECT users.rowid
  205. FROM users
  206. WHERE username = ?
  207. ''', (username,))
  208. return current_cursor.fetchone()[0]
  209. def get_user_ownership(user_id):
  210. data = execute('''
  211. SELECT
  212. ownables.name,
  213. ownable_id, -- this is used for computing the available amount
  214. COALESCE (
  215. CASE -- sum score for each of the users ownables
  216. WHEN ownership.ownable_id = ? THEN 1
  217. ELSE (SELECT price
  218. FROM transactions
  219. WHERE ownable_id = ownership.ownable_id
  220. ORDER BY rowid DESC -- equivalent to ordering by dt
  221. LIMIT 1)
  222. END, 0) AS price,
  223. (SELECT MAX("limit")
  224. FROM orders, ownership o2
  225. WHERE o2.rowid = orders.ownership_id
  226. AND o2.ownable_id = ownership.ownable_id
  227. AND buy
  228. AND NOT stop_loss) AS bid,
  229. (SELECT MIN("limit")
  230. FROM orders, ownership o2
  231. WHERE o2.rowid = orders.ownership_id
  232. AND o2.ownable_id = ownership.ownable_id
  233. AND NOT buy
  234. AND NOT stop_loss) AS ask
  235. FROM ownership, ownables
  236. WHERE user_id = ?
  237. AND (ownership.amount >= 0.01 OR ownership.amount <= -0.01 OR ownership.ownable_id = ?)
  238. AND ownership.ownable_id = ownables.rowid
  239. ORDER BY ownables.rowid ASC
  240. ''', (currency_id(), user_id, currency_id(),)).fetchall()
  241. data = [list(row) for row in data]
  242. for row in data:
  243. ownable_id = row[1]
  244. available_amount = user_available_ownable(user_id, ownable_id)
  245. row[1] = available_amount
  246. return data
  247. def bank_id():
  248. execute('''
  249. SELECT users.rowid
  250. FROM users
  251. WHERE username = ?
  252. ''', (BANK_NAME,))
  253. return current_cursor.fetchone()[0]
  254. def valid_session_id(session_id):
  255. execute('''
  256. SELECT rowid
  257. FROM sessions
  258. WHERE session_id = ?
  259. ''', (session_id,))
  260. if current_cursor.fetchone():
  261. return True
  262. else:
  263. return False
  264. def get_user_orders(user_id):
  265. execute('''
  266. SELECT
  267. CASE
  268. WHEN orders.buy THEN 'Buy'
  269. ELSE 'Sell'
  270. END,
  271. ownables.name,
  272. (orders.ordered_amount - orders.executed_amount) || '/' || orders.ordered_amount,
  273. orders."limit",
  274. CASE
  275. WHEN orders."limit" IS NULL THEN NULL
  276. WHEN orders.stop_loss THEN 'Yes'
  277. ELSE 'No'
  278. END,
  279. datetime(orders.expiry_dt, 'localtime'),
  280. orders.rowid
  281. FROM orders, ownables, ownership
  282. WHERE ownership.user_id = ?
  283. AND ownership.ownable_id = ownables.rowid
  284. AND orders.ownership_id = ownership.rowid
  285. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  286. ''', (user_id,))
  287. return current_cursor.fetchall()
  288. def get_user_loans(user_id):
  289. execute('''
  290. SELECT
  291. rowid,
  292. total_amount,
  293. amount,
  294. interest_rate
  295. FROM loans
  296. WHERE user_id is ?
  297. ORDER BY rowid ASC
  298. ''', (user_id,))
  299. return current_cursor.fetchall()
  300. def next_mro_dt(dt=None):
  301. if dt is None:
  302. dt = current_db_timestamp()
  303. return execute('''
  304. SELECT MIN(t.dt) FROM tender_calendar t WHERE t.dt > ?
  305. ''', (dt,)).fetchone()[0]
  306. def next_mro_interest(dt=None):
  307. return execute('''
  308. SELECT t.mro_interest FROM tender_calendar t WHERE t.dt = ?
  309. ''', (next_mro_dt(dt),)).fetchone()[0]
  310. def next_mro_maturity(dt=None):
  311. return execute('''
  312. SELECT t.maturity_dt FROM tender_calendar t WHERE t.dt = ?
  313. ''', (next_mro_dt(dt),)).fetchone()[0]
  314. def credits(issuer_id=None, only_next_mro_qualified=False):
  315. if issuer_id is not None:
  316. issuer_condition = 'issuer.rowid = ?'
  317. issuer_params = (issuer_id,)
  318. else:
  319. issuer_condition = '1'
  320. issuer_params = ()
  321. if only_next_mro_qualified:
  322. only_next_mro_condition = ''' -- noinspection SqlResolve @ any/"credits"
  323. SELECT EXISTS(
  324. SELECT *
  325. FROM banks b
  326. JOIN tender_calendar t ON t.maturity_dt = credits.maturity_dt
  327. WHERE credits.issuer_id = b.user_id
  328. AND credits.coupon >= t.mro_interest
  329. AND t.dt = ?
  330. )
  331. '''
  332. only_next_mro_params = (next_mro_dt(),)
  333. else:
  334. only_next_mro_condition = '1'
  335. only_next_mro_params = ()
  336. execute(f'''
  337. SELECT
  338. name,
  339. coupon,
  340. datetime(maturity_dt, 'unixepoch', 'localtime'),
  341. username
  342. FROM credits
  343. JOIN ownables o on credits.ownable_id = o.rowid
  344. JOIN users issuer on credits.issuer_id = issuer.rowid
  345. WHERE ({issuer_condition})
  346. AND ({only_next_mro_condition})
  347. ORDER BY coupon * (maturity_dt - ?) DESC
  348. ''', (*issuer_params, *only_next_mro_params, current_db_timestamp(),))
  349. return current_cursor.fetchall()
  350. def get_ownable_orders(user_id, ownable_id):
  351. execute('''
  352. SELECT
  353. CASE
  354. WHEN ownership.user_id = ? THEN 'X'
  355. ELSE NULL
  356. END,
  357. CASE
  358. WHEN orders.buy THEN 'Buy'
  359. ELSE 'Sell'
  360. END,
  361. ownables.name,
  362. orders.ordered_amount - orders.executed_amount,
  363. orders."limit",
  364. datetime(orders.expiry_dt, 'localtime'),
  365. orders.rowid
  366. FROM orders, ownables, ownership
  367. WHERE ownership.ownable_id = ?
  368. AND ownership.ownable_id = ownables.rowid
  369. AND orders.ownership_id = ownership.rowid
  370. AND (orders.stop_loss IS NULL OR NOT orders.stop_loss)
  371. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  372. ''', (user_id, ownable_id,))
  373. return current_cursor.fetchall()
  374. def sell_ordered_amount(user_id, ownable_id):
  375. execute('''
  376. SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  377. FROM orders, ownership
  378. WHERE ownership.rowid = orders.ownership_id
  379. AND ownership.user_id = ?
  380. AND ownership.ownable_id = ?
  381. AND NOT orders.buy
  382. ''', (user_id, ownable_id))
  383. return current_cursor.fetchone()[0]
  384. def is_bond_of_user(ownable_id, user_id):
  385. execute('''
  386. SELECT EXISTS(
  387. SELECT * FROM credits
  388. WHERE ownable_id = ?
  389. AND issuer_id = ?
  390. )
  391. ''', (ownable_id, user_id,))
  392. return current_cursor.fetchone()[0]
  393. def user_available_ownable(user_id, ownable_id):
  394. if is_bond_of_user(ownable_id, user_id):
  395. return inf
  396. if user_id == bank_id() and ownable_id == currency_id():
  397. return inf
  398. if ownable_id == currency_id() and user_has_banking_license(user_id):
  399. minimum_reserve = required_minimum_reserve(user_id) + sell_ordered_amount(user_id, ownable_id)
  400. else:
  401. minimum_reserve = sell_ordered_amount(user_id, ownable_id)
  402. execute('''
  403. SELECT amount
  404. FROM ownership
  405. WHERE user_id = ?
  406. AND ownable_id = ?
  407. ''', (user_id, ownable_id))
  408. return current_cursor.fetchone()[0] - minimum_reserve
  409. def user_has_at_least_available(amount, user_id, ownable_id):
  410. if not isinstance(amount, float) and not isinstance(amount, int):
  411. # comparison of float with strings does not work so well in sql
  412. raise ValueError()
  413. return user_available_ownable(user_id, ownable_id) >= amount
  414. def news():
  415. execute('''
  416. SELECT dt, title FROM
  417. (SELECT *, rowid
  418. FROM news
  419. ORDER BY news.rowid DESC -- equivalent to order by dt
  420. LIMIT 20) n
  421. ORDER BY rowid ASC -- equivalent to order by dt
  422. ''')
  423. return current_cursor.fetchall()
  424. def ownable_name_exists(name):
  425. execute('''
  426. SELECT rowid
  427. FROM ownables
  428. WHERE name = ?
  429. ''', (name,))
  430. if current_cursor.fetchone():
  431. return True
  432. else:
  433. return False
  434. def new_stock(expiry, name=None):
  435. name = new_random_ownable_name(name)
  436. execute('''
  437. INSERT INTO ownables(name)
  438. VALUES (?)
  439. ''', (name,))
  440. new_news('A new stock can now be bought: ' + name)
  441. if random.getrandbits(1):
  442. new_news('Experts expect the price of ' + name + ' to fall')
  443. else:
  444. new_news('Experts expect the price of ' + name + ' to rise')
  445. amount = random.randrange(100, 10000)
  446. price = random.randrange(10000, 20000) / amount
  447. ownable_id = ownable_id_by_name(name)
  448. own(bank_id(), name, amount)
  449. bank_order(False,
  450. ownable_id,
  451. price,
  452. amount,
  453. expiry,
  454. ioc=False)
  455. return name
  456. def new_random_ownable_name(name):
  457. while name is None:
  458. name = random_ownable_name()
  459. if ownable_name_exists(name):
  460. name = None
  461. return name
  462. def ownable_id_by_name(ownable_name):
  463. execute('''
  464. SELECT rowid
  465. FROM ownables
  466. WHERE name = ?
  467. ''', (ownable_name,))
  468. return current_cursor.fetchone()[0]
  469. def get_ownership_id(ownable_id, user_id):
  470. execute('''
  471. SELECT rowid
  472. FROM ownership
  473. WHERE ownable_id = ?
  474. AND user_id = ?
  475. ''', (ownable_id, user_id,))
  476. return current_cursor.fetchone()[0]
  477. def currency_id():
  478. execute('''
  479. SELECT rowid
  480. FROM ownables
  481. WHERE name = ?
  482. ''', (CURRENCY_NAME,))
  483. return current_cursor.fetchone()[0]
  484. def user_available_money(user_id):
  485. return user_available_ownable(user_id, currency_id())
  486. def delete_order(order_id, new_order_status):
  487. execute('''
  488. INSERT INTO order_history
  489. (ownership_id, buy, "limit", ordered_amount, executed_amount, expiry_dt, status, order_id)
  490. SELECT
  491. ownership_id,
  492. buy,
  493. "limit",
  494. ordered_amount,
  495. executed_amount,
  496. expiry_dt,
  497. ?,
  498. rowid
  499. FROM orders
  500. WHERE rowid = ?
  501. ''', (new_order_status, order_id,))
  502. execute('''
  503. DELETE FROM orders
  504. WHERE rowid = ?
  505. ''', (order_id,))
  506. def current_value(ownable_id):
  507. if ownable_id == currency_id():
  508. return 1
  509. execute('''SELECT price
  510. FROM transactions
  511. WHERE ownable_id = ?
  512. ORDER BY rowid DESC -- equivalent to order by dt
  513. LIMIT 1
  514. ''', (ownable_id,))
  515. return current_cursor.fetchone()[0]
  516. def execute_orders(ownable_id):
  517. while True:
  518. # find order to execute
  519. execute('''
  520. -- two best orders
  521. SELECT * FROM (
  522. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  523. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  524. WHERE buy_order.buy AND NOT sell_order.buy
  525. AND buyer.rowid = buy_order.ownership_id
  526. AND seller.rowid = sell_order.ownership_id
  527. AND buyer.ownable_id = ?
  528. AND seller.ownable_id = ?
  529. AND buy_order."limit" IS NULL
  530. AND sell_order."limit" IS NULL
  531. ORDER BY buy_order.rowid ASC,
  532. sell_order.rowid ASC
  533. LIMIT 1)
  534. UNION ALL -- best buy orders
  535. SELECT * FROM (
  536. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  537. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  538. WHERE buy_order.buy AND NOT sell_order.buy
  539. AND buyer.rowid = buy_order.ownership_id
  540. AND seller.rowid = sell_order.ownership_id
  541. AND buyer.ownable_id = ?
  542. AND seller.ownable_id = ?
  543. AND buy_order."limit" IS NULL
  544. AND sell_order."limit" IS NOT NULL
  545. AND NOT sell_order.stop_loss
  546. ORDER BY sell_order."limit" ASC,
  547. buy_order.rowid ASC,
  548. sell_order.rowid ASC
  549. LIMIT 1)
  550. UNION ALL -- best sell orders
  551. SELECT * FROM (
  552. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  553. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  554. WHERE buy_order.buy AND NOT sell_order.buy
  555. AND buyer.rowid = buy_order.ownership_id
  556. AND seller.rowid = sell_order.ownership_id
  557. AND buyer.ownable_id = ?
  558. AND seller.ownable_id = ?
  559. AND buy_order."limit" IS NOT NULL
  560. AND NOT buy_order.stop_loss
  561. AND sell_order."limit" IS NULL
  562. ORDER BY buy_order."limit" DESC,
  563. buy_order.rowid ASC,
  564. sell_order.rowid ASC
  565. LIMIT 1)
  566. UNION ALL -- both limit orders
  567. SELECT * FROM (
  568. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  569. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  570. WHERE buy_order.buy AND NOT sell_order.buy
  571. AND buyer.rowid = buy_order.ownership_id
  572. AND seller.rowid = sell_order.ownership_id
  573. AND buyer.ownable_id = ?
  574. AND seller.ownable_id = ?
  575. AND buy_order."limit" IS NOT NULL
  576. AND sell_order."limit" IS NOT NULL
  577. AND sell_order."limit" <= buy_order."limit"
  578. AND NOT sell_order.stop_loss
  579. AND NOT buy_order.stop_loss
  580. ORDER BY buy_order."limit" DESC,
  581. sell_order."limit" ASC,
  582. buy_order.rowid ASC,
  583. sell_order.rowid ASC
  584. LIMIT 1)
  585. LIMIT 1
  586. ''', tuple(ownable_id for _ in range(8)))
  587. matching_order = current_cursor.fetchone()
  588. # return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  589. # ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  590. # user_id,user_id,rowid,rowid)
  591. if not matching_order:
  592. break
  593. _, buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, _, \
  594. _, sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, _, \
  595. buyer_id, seller_id, buy_order_id, sell_order_id \
  596. = matching_order
  597. if buy_limit is None and sell_limit is None:
  598. price = current_value(ownable_id)
  599. elif buy_limit is None:
  600. price = sell_limit
  601. elif sell_limit is None:
  602. price = buy_limit
  603. else: # both not NULL
  604. # the price of the older order is used, just like in the real exchange
  605. if buy_order_id < sell_order_id:
  606. price = buy_limit
  607. else:
  608. price = sell_limit
  609. buyer_money = user_available_money(buyer_id)
  610. def affordable_nominal(money, price_per_nominal):
  611. if money == inf or price_per_nominal <= 0:
  612. return inf
  613. else:
  614. return floor(money / price_per_nominal)
  615. amount = min(buy_order_amount - buy_executed_amount,
  616. sell_order_amount - sell_executed_amount,
  617. affordable_nominal(buyer_money, price))
  618. if amount < 0:
  619. amount = 0
  620. if amount == 0: # probable because buyer has not enough money
  621. delete_order(buy_order_id, 'Unable to pay')
  622. continue
  623. buy_order_finished = (buy_order_amount - buy_executed_amount - amount <= 0) or (
  624. buyer_money - amount * price < price)
  625. sell_order_finished = (sell_order_amount - sell_executed_amount - amount <= 0)
  626. if price < 0 or amount <= 0: # price of 0 is possible though unlikely
  627. return AssertionError()
  628. # actually execute the order, but the bank does not send or receive anything
  629. send_ownable(buyer_id, seller_id, currency_id(), price * amount)
  630. send_ownable(seller_id, buyer_id, ownable_id, amount)
  631. # update order execution state
  632. execute('''
  633. UPDATE orders
  634. SET executed_amount = executed_amount + ?
  635. WHERE rowid = ?
  636. OR rowid = ?
  637. ''', (amount, buy_order_id, sell_order_id))
  638. if buy_order_finished:
  639. delete_order(buy_order_id, 'Executed')
  640. if sell_order_finished:
  641. delete_order(sell_order_id, 'Executed')
  642. if seller_id != buyer_id: # prevent showing self-transactions
  643. execute('''
  644. INSERT INTO transactions
  645. (price, ownable_id, amount, buyer_id, seller_id)
  646. VALUES(?, ?, ?, ?, ?)
  647. ''', (price, ownable_id, amount, buyer_id, seller_id))
  648. # trigger stop-loss orders
  649. if buyer_id != seller_id:
  650. execute('''
  651. UPDATE orders
  652. SET stop_loss = NULL,
  653. "limit" = NULL
  654. WHERE stop_loss IS NOT NULL
  655. AND stop_loss
  656. AND ? IN (SELECT ownable_id FROM ownership WHERE rowid = ownership_id)
  657. AND ((buy AND "limit" < ?) OR (NOT buy AND "limit" > ?))
  658. ''', (ownable_id, price, price,))
  659. def ownable_id_by_ownership_id(ownership_id):
  660. execute('''
  661. SELECT ownable_id
  662. FROM ownership
  663. WHERE rowid = ?
  664. ''', (ownership_id,))
  665. return current_cursor.fetchone()[0]
  666. def ownable_name_by_id(ownable_id):
  667. execute('''
  668. SELECT name
  669. FROM ownables
  670. WHERE rowid = ?
  671. ''', (ownable_id,))
  672. return current_cursor.fetchone()[0]
  673. def user_name_by_id(user_id):
  674. execute('''
  675. SELECT username
  676. FROM users
  677. WHERE rowid = ?
  678. ''', (user_id,))
  679. return current_cursor.fetchone()[0]
  680. def bank_order(buy, ownable_id, limit, amount, expiry, ioc):
  681. if not limit:
  682. raise AssertionError('The bank does not give away anything.')
  683. own_id(bank_id(), ownable_id)
  684. place_order(buy,
  685. get_ownership_id(ownable_id, bank_id()),
  686. limit,
  687. False,
  688. amount,
  689. expiry,
  690. ioc=ioc)
  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. ( SELECT COALESCE(SUM(
  818. amount
  819. ), 0)
  820. FROM credits
  821. JOIN ownership o on credits.ownable_id = o.ownable_id
  822. WHERE credits.issuer_id = ?
  823. AND o.user_id != ?
  824. )
  825. '''
  826. execute(f'''
  827. SELECT ({score_expression}) AS score
  828. ''', (currency_id(), user_id, user_id, user_id, user_id,))
  829. return current_cursor.fetchone()[0]
  830. def change_password(session_id, password, salt):
  831. execute('''
  832. UPDATE users
  833. SET password = ?, salt= ?
  834. WHERE rowid = (SELECT user_id FROM sessions WHERE sessions.session_id = ?)
  835. ''', (password, salt, session_id,))
  836. def sign_out_user(session_id):
  837. execute('''
  838. DELETE FROM sessions
  839. WHERE user_id = (SELECT user_id FROM sessions s2 WHERE s2.session_id = ?)
  840. ''', (session_id,))
  841. def delete_user(user_id):
  842. execute('''
  843. DELETE FROM sessions
  844. WHERE user_id = ?
  845. ''', (user_id,))
  846. execute('''
  847. DELETE FROM orders
  848. WHERE ownership_id IN (
  849. SELECT rowid FROM ownership WHERE user_id = ?)
  850. ''', (user_id,))
  851. execute('''
  852. DELETE FROM ownership
  853. WHERE user_id = ?
  854. ''', (user_id,))
  855. execute('''
  856. DELETE FROM keys
  857. WHERE used_by_user_id = ?
  858. ''', (user_id,))
  859. execute('''
  860. INSERT INTO news(title)
  861. VALUES ((SELECT username FROM users WHERE rowid = ?) || ' retired.')
  862. ''', (user_id,))
  863. execute('''
  864. DELETE FROM users
  865. WHERE rowid = ?
  866. ''', (user_id,))
  867. def delete_ownable(ownable_id):
  868. execute('''
  869. DELETE FROM transactions
  870. WHERE ownable_id = ?
  871. ''', (ownable_id,))
  872. execute('''
  873. DELETE FROM orders
  874. WHERE ownership_id IN (
  875. SELECT rowid FROM ownership WHERE ownable_id = ?)
  876. ''', (ownable_id,))
  877. execute('''
  878. DELETE FROM order_history
  879. WHERE ownership_id IN (
  880. SELECT rowid FROM ownership WHERE ownable_id = ?)
  881. ''', (ownable_id,))
  882. # only delete empty ownerships
  883. execute('''
  884. DELETE FROM ownership
  885. WHERE ownable_id = ?
  886. AND amount = 0
  887. ''', (ownable_id,))
  888. execute('''
  889. INSERT INTO news(title)
  890. VALUES ((SELECT name FROM ownables WHERE rowid = ?) || ' can not be traded any more.')
  891. ''', (ownable_id,))
  892. execute('''
  893. DELETE FROM ownables
  894. WHERE rowid = ?
  895. ''', (ownable_id,))
  896. def hash_all_users_passwords():
  897. execute('''
  898. SELECT rowid, password, salt
  899. FROM users
  900. ''')
  901. users = current_cursor.fetchall()
  902. for user_id, pw, salt in users:
  903. valid_hash = True
  904. try:
  905. sha256_crypt.verify('password' + salt, pw)
  906. except ValueError:
  907. valid_hash = False
  908. if valid_hash:
  909. raise AssertionError('There is already a hashed password in the database! Be careful what you are doing!')
  910. pw = sha256_crypt.encrypt(pw + salt)
  911. execute('''
  912. UPDATE users
  913. SET password = ?
  914. WHERE rowid = ?
  915. ''', (pw, user_id,))
  916. def new_news(message):
  917. execute('''
  918. INSERT INTO news(title)
  919. VALUES (?)
  920. ''', (message,))
  921. def abs_spread(ownable_id):
  922. execute('''
  923. SELECT
  924. (SELECT MAX("limit")
  925. FROM orders, ownership
  926. WHERE ownership.rowid = orders.ownership_id
  927. AND ownership.ownable_id = ?
  928. AND buy
  929. AND NOT stop_loss) AS bid,
  930. (SELECT MIN("limit")
  931. FROM orders, ownership
  932. WHERE ownership.rowid = orders.ownership_id
  933. AND ownership.ownable_id = ?
  934. AND NOT buy
  935. AND NOT stop_loss) AS ask
  936. ''', (ownable_id, ownable_id,))
  937. return current_cursor.fetchone()
  938. def ownables():
  939. execute('''
  940. SELECT name, course,
  941. (SELECT SUM(amount)
  942. FROM ownership
  943. WHERE ownership.ownable_id = ownables_with_course.rowid) market_size
  944. FROM (SELECT
  945. name, ownables.rowid,
  946. CASE WHEN ownables.rowid = ?
  947. THEN 1
  948. ELSE (SELECT price
  949. FROM transactions
  950. WHERE ownable_id = ownables.rowid
  951. ORDER BY rowid DESC -- equivalent to ordering by dt
  952. LIMIT 1) END course
  953. FROM ownables) ownables_with_course
  954. ''', (currency_id(),))
  955. data = current_cursor.fetchall()
  956. for idx in range(len(data)):
  957. # compute market cap
  958. row = data[idx]
  959. if row[1] is None:
  960. market_cap = None
  961. elif row[2] is None:
  962. market_cap = None
  963. else:
  964. market_cap = row[1] * row[2]
  965. data[idx] = (row[0], row[1], market_cap)
  966. return data
  967. def reset_bank():
  968. execute('''
  969. DELETE FROM ownership
  970. WHERE user_id = ?
  971. ''', (bank_id(),))
  972. def cleanup():
  973. global connections
  974. global current_connection
  975. global current_cursor
  976. global current_db_name
  977. global current_user_id
  978. for name in connections:
  979. connections[name].rollback()
  980. connections[name].close()
  981. connections = {}
  982. current_connection = None
  983. current_cursor = None
  984. current_db_name = None
  985. current_user_id = None
  986. def ownable_ids():
  987. execute('''
  988. SELECT rowid FROM ownables
  989. ''')
  990. return [ownable_id[0] for ownable_id in current_cursor.fetchall()]
  991. def get_old_orders(user_id, include_executed, include_canceled, limit):
  992. execute('''
  993. SELECT
  994. (CASE WHEN order_history.buy THEN 'Buy' ELSE 'Sell' END),
  995. ownables.name,
  996. (order_history.ordered_amount - order_history.executed_amount) || '/' || order_history.ordered_amount,
  997. order_history."limit",
  998. order_history.expiry_dt,
  999. order_history.order_id,
  1000. order_history.status
  1001. FROM order_history, ownership, ownables
  1002. WHERE ownership.user_id = ?
  1003. AND ownership.rowid = order_history.ownership_id
  1004. AND ownables.rowid = ownership.ownable_id
  1005. AND (
  1006. (order_history.status = 'Executed' AND ?)
  1007. OR
  1008. ((order_history.status = 'Expired' OR order_history.status = 'Canceled') AND ?)
  1009. )
  1010. ORDER BY order_history.rowid DESC -- equivalent to ordering by creation time
  1011. LIMIT ?
  1012. ''', (user_id, include_executed, include_canceled, limit))
  1013. return current_cursor.fetchall()
  1014. def user_has_banking_license(user_id):
  1015. execute('''
  1016. SELECT EXISTS (SELECT * FROM banks WHERE user_id = ?)
  1017. ''', (user_id,))
  1018. return current_cursor.fetchone()[0]
  1019. def global_control_value(value_name):
  1020. execute('''
  1021. SELECT value
  1022. FROM global_control_values
  1023. WHERE value_name = ?
  1024. AND dt = (SELECT MAX(dt) FROM global_control_values WHERE value_name = ?)
  1025. ''', (value_name, value_name,))
  1026. return current_cursor.fetchone()[0]
  1027. def global_control_values():
  1028. execute('''
  1029. SELECT value_name, value
  1030. FROM global_control_values v1
  1031. WHERE dt IN (SELECT MAX(dt) FROM global_control_values v2 GROUP BY v2.value_name)
  1032. ''')
  1033. return {
  1034. row[0]: row[1] for row in current_cursor.fetchall()
  1035. }
  1036. def assign_banking_licence(user_id):
  1037. execute('''
  1038. INSERT INTO banks(user_id)
  1039. VALUES (?)
  1040. ''', (user_id,))
  1041. def pay_bond_interest(until=None):
  1042. if until is None:
  1043. current_dt = current_db_timestamp()
  1044. else:
  1045. current_dt = until
  1046. sec_per_year = 3600 * 24 * 365
  1047. interests = execute('''
  1048. SELECT
  1049. SUM(amount * coupon * (MIN(CAST(? AS FLOAT), maturity_dt) - last_interest_pay_dt) / ?) AS interest_since_last_pay,
  1050. o.user_id AS to_user_id,
  1051. credits.issuer_id AS from_user_id
  1052. FROM credits
  1053. JOIN ownership o on credits.ownable_id = o.ownable_id
  1054. WHERE (? - last_interest_pay_dt > ? OR ? > maturity_dt) -- every interval or when the bond expired
  1055. AND amount != 0
  1056. GROUP BY o.user_id, credits.issuer_id
  1057. ''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL, current_dt)).fetchall()
  1058. matured_credits = execute('''
  1059. SELECT
  1060. amount,
  1061. o.user_id AS to_user_id,
  1062. credits.issuer_id AS from_user_id
  1063. FROM credits
  1064. JOIN ownership o ON credits.ownable_id = o.ownable_id
  1065. WHERE ? > maturity_dt
  1066. ''', (current_dt,)).fetchall()
  1067. # transfer the interest money
  1068. for amount, to_user_id, from_user_id in interests:
  1069. send_ownable(from_user_id, to_user_id, currency_id(), amount)
  1070. # pay back matured credits
  1071. for amount, to_user_id, from_user_id in matured_credits:
  1072. send_ownable(from_user_id, to_user_id, currency_id(), amount)
  1073. execute('''
  1074. UPDATE credits
  1075. SET last_interest_pay_dt = ?
  1076. WHERE ? - last_interest_pay_dt > ?''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1077. # delete matured credits
  1078. delete_matured_credits(current_dt)
  1079. def delete_matured_credits(current_dt):
  1080. execute('''
  1081. DELETE FROM transactions
  1082. WHERE ownable_id IN (
  1083. SELECT ownable_id
  1084. FROM credits
  1085. WHERE ? > maturity_dt
  1086. )
  1087. ''', (current_dt,))
  1088. execute('''
  1089. DELETE FROM orders
  1090. WHERE ownership_id IN (
  1091. SELECT o2.rowid
  1092. FROM credits
  1093. JOIN ownables o on credits.ownable_id = o.rowid
  1094. JOIN ownership o2 on o.rowid = o2.ownable_id
  1095. WHERE ? > maturity_dt
  1096. )
  1097. ''', (current_dt,))
  1098. execute('''
  1099. DELETE FROM order_history
  1100. WHERE ownership_id IN (
  1101. SELECT o2.rowid
  1102. FROM credits
  1103. JOIN ownables o on credits.ownable_id = o.rowid
  1104. JOIN ownership o2 on o.rowid = o2.ownable_id
  1105. WHERE ? > maturity_dt
  1106. )
  1107. ''', (current_dt,))
  1108. execute('''
  1109. DELETE FROM ownership
  1110. WHERE ownable_id IN (
  1111. SELECT ownable_id
  1112. FROM credits
  1113. WHERE ? > maturity_dt
  1114. )
  1115. ''', (current_dt,))
  1116. execute('''
  1117. DELETE FROM credits
  1118. WHERE ? > maturity_dt
  1119. ''', (current_dt,))
  1120. execute('''
  1121. DELETE FROM ownables
  1122. WHERE rowid IN (
  1123. SELECT ownable_id
  1124. FROM credits
  1125. WHERE ? > maturity_dt
  1126. )
  1127. ''', (current_dt,))
  1128. def pay_loan_interest(until=None):
  1129. if until is None:
  1130. current_dt = current_db_timestamp()
  1131. else:
  1132. current_dt = until
  1133. sec_per_year = 3600 * 24 * 365
  1134. interests = execute('''
  1135. SELECT
  1136. SUM(amount * interest_rate * (CAST(? AS FLOAT) - last_interest_pay_dt) / ?) AS interest_since_last_pay,
  1137. user_id
  1138. FROM loans
  1139. WHERE ? - last_interest_pay_dt > ?
  1140. GROUP BY user_id
  1141. ''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL)).fetchall()
  1142. executemany(f'''
  1143. UPDATE ownership
  1144. SET amount = amount - ?
  1145. WHERE ownable_id = {currency_id()}
  1146. AND user_id = ?
  1147. ''', interests)
  1148. # noinspection SqlWithoutWhere
  1149. execute('''
  1150. UPDATE loans
  1151. SET last_interest_pay_dt = ?
  1152. WHERE ? - last_interest_pay_dt > ?
  1153. ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1154. def pay_deposit_facility(until=None):
  1155. if until is None:
  1156. current_dt = current_db_timestamp()
  1157. else:
  1158. current_dt = until
  1159. sec_per_year = 3600 * 24 * 365
  1160. interest_rate = global_control_value('deposit_facility')
  1161. banks = execute('''
  1162. SELECT
  1163. banks.user_id,
  1164. o.amount * ? * (CAST(? AS FLOAT) - last_deposit_facility_pay_dt) / ?
  1165. FROM banks
  1166. JOIN ownership o on banks.user_id = o.user_id
  1167. WHERE o.rowid = ?
  1168. AND ? - last_deposit_facility_pay_dt > ?''', (interest_rate, current_dt, sec_per_year, currency_id(), current_dt, MIN_INTEREST_INTERVAL)).fetchall()
  1169. for user_id, interest_amount in banks:
  1170. send_ownable(user_id, bank_id(), currency_id(), interest_amount)
  1171. execute('''
  1172. UPDATE banks
  1173. SET last_deposit_facility_pay_dt = ?
  1174. WHERE ? - last_deposit_facility_pay_dt > ?
  1175. ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1176. def triggered_mros():
  1177. return execute('''
  1178. SELECT
  1179. rowid AS mro_id,
  1180. maturity_dt,
  1181. mro_interest AS min_interest,
  1182. dt AS mro_dt
  1183. FROM tender_calendar
  1184. WHERE NOT executed
  1185. AND dt < ?
  1186. ORDER BY dt ASC
  1187. ''', (current_db_timestamp(),)).fetchall()
  1188. def mro(mro_id, maturity_dt, min_interest):
  1189. qualified_credits = execute('''
  1190. SELECT credits.ownable_id, SUM(ordered_amount)
  1191. FROM credits
  1192. JOIN banks b ON credits.issuer_id = b.user_id
  1193. JOIN ownership o ON o.ownable_id = credits.ownable_id -- AND credits.issuer_id = o.user_id
  1194. JOIN orders o2 ON o.rowid = o2.ownership_id AND NOT o2.buy
  1195. WHERE maturity_dt = ?
  1196. AND coupon >= ?
  1197. AND ("limit" IS NULL OR "limit" <= 1)
  1198. GROUP BY credits.ownable_id
  1199. ''', (maturity_dt, min_interest)).fetchall()
  1200. for ownable_id, amount in qualified_credits:
  1201. if amount == 0:
  1202. continue
  1203. assert amount > 0
  1204. bank_order(buy=True,
  1205. ownable_id=ownable_id,
  1206. limit=1,
  1207. amount=amount,
  1208. expiry=maturity_dt,
  1209. ioc=True)
  1210. execute('''
  1211. UPDATE tender_calendar
  1212. SET executed = TRUE
  1213. WHERE rowid = ?''', (mro_id,))
  1214. def loan_recipient_id(loan_id):
  1215. execute('''
  1216. SELECT user_id
  1217. FROM loans
  1218. WHERE rowid = ?
  1219. ''', (loan_id,))
  1220. return current_cursor.fetchone()[0]
  1221. def loan_remaining_amount(loan_id):
  1222. execute('''
  1223. SELECT amount
  1224. FROM loans
  1225. WHERE rowid = ?
  1226. ''', (loan_id,))
  1227. return current_cursor.fetchone()[0]
  1228. def repay_loan(loan_id, amount, known_user_id=None):
  1229. if known_user_id is None:
  1230. user_id = loan_recipient_id(loan_id)
  1231. else:
  1232. user_id = known_user_id
  1233. send_ownable(user_id, bank_id(), currency_id(), amount)
  1234. execute('''
  1235. UPDATE loans
  1236. SET amount = amount - ?
  1237. WHERE rowid = ?
  1238. ''', (amount, loan_id,))
  1239. if loan_remaining_amount(loan_id) == 0:
  1240. execute('''
  1241. DELETE FROM loans
  1242. WHERE rowid = ?
  1243. ''', (loan_id,))
  1244. def take_out_personal_loan(user_id, amount):
  1245. execute('''
  1246. INSERT INTO loans(user_id, total_amount, amount, interest_rate)
  1247. VALUES (?, ?, ?, ?)
  1248. ''', (user_id, amount, amount, global_control_value('personal_loan_interest_rate')))
  1249. send_ownable(bank_id(), user_id, currency_id(), amount)
  1250. def loan_id_exists(loan_id):
  1251. execute('''
  1252. SELECT EXISTS (SELECT * FROM loans WHERE rowid = ?)
  1253. ''', (loan_id,))
  1254. return current_cursor.fetchone()[0]
  1255. def time_travel(delta_t):
  1256. """
  1257. Modify all timestamps in the database by -delta_t.
  1258. A positive delta_t travels into the future, a negative delta_t to the past.
  1259. Be careful with time travel into the past though.
  1260. :param delta_t: time in seconds to travel
  1261. """
  1262. print(f'DEBUG INFO: Time traveling {round(delta_t)}s into the future by reducing all timestamps by {round(delta_t)}...')
  1263. tables = execute('''
  1264. SELECT name
  1265. FROM sqlite_master
  1266. WHERE type = 'table'
  1267. ''').fetchall()
  1268. for (table,) in tables:
  1269. columns = execute(f'''
  1270. SELECT * FROM {table}
  1271. LIMIT 1
  1272. ''').description
  1273. timestamp_columns = []
  1274. for column in columns:
  1275. name = column[0]
  1276. if re.search(r'(?:^|_)dt(?:$|_)', name):
  1277. timestamp_columns.append(name)
  1278. if len(timestamp_columns) != 0:
  1279. updates = ',\n'.join(f'"{column}" = "{column}" + ?' for column in timestamp_columns)
  1280. execute(f'''
  1281. UPDATE {table}
  1282. SET {updates}
  1283. ''', tuple(-delta_t for _ in timestamp_columns))
  1284. def user_has_loan_with_id(user_id, loan_id):
  1285. execute('''
  1286. SELECT EXISTS (SELECT * FROM loans WHERE rowid = ? AND user_id = ?)
  1287. ''', (loan_id, user_id))
  1288. return current_cursor.fetchone()[0]
  1289. def tender_calendar():
  1290. return execute('''
  1291. SELECT dt, mro_interest, maturity_dt
  1292. FROM tender_calendar
  1293. ORDER BY dt DESC
  1294. LIMIT 20
  1295. ''', ).fetchall()
  1296. def required_minimum_reserve(user_id):
  1297. assert user_has_banking_license(user_id)
  1298. borrowed_money = execute('''
  1299. SELECT COALESCE(SUM(amount), 0)
  1300. FROM ownership
  1301. JOIN credits b on ownership.ownable_id = b.ownable_id
  1302. WHERE b.issuer_id = ?
  1303. AND ownership.user_id = ?
  1304. ''', (user_id, bank_id())).fetchone()[0]
  1305. return max(0, global_control_value('cash_reserve_ratio') * borrowed_money - global_control_value('cash_reserve_free_amount'))
  1306. def issue_bond(user_id, ownable_name, coupon, maturity_dt):
  1307. execute('''
  1308. INSERT INTO ownables(name)
  1309. VALUES (?)
  1310. ''', (ownable_name,))
  1311. execute('''
  1312. INSERT INTO credits(issuer_id, ownable_id, coupon, maturity_dt)
  1313. VALUES (?, (SELECT MAX(rowid) FROM ownables), ?, ?)
  1314. ''', (user_id, coupon, maturity_dt))
  1315. def update_tender_calendar():
  1316. last_mro_dt = execute('''
  1317. SELECT COALESCE((SELECT dt
  1318. FROM tender_calendar
  1319. ORDER BY dt DESC
  1320. LIMIT 1), ?)
  1321. ''', (current_db_timestamp(),)).fetchone()[0]
  1322. one_month = 30 * 24 * 3600
  1323. while last_mro_dt < current_db_timestamp() + one_month:
  1324. last_mro_dt += MRO_INTERVAL
  1325. maturity_dt = last_mro_dt + MRO_RUNNING_TIME
  1326. execute('''
  1327. INSERT INTO tender_calendar(dt, mro_interest, maturity_dt)
  1328. VALUES (?, ?, ?)
  1329. ''', (last_mro_dt, global_control_value('main_refinancing_operations'), maturity_dt))