model.py 40 KB

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