model.py 43 KB

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