model.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339
  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
  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_:
  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_ or ownable_id != currency_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 = 'bank'
  278. ''')
  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 get_ownable_orders(user_id, ownable_id):
  327. execute('''
  328. SELECT
  329. CASE
  330. WHEN ownership.user_id = ? THEN 'X'
  331. ELSE NULL
  332. END,
  333. CASE
  334. WHEN orders.buy THEN 'Buy'
  335. ELSE 'Sell'
  336. END,
  337. ownables.name,
  338. orders.ordered_amount - orders.executed_amount,
  339. orders."limit",
  340. datetime(orders.expiry_dt, 'localtime'),
  341. orders.rowid
  342. FROM orders, ownables, ownership
  343. WHERE ownership.ownable_id = ?
  344. AND ownership.ownable_id = ownables.rowid
  345. AND orders.ownership_id = ownership.rowid
  346. AND (orders.stop_loss IS NULL OR NOT orders.stop_loss)
  347. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  348. ''', (user_id, ownable_id,))
  349. return current_cursor.fetchall()
  350. def sell_ordered_amount(user_id, ownable_id):
  351. execute('''
  352. SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  353. FROM orders, ownership
  354. WHERE ownership.rowid = orders.ownership_id
  355. AND ownership.user_id = ?
  356. AND ownership.ownable_id = ?
  357. AND NOT orders.buy
  358. ''', (user_id, ownable_id))
  359. return current_cursor.fetchone()[0]
  360. def available_amount(user_id, ownable_id):
  361. execute('''
  362. SELECT amount
  363. FROM ownership
  364. WHERE user_id = ?
  365. AND ownable_id = ?
  366. ''', (user_id, ownable_id))
  367. return current_cursor.fetchone()[0] - sell_ordered_amount(user_id, ownable_id)
  368. def is_bond_of_user(ownable_id, user_id):
  369. execute('''
  370. SELECT EXISTS(
  371. SELECT * FROM bonds
  372. WHERE ownable_id = ?
  373. AND issuer_id = ?
  374. )
  375. ''', (ownable_id, user_id,))
  376. return current_cursor.fetchone()[0]
  377. def user_has_at_least_available(amount, user_id, ownable_id):
  378. if is_bond_of_user(ownable_id, user_id):
  379. return True
  380. if not isinstance(amount, float) and not isinstance(amount, int):
  381. # comparison of float with strings does not work so well in sql
  382. raise AssertionError()
  383. execute('''
  384. SELECT rowid
  385. FROM ownership
  386. WHERE user_id = ?
  387. AND ownable_id = ?
  388. AND amount - ? >= ?
  389. ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
  390. if current_cursor.fetchone():
  391. return True
  392. else:
  393. return False
  394. def news():
  395. execute('''
  396. SELECT dt, title FROM
  397. (SELECT *, rowid
  398. FROM news
  399. ORDER BY news.rowid DESC -- equivalent to order by dt
  400. LIMIT 20) n
  401. ORDER BY rowid ASC -- equivalent to order by dt
  402. ''')
  403. return current_cursor.fetchall()
  404. def ownable_name_exists(name):
  405. execute('''
  406. SELECT rowid
  407. FROM ownables
  408. WHERE name = ?
  409. ''', (name,))
  410. if current_cursor.fetchone():
  411. return True
  412. else:
  413. return False
  414. def new_stock(expiry, name=None):
  415. while name is None:
  416. name = random_chars(6)
  417. if ownable_name_exists(name):
  418. name = None
  419. execute('''
  420. INSERT INTO ownables(name)
  421. VALUES (?)
  422. ''', (name,))
  423. new_news('A new stock can now be bought: ' + name)
  424. if random.getrandbits(1):
  425. new_news('Experts expect the price of ' + name + ' to fall')
  426. else:
  427. new_news('Experts expect the price of ' + name + ' to rise')
  428. amount = random.randrange(100, 10000)
  429. price = random.randrange(10000, 20000) / amount
  430. ownable_id = ownable_id_by_name(name)
  431. own(bank_id(), name, amount)
  432. bank_order(False,
  433. ownable_id,
  434. price,
  435. amount,
  436. expiry,
  437. ioc=False)
  438. return name
  439. def ownable_id_by_name(ownable_name):
  440. execute('''
  441. SELECT rowid
  442. FROM ownables
  443. WHERE name = ?
  444. ''', (ownable_name,))
  445. return current_cursor.fetchone()[0]
  446. def get_ownership_id(ownable_id, user_id):
  447. execute('''
  448. SELECT rowid
  449. FROM ownership
  450. WHERE ownable_id = ?
  451. AND user_id = ?
  452. ''', (ownable_id, user_id,))
  453. return current_cursor.fetchone()[0]
  454. def currency_id():
  455. execute('''
  456. SELECT rowid
  457. FROM ownables
  458. WHERE name = ?
  459. ''', (CURRENCY_NAME,))
  460. return current_cursor.fetchone()[0]
  461. def mro_id():
  462. execute('''
  463. SELECT rowid
  464. FROM ownables
  465. WHERE name = ?
  466. ''', (MRO_NAME,))
  467. return current_cursor.fetchone()[0]
  468. def user_money(user_id):
  469. execute('''
  470. SELECT amount
  471. FROM ownership
  472. WHERE user_id = ?
  473. AND ownable_id = ?
  474. ''', (user_id, currency_id()))
  475. return current_cursor.fetchone()[0]
  476. def delete_order(order_id, new_order_status):
  477. execute('''
  478. INSERT INTO order_history
  479. (ownership_id, buy, "limit", ordered_amount, executed_amount, expiry_dt, status, order_id)
  480. SELECT
  481. ownership_id,
  482. buy,
  483. "limit",
  484. ordered_amount,
  485. executed_amount,
  486. expiry_dt,
  487. ?,
  488. rowid
  489. FROM orders
  490. WHERE rowid = ?
  491. ''', (new_order_status, order_id,))
  492. execute('''
  493. DELETE FROM orders
  494. WHERE rowid = ?
  495. ''', (order_id,))
  496. def current_value(ownable_id):
  497. if ownable_id == currency_id():
  498. return 1
  499. execute('''SELECT price
  500. FROM transactions
  501. WHERE ownable_id = ?
  502. ORDER BY rowid DESC -- equivalent to order by dt
  503. LIMIT 1
  504. ''', (ownable_id,))
  505. return current_cursor.fetchone()[0]
  506. def execute_orders(ownable_id):
  507. orders_traded = False
  508. while True:
  509. # find order to execute
  510. execute('''
  511. -- two best orders
  512. SELECT * FROM (
  513. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  514. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  515. WHERE buy_order.buy AND NOT sell_order.buy
  516. AND buyer.rowid = buy_order.ownership_id
  517. AND seller.rowid = sell_order.ownership_id
  518. AND buyer.ownable_id = ?
  519. AND seller.ownable_id = ?
  520. AND buy_order."limit" IS NULL
  521. AND sell_order."limit" IS NULL
  522. ORDER BY buy_order.rowid ASC,
  523. sell_order.rowid ASC
  524. LIMIT 1)
  525. UNION ALL -- best buy orders
  526. SELECT * FROM (
  527. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  528. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  529. WHERE buy_order.buy AND NOT sell_order.buy
  530. AND buyer.rowid = buy_order.ownership_id
  531. AND seller.rowid = sell_order.ownership_id
  532. AND buyer.ownable_id = ?
  533. AND seller.ownable_id = ?
  534. AND buy_order."limit" IS NULL
  535. AND sell_order."limit" IS NOT NULL
  536. AND NOT sell_order.stop_loss
  537. ORDER BY sell_order."limit" ASC,
  538. buy_order.rowid ASC,
  539. sell_order.rowid ASC
  540. LIMIT 1)
  541. UNION ALL -- best sell orders
  542. SELECT * FROM (
  543. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  544. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  545. WHERE buy_order.buy AND NOT sell_order.buy
  546. AND buyer.rowid = buy_order.ownership_id
  547. AND seller.rowid = sell_order.ownership_id
  548. AND buyer.ownable_id = ?
  549. AND seller.ownable_id = ?
  550. AND buy_order."limit" IS NOT NULL
  551. AND NOT buy_order.stop_loss
  552. AND sell_order."limit" IS NULL
  553. ORDER BY buy_order."limit" DESC,
  554. buy_order.rowid ASC,
  555. sell_order.rowid ASC
  556. LIMIT 1)
  557. UNION ALL -- both limit orders
  558. SELECT * FROM (
  559. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  560. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  561. WHERE buy_order.buy AND NOT sell_order.buy
  562. AND buyer.rowid = buy_order.ownership_id
  563. AND seller.rowid = sell_order.ownership_id
  564. AND buyer.ownable_id = ?
  565. AND seller.ownable_id = ?
  566. AND buy_order."limit" IS NOT NULL
  567. AND sell_order."limit" IS NOT NULL
  568. AND sell_order."limit" <= buy_order."limit"
  569. AND NOT sell_order.stop_loss
  570. AND NOT buy_order.stop_loss
  571. ORDER BY buy_order."limit" DESC,
  572. sell_order."limit" ASC,
  573. buy_order.rowid ASC,
  574. sell_order.rowid ASC
  575. LIMIT 1)
  576. LIMIT 1
  577. ''', tuple(ownable_id for _ in range(8)))
  578. matching_orders = current_cursor.fetchone()
  579. # return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  580. # ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  581. # user_id,user_id,rowid,rowid)
  582. if not matching_orders:
  583. break
  584. _, buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, _, \
  585. _, sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, _, \
  586. buyer_id, seller_id, buy_order_id, sell_order_id \
  587. = matching_orders
  588. if buy_limit is None and sell_limit is None:
  589. price = current_value(ownable_id)
  590. elif buy_limit is None:
  591. price = sell_limit
  592. elif sell_limit is None:
  593. price = buy_limit
  594. else: # both not NULL
  595. # the price of the older order is used, just like in the real exchange
  596. if buy_order_id < sell_order_id:
  597. price = buy_limit
  598. else:
  599. price = sell_limit
  600. buyer_money = user_money(buyer_id)
  601. def _my_division(x, y):
  602. try:
  603. return floor(x / y)
  604. except ZeroDivisionError:
  605. return float('Inf')
  606. amount = min(buy_order_amount - buy_executed_amount,
  607. sell_order_amount - sell_executed_amount,
  608. _my_division(buyer_money, price))
  609. if amount < 0:
  610. amount = 0
  611. if amount == 0: # probable because buyer has not enough money
  612. delete_order(buy_order_id, 'Unable to pay')
  613. continue
  614. buy_order_finished = (buy_order_amount - buy_executed_amount - amount <= 0) or (
  615. buyer_money - amount * price < price)
  616. sell_order_finished = (sell_order_amount - sell_executed_amount - amount <= 0)
  617. if price < 0 or amount <= 0: # price of 0 is possible though unlikely
  618. return AssertionError()
  619. # actually execute the order, but the bank does not send or receive anything
  620. send_ownable(buyer_id, seller_id, currency_id(), price * amount)
  621. send_ownable(seller_id, buyer_id, ownable_id, amount)
  622. # update order execution state
  623. execute('''
  624. UPDATE orders
  625. SET executed_amount = executed_amount + ?
  626. WHERE rowid = ?
  627. OR rowid = ?
  628. ''', (amount, buy_order_id, sell_order_id))
  629. if buy_order_finished:
  630. delete_order(buy_order_id, 'Executed')
  631. orders_traded = True
  632. if sell_order_finished:
  633. delete_order(sell_order_id, 'Executed')
  634. orders_traded = True
  635. if seller_id != buyer_id: # prevent showing self-transactions
  636. execute('''
  637. INSERT INTO transactions
  638. (price, ownable_id, amount, buyer_id, seller_id)
  639. VALUES(?, ?, ?, ?, ?)
  640. ''', (price, ownable_id, amount, buyer_id, seller_id))
  641. # trigger stop-loss orders
  642. if buyer_id != seller_id:
  643. execute('''
  644. UPDATE orders
  645. SET stop_loss = NULL,
  646. "limit" = NULL
  647. WHERE stop_loss IS NOT NULL
  648. AND stop_loss
  649. AND ? IN (SELECT ownable_id FROM ownership WHERE rowid = ownership_id)
  650. AND ((buy AND "limit" < ?) OR (NOT buy AND "limit" > ?))
  651. ''', (ownable_id, price, price,))
  652. def ownable_id_by_ownership_id(ownership_id):
  653. execute('''
  654. SELECT ownable_id
  655. FROM ownership
  656. WHERE rowid = ?
  657. ''', (ownership_id,))
  658. return current_cursor.fetchone()[0]
  659. def ownable_name_by_id(ownable_id):
  660. execute('''
  661. SELECT name
  662. FROM ownables
  663. WHERE rowid = ?
  664. ''', (ownable_id,))
  665. return current_cursor.fetchone()[0]
  666. def bank_order(buy, ownable_id, limit, amount, expiry, ioc):
  667. if not limit:
  668. raise AssertionError('The bank does not give away anything.')
  669. place_order(buy,
  670. get_ownership_id(ownable_id, bank_id()),
  671. limit,
  672. False,
  673. amount,
  674. expiry,
  675. ioc=ioc)
  676. ownable_name = ownable_name_by_id(ownable_id)
  677. new_news('External investors are selling ' + ownable_name + ' atm')
  678. def current_db_time(): # might differ from datetime.datetime.now() for time zone reasons
  679. connect()
  680. execute('''
  681. SELECT datetime('now')
  682. ''')
  683. return current_cursor.fetchone()[0]
  684. def current_db_timestamp():
  685. connect()
  686. execute('''
  687. SELECT CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)
  688. ''')
  689. return int(current_cursor.fetchone()[0])
  690. def place_order(buy, ownership_id, limit, stop_loss, amount, expiry, ioc: bool):
  691. if isinstance(expiry, datetime):
  692. expiry = expiry.timestamp()
  693. execute(''' INSERT INTO orders
  694. (buy, ownership_id, "limit", stop_loss, ordered_amount, expiry_dt, ioc)
  695. VALUES (?, ?, ?, ?, ?, ?, ?)
  696. ''', (buy, ownership_id, limit, stop_loss, amount, expiry, ioc))
  697. execute_orders(ownable_id_by_ownership_id(ownership_id))
  698. execute('''DELETE FROM orders WHERE ioc''')
  699. return True
  700. def trades_on(ownable_id, limit):
  701. execute('''
  702. SELECT datetime(dt,'localtime'), amount, price
  703. FROM transactions
  704. WHERE ownable_id = ?
  705. ORDER BY rowid DESC -- equivalent to order by dt
  706. LIMIT ?
  707. ''', (ownable_id, limit,))
  708. return current_cursor.fetchall()
  709. def trades(user_id, limit):
  710. execute('''
  711. SELECT
  712. (CASE WHEN seller_id = ? THEN 'Sell' ELSE 'Buy' END),
  713. (SELECT name FROM ownables WHERE rowid = transactions.ownable_id),
  714. amount,
  715. price,
  716. datetime(dt,'localtime')
  717. FROM transactions
  718. WHERE seller_id = ? OR buyer_id = ?
  719. ORDER BY rowid DESC -- equivalent to order by dt
  720. LIMIT ?
  721. ''', (user_id, user_id, user_id, limit,))
  722. return current_cursor.fetchall()
  723. def drop_expired_orders():
  724. execute('''
  725. SELECT rowid, ownership_id, * FROM orders
  726. WHERE expiry_dt < CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)
  727. ''')
  728. data = current_cursor.fetchall()
  729. for order in data:
  730. order_id = order[0]
  731. delete_order(order_id, 'Expired')
  732. return data
  733. def generate_keys(count=1):
  734. # source https://stackoverflow.com/questions/17049308/python-3-3-serial-key-generator-list-problems
  735. for i in range(count):
  736. key = '-'.join(random_chars(5) for _ in range(5))
  737. save_key(key)
  738. print(key)
  739. def user_has_order_with_id(session_id, order_id):
  740. execute('''
  741. SELECT orders.rowid
  742. FROM orders, ownership, sessions
  743. WHERE orders.rowid = ?
  744. AND sessions.session_id = ?
  745. AND sessions.user_id = ownership.user_id
  746. AND ownership.rowid = orders.ownership_id
  747. ''', (order_id, session_id,))
  748. if current_cursor.fetchone():
  749. return True
  750. else:
  751. return False
  752. def leaderboard():
  753. execute('''
  754. SELECT *
  755. FROM ( -- one score for each user
  756. SELECT
  757. username,
  758. SUM(CASE -- sum score for each of the users ownables
  759. WHEN ownership.ownable_id = ? THEN ownership.amount
  760. ELSE ownership.amount * (SELECT price
  761. FROM transactions
  762. WHERE ownable_id = ownership.ownable_id
  763. ORDER BY rowid DESC -- equivalent to ordering by dt
  764. LIMIT 1)
  765. END
  766. ) score
  767. FROM users, ownership
  768. WHERE ownership.user_id = users.rowid
  769. AND users.username != 'bank'
  770. GROUP BY users.rowid
  771. ) AS scores
  772. ORDER BY score DESC
  773. LIMIT 50
  774. ''', (currency_id(),))
  775. return current_cursor.fetchall()
  776. def user_wealth(user_id):
  777. execute('''
  778. SELECT (
  779. SELECT COALESCE(SUM(
  780. CASE -- sum score for each of the users ownables
  781. WHEN ownership.ownable_id = ? THEN ownership.amount
  782. ELSE ownership.amount * (SELECT price
  783. FROM transactions
  784. WHERE ownable_id = ownership.ownable_id
  785. ORDER BY rowid DESC -- equivalent to ordering by dt
  786. LIMIT 1)
  787. END
  788. ), 0)
  789. FROM ownership
  790. WHERE ownership.user_id = ?)
  791. -
  792. ( SELECT COALESCE(SUM(
  793. amount
  794. ), 0)
  795. FROM loans
  796. WHERE loans.user_id = ?)
  797. ''', (currency_id(), user_id, user_id,))
  798. return current_cursor.fetchone()[0]
  799. def change_password(session_id, password, salt):
  800. execute('''
  801. UPDATE users
  802. SET password = ?, salt= ?
  803. WHERE rowid = (SELECT user_id FROM sessions WHERE sessions.session_id = ?)
  804. ''', (password, salt, session_id,))
  805. def sign_out_user(session_id):
  806. execute('''
  807. DELETE FROM sessions
  808. WHERE user_id = (SELECT user_id FROM sessions s2 WHERE s2.session_id = ?)
  809. ''', (session_id,))
  810. def delete_user(user_id):
  811. execute('''
  812. DELETE FROM sessions
  813. WHERE user_id = ?
  814. ''', (user_id,))
  815. execute('''
  816. DELETE FROM orders
  817. WHERE ownership_id IN (
  818. SELECT rowid FROM ownership WHERE user_id = ?)
  819. ''', (user_id,))
  820. execute('''
  821. DELETE FROM ownership
  822. WHERE user_id = ?
  823. ''', (user_id,))
  824. execute('''
  825. DELETE FROM keys
  826. WHERE used_by_user_id = ?
  827. ''', (user_id,))
  828. execute('''
  829. INSERT INTO news(title)
  830. VALUES ((SELECT username FROM users WHERE rowid = ?) || ' retired.')
  831. ''', (user_id,))
  832. execute('''
  833. DELETE FROM users
  834. WHERE rowid = ?
  835. ''', (user_id,))
  836. def delete_ownable(ownable_id):
  837. execute('''
  838. DELETE FROM transactions
  839. WHERE ownable_id = ?
  840. ''', (ownable_id,))
  841. execute('''
  842. DELETE FROM orders
  843. WHERE ownership_id IN (
  844. SELECT rowid FROM ownership WHERE ownable_id = ?)
  845. ''', (ownable_id,))
  846. execute('''
  847. DELETE FROM order_history
  848. WHERE ownership_id IN (
  849. SELECT rowid FROM ownership WHERE ownable_id = ?)
  850. ''', (ownable_id,))
  851. # only delete empty ownerships
  852. execute('''
  853. DELETE FROM ownership
  854. WHERE ownable_id = ?
  855. AND amount = 0
  856. ''', (ownable_id,))
  857. execute('''
  858. INSERT INTO news(title)
  859. VALUES ((SELECT name FROM ownables WHERE rowid = ?) || ' can not be traded any more.')
  860. ''', (ownable_id,))
  861. execute('''
  862. DELETE FROM ownables
  863. WHERE rowid = ?
  864. ''', (ownable_id,))
  865. def hash_all_users_passwords():
  866. execute('''
  867. SELECT rowid, password, salt
  868. FROM users
  869. ''')
  870. users = current_cursor.fetchall()
  871. for user_id, pw, salt in users:
  872. valid_hash = True
  873. try:
  874. sha256_crypt.verify('password' + salt, pw)
  875. except ValueError:
  876. valid_hash = False
  877. if valid_hash:
  878. raise AssertionError('There is already a hashed password in the database! Be careful what you are doing!')
  879. pw = sha256_crypt.encrypt(pw + salt)
  880. execute('''
  881. UPDATE users
  882. SET password = ?
  883. WHERE rowid = ?
  884. ''', (pw, user_id,))
  885. def new_news(message):
  886. execute('''
  887. INSERT INTO news(title)
  888. VALUES (?)
  889. ''', (message,))
  890. def abs_spread(ownable_id):
  891. execute('''
  892. SELECT
  893. (SELECT MAX("limit")
  894. FROM orders, ownership
  895. WHERE ownership.rowid = orders.ownership_id
  896. AND ownership.ownable_id = ?
  897. AND buy
  898. AND NOT stop_loss) AS bid,
  899. (SELECT MIN("limit")
  900. FROM orders, ownership
  901. WHERE ownership.rowid = orders.ownership_id
  902. AND ownership.ownable_id = ?
  903. AND NOT buy
  904. AND NOT stop_loss) AS ask
  905. ''', (ownable_id, ownable_id,))
  906. return current_cursor.fetchone()
  907. def ownables():
  908. execute('''
  909. SELECT name, course,
  910. (SELECT SUM(amount)
  911. FROM ownership
  912. WHERE ownership.ownable_id = ownables_with_course.rowid) market_size
  913. FROM (SELECT
  914. name, ownables.rowid,
  915. CASE WHEN ownables.rowid = ?
  916. THEN 1
  917. ELSE (SELECT price
  918. FROM transactions
  919. WHERE ownable_id = ownables.rowid
  920. ORDER BY rowid DESC -- equivalent to ordering by dt
  921. LIMIT 1) END course
  922. FROM ownables) ownables_with_course
  923. ''', (currency_id(),))
  924. data = current_cursor.fetchall()
  925. for idx in range(len(data)):
  926. # compute market cap
  927. row = data[idx]
  928. if row[1] is None:
  929. market_cap = None
  930. elif row[2] is None:
  931. market_cap = None
  932. else:
  933. market_cap = row[1] * row[2]
  934. data[idx] = (row[0], row[1], market_cap)
  935. return data
  936. def reset_bank():
  937. execute('''
  938. DELETE FROM ownership
  939. WHERE user_id = ?
  940. ''', (bank_id(),))
  941. def cleanup():
  942. global connections
  943. global current_connection
  944. global current_cursor
  945. global current_db_name
  946. global current_user_id
  947. for name in connections:
  948. connections[name].rollback()
  949. connections[name].close()
  950. connections = []
  951. current_connection = None
  952. current_cursor = None
  953. current_db_name = None
  954. current_user_id = None
  955. def ownable_ids():
  956. execute('''
  957. SELECT rowid FROM ownables
  958. ''')
  959. return [ownable_id[0] for ownable_id in current_cursor.fetchall()]
  960. def get_old_orders(user_id, include_executed, include_canceled, limit):
  961. execute('''
  962. SELECT
  963. (CASE WHEN order_history.buy THEN 'Buy' ELSE 'Sell' END),
  964. ownables.name,
  965. (order_history.ordered_amount - order_history.executed_amount) || '/' || order_history.ordered_amount,
  966. order_history."limit",
  967. order_history.expiry_dt,
  968. order_history.order_id,
  969. order_history.status
  970. FROM order_history, ownership, ownables
  971. WHERE ownership.user_id = ?
  972. AND ownership.rowid = order_history.ownership_id
  973. AND ownables.rowid = ownership.ownable_id
  974. AND (
  975. (order_history.status = 'Executed' AND ?)
  976. OR
  977. ((order_history.status = 'Expired' OR order_history.status = 'Canceled') AND ?)
  978. )
  979. ORDER BY order_history.rowid DESC -- equivalent to ordering by creation time
  980. LIMIT ?
  981. ''', (user_id, include_executed, include_canceled, limit))
  982. return current_cursor.fetchall()
  983. def user_has_banking_license(user_id):
  984. execute('''
  985. SELECT EXISTS (SELECT * FROM banks WHERE user_id = ?)
  986. ''', (user_id,))
  987. return current_cursor.fetchone()[0]
  988. def global_control_value(value_name):
  989. execute('''
  990. SELECT value
  991. FROM global_control_values
  992. WHERE value_name = ?
  993. AND dt = (SELECT MAX(dt) FROM global_control_values WHERE value_name = ?)
  994. ''', (value_name, value_name,))
  995. return current_cursor.fetchone()[0]
  996. def global_control_values():
  997. execute('''
  998. SELECT value_name, value
  999. FROM global_control_values v1
  1000. WHERE dt IN (SELECT MAX(dt) FROM global_control_values v2 GROUP BY v2.value_name)
  1001. ''')
  1002. return {
  1003. row[0]: row[1] for row in current_cursor.fetchall()
  1004. }
  1005. def assign_banking_licence(user_id):
  1006. execute('''
  1007. INSERT INTO banks(user_id)
  1008. VALUES (?)
  1009. ''', (user_id,))
  1010. def pay_loan_interest():
  1011. current_dt = execute("SELECT CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)").fetchone()[0]
  1012. sec_per_year = 3600 * 24 * 365
  1013. interests = execute('''
  1014. SELECT
  1015. SUM(amount * (POWER(1 + interest_rate,
  1016. (CAST(? AS FLOAT) - last_interest_pay_dt) / ?) - 1)
  1017. ) AS interest_since_last_pay,
  1018. user_id
  1019. FROM loans
  1020. WHERE ? - last_interest_pay_dt > ?
  1021. GROUP BY user_id
  1022. ''', (current_dt, sec_per_year, current_dt, MIN_INTEREST_INTERVAL)).fetchall()
  1023. executemany(f'''
  1024. UPDATE ownership
  1025. SET amount = amount - ?
  1026. WHERE ownable_id = {currency_id()}
  1027. AND user_id = ?
  1028. ''', interests)
  1029. # noinspection SqlWithoutWhere
  1030. execute('''
  1031. UPDATE loans
  1032. SET last_interest_pay_dt = ?
  1033. WHERE ? - last_interest_pay_dt > ?
  1034. ''', (current_dt, current_dt, MIN_INTEREST_INTERVAL,))
  1035. def loan_recipient_id(loan_id):
  1036. execute('''
  1037. SELECT user_id
  1038. FROM loans
  1039. WHERE rowid = ?
  1040. ''', (loan_id,))
  1041. return current_cursor.fetchone()[0]
  1042. def loan_remaining_amount(loan_id):
  1043. execute('''
  1044. SELECT amount
  1045. FROM loans
  1046. WHERE rowid = ?
  1047. ''', (loan_id,))
  1048. return current_cursor.fetchone()[0]
  1049. def repay_loan(loan_id, amount, known_user_id=None):
  1050. if known_user_id is None:
  1051. user_id = loan_recipient_id(loan_id)
  1052. else:
  1053. user_id = known_user_id
  1054. send_ownable(user_id, bank_id(), currency_id(), amount)
  1055. execute('''
  1056. UPDATE loans
  1057. SET amount = amount - ?
  1058. WHERE rowid = ?
  1059. ''', (amount, loan_id,))
  1060. if loan_remaining_amount(loan_id) == 0:
  1061. execute('''
  1062. DELETE FROM loans
  1063. WHERE rowid = ?
  1064. ''', (loan_id,))
  1065. def take_out_personal_loan(user_id, amount):
  1066. execute('''
  1067. INSERT INTO loans(user_id, total_amount, amount, interest_rate)
  1068. VALUES (?, ?, ?, ?)
  1069. ''', (user_id, amount, amount, global_control_value('personal_loan_interest_rate')))
  1070. send_ownable(bank_id(), user_id, currency_id(), amount)
  1071. def loan_id_exists(loan_id):
  1072. execute('''
  1073. SELECT EXISTS (SELECT * FROM loans WHERE rowid = ?)
  1074. ''', (loan_id,))
  1075. return current_cursor.fetchone()[0]
  1076. def main_refinancing_operations():
  1077. ... # TODO
  1078. def issue_bond(user_id, ownable_name, coupon):
  1079. execute('''
  1080. INSERT INTO ownables(name)
  1081. VALUES (?)
  1082. ''', (ownable_name,))
  1083. execute('''
  1084. INSERT INTO bonds(issuer_id, ownable_id, coupon)
  1085. VALUES (?, (SELECT MAX(rowid) FROM ownables), ?)
  1086. ''', (user_id, coupon))