model.py 41 KB

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