model.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  1. import random
  2. import re
  3. import sqlite3 as db
  4. import sys
  5. import uuid
  6. from datetime import timedelta, datetime
  7. from math import floor
  8. import db_setup
  9. from game import CURRENCY_NAME
  10. from util import debug, random_chars
  11. # connection: db.Connection = None
  12. # cursor: db.Cursor = None
  13. connection = None # no type annotations in python 3.5
  14. cursor = None
  15. db_name = None
  16. def query_save_name():
  17. global db_name
  18. if debug:
  19. db_name = 'test.db'
  20. return
  21. while True:
  22. save_name = input('Name of the savegame: ')
  23. if re.match(r"[A-Za-z0-9.-]{0,50}", save_name):
  24. db_name = save_name + '.db'
  25. return
  26. else:
  27. print('Must match "[A-Za-z0-9.-]{0,50}"')
  28. def connect(reconnect=False):
  29. global connection
  30. global cursor
  31. global db_name
  32. if reconnect:
  33. connection.commit()
  34. connection.close()
  35. cursor = None
  36. connection = None
  37. db_name = None
  38. if connection is None or cursor is None:
  39. query_save_name()
  40. try:
  41. connection = db.connect(db_name)
  42. # connection.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')
  43. cursor = connection.cursor()
  44. except db.Error as e:
  45. print("Database error %s:" % e.args[0])
  46. sys.exit(1)
  47. # finally:
  48. # if con is not None:
  49. # con.close()
  50. def setup():
  51. connect()
  52. db_setup.setup(cursor)
  53. connection.commit()
  54. def used_key_count():
  55. connect()
  56. cursor.execute('''
  57. SELECT COUNT(*)
  58. FROM keys
  59. WHERE used_by_user_id IS NOT NULL
  60. ''')
  61. return cursor.fetchone()[0]
  62. def login(username, password):
  63. connect()
  64. # do not allow login as bank
  65. if password == '':
  66. return None
  67. cursor.execute('''
  68. SELECT rowid
  69. FROM users
  70. WHERE username = ?
  71. AND password = ?
  72. ''', (username, password))
  73. user_id = cursor.fetchone()
  74. if user_id:
  75. return new_session(user_id)
  76. else:
  77. return None
  78. def register(username, password, game_key):
  79. connect()
  80. if username == '':
  81. return False
  82. if password == '':
  83. return False
  84. cursor.execute('''
  85. INSERT INTO users
  86. (username, password)
  87. VALUES (? , ?)
  88. ''', (username, password))
  89. own(get_user_id_by_name(username), CURRENCY_NAME)
  90. if game_key != '':
  91. if valid_key(game_key):
  92. activate_key(game_key, get_user_id_by_name(username))
  93. return True
  94. def own(user_id, ownable_name):
  95. if not isinstance(ownable_name, str):
  96. return AssertionError('A name must be a string.')
  97. cursor.execute('''
  98. WITH one_ownable_id AS (SELECT rowid FROM ownables WHERE name = ?),
  99. one_user_id AS (SELECT ?)
  100. INSERT INTO ownership (user_id, ownable_id)
  101. SELECT *
  102. FROM one_user_id, one_ownable_id
  103. WHERE NOT EXISTS (
  104. SELECT * FROM ownership
  105. WHERE ownership.user_id IN one_user_id
  106. AND ownership.ownable_id IN one_ownable_id
  107. )
  108. ''', (ownable_name, user_id,))
  109. def send_ownable(from_user_id, to_user_id, ownable_name, amount):
  110. connect()
  111. if amount < 0:
  112. return False
  113. if from_user_id != bank_id():
  114. cursor.execute('''
  115. UPDATE ownership
  116. SET amount = amount - ?
  117. WHERE user_id = ?
  118. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  119. ''', (amount, from_user_id, ownable_name,))
  120. cursor.execute('''
  121. UPDATE ownership
  122. SET amount = amount + ?
  123. WHERE user_id = ?
  124. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  125. ''', (amount, to_user_id, ownable_name))
  126. return True
  127. def valid_key(key):
  128. connect()
  129. cursor.execute('''
  130. SELECT key
  131. FROM keys
  132. WHERE used_by_user_id IS NULL
  133. AND key = ?
  134. ''', (key,))
  135. if cursor.fetchone():
  136. return True
  137. else:
  138. return False
  139. def new_session(user_id):
  140. connect()
  141. session_id = str(uuid.uuid4())
  142. cursor.execute('''
  143. INSERT INTO SESSIONS
  144. (user_id, session_id)
  145. VALUES (? , ?)
  146. ''', (user_id[0], session_id))
  147. return session_id
  148. def save_key(key):
  149. connect()
  150. cursor.execute('''
  151. INSERT INTO keys
  152. (key)
  153. VALUES (?)
  154. ''', (key,))
  155. def drop_old_sessions():
  156. connect()
  157. cursor.execute('''
  158. DELETE FROM sessions s1
  159. WHERE
  160. (SELECT COUNT(*) as newer
  161. FROM sessions s2
  162. WHERE s1.user_id = s2.user_id
  163. AND s1.rowid < s2.rowid) >= 10
  164. ''')
  165. def user_exists(username):
  166. connect()
  167. cursor.execute('''
  168. SELECT rowid
  169. FROM users
  170. WHERE username = ?
  171. ''', (username,))
  172. if cursor.fetchone():
  173. return True
  174. else:
  175. return False
  176. def unused_keys():
  177. connect()
  178. cursor.execute('''
  179. SELECT key
  180. FROM keys
  181. WHERE used_by_user_id IS NULL
  182. ''')
  183. return [str(key[0]).strip().upper() for key in cursor.fetchall()]
  184. def get_user_id_by_session_id(session_id):
  185. connect()
  186. cursor.execute('''
  187. SELECT users.rowid
  188. FROM sessions, users
  189. WHERE sessions.session_id = ?
  190. AND users.rowid = sessions.user_id
  191. ''', (session_id,))
  192. ids = cursor.fetchone()
  193. if not ids:
  194. return False
  195. return ids[0]
  196. def get_user_id_by_name(username):
  197. connect()
  198. cursor.execute('''
  199. SELECT users.rowid
  200. FROM users
  201. WHERE username = ?
  202. ''', (username,))
  203. return cursor.fetchone()[0]
  204. def get_user_ownership(user_id):
  205. connect()
  206. cursor.execute('''
  207. SELECT ownables.name, ownership.amount
  208. FROM ownership, ownables
  209. WHERE user_id = ?
  210. AND ownership.ownable_id = ownables.rowid
  211. ''', (user_id,))
  212. return cursor.fetchall()
  213. def activate_key(key, user_id):
  214. connect()
  215. cursor.execute('''
  216. UPDATE keys
  217. SET used_by_user_id = ?
  218. WHERE used_by_user_id IS NULL
  219. AND key = ?
  220. ''', (user_id, key,))
  221. send_ownable(bank_id(), user_id, CURRENCY_NAME, 1000)
  222. def bank_id():
  223. connect()
  224. cursor.execute('''
  225. SELECT users.rowid
  226. FROM users
  227. WHERE username = 'bank'
  228. ''')
  229. return cursor.fetchone()[0]
  230. def valid_session_id(session_id):
  231. connect()
  232. cursor.execute('''
  233. SELECT rowid
  234. FROM sessions
  235. WHERE session_id = ?
  236. ''', (session_id,))
  237. if cursor.fetchone():
  238. return True
  239. else:
  240. return False
  241. def get_user_orders(user_id):
  242. connect()
  243. cursor.execute('''
  244. SELECT
  245. CASE
  246. WHEN orders.buy THEN 'Buy'
  247. ELSE 'Sell'
  248. END,
  249. ownables.name,
  250. orders.ordered_amount - orders.executed_amount,
  251. orders."limit",
  252. CASE
  253. WHEN orders."limit" IS NULL THEN NULL
  254. WHEN orders.stop_loss THEN 'Yes'
  255. ELSE 'No'
  256. END,
  257. orders.ordered_amount,
  258. datetime(orders.expiry_dt),
  259. orders.rowid
  260. FROM orders, ownables, ownership
  261. WHERE ownership.user_id = ?
  262. AND ownership.ownable_id = ownables.rowid
  263. AND orders.ownership_id = ownership.rowid
  264. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  265. ''', (user_id,))
  266. return cursor.fetchall()
  267. def get_ownable_orders(ownable_id):
  268. connect()
  269. cursor.execute('''
  270. SELECT
  271. CASE
  272. WHEN orders.buy THEN 'Buy'
  273. ELSE 'Sell'
  274. END,
  275. ownables.name,
  276. orders.ordered_amount - orders.executed_amount,
  277. orders."limit",
  278. CASE
  279. WHEN orders."limit" IS NULL THEN NULL
  280. WHEN orders.stop_loss THEN 'Yes'
  281. ELSE 'No'
  282. END,
  283. datetime(orders.expiry_dt),
  284. orders.rowid
  285. FROM orders, ownables, ownership
  286. WHERE ownership.ownable_id = ?
  287. AND ownership.ownable_id = ownables.rowid
  288. AND orders.ownership_id = ownership.rowid
  289. ORDER BY ownables.name ASC, orders.stop_loss ASC, orders.buy DESC, orders."limit" ASC
  290. ''', (ownable_id,))
  291. return cursor.fetchall()
  292. def sell_ordered_amount(user_id, ownable_id):
  293. connect()
  294. # if ownable_id == currency_id():
  295. # return 0
  296. cursor.execute('''
  297. SELECT COALESCE(SUM(orders.ordered_amount - orders.executed_amount),0)
  298. FROM orders, ownership
  299. WHERE ownership.rowid = orders.ownership_id
  300. AND ownership.user_id = ?
  301. AND ownership.ownable_id = ?
  302. AND NOT orders.buy
  303. ''', (user_id, ownable_id))
  304. return cursor.fetchone()[0]
  305. def user_owns_at_least(amount, user_id, ownable_id):
  306. connect()
  307. if not isinstance(amount, float) and not isinstance(amount, int):
  308. # comparison of float with strings does not work so well in sql
  309. raise AssertionError()
  310. cursor.execute('''
  311. SELECT rowid
  312. FROM ownership
  313. WHERE user_id = ?
  314. AND ownable_id = ?
  315. AND amount - ? >= ?
  316. ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
  317. if cursor.fetchone():
  318. return True
  319. else:
  320. return False
  321. def news():
  322. connect()
  323. cursor.execute('''
  324. SELECT *
  325. FROM news
  326. ORDER BY dt DESC
  327. LIMIT 20
  328. ''')
  329. return cursor.fetchall()
  330. def ownable_name_exists(name):
  331. connect()
  332. cursor.execute('''
  333. SELECT rowid
  334. FROM ownables
  335. WHERE name = ?
  336. ''', (name,))
  337. if cursor.fetchone():
  338. return True
  339. else:
  340. return False
  341. def new_stock(timeout=60, name=None):
  342. connect()
  343. while name is None:
  344. name = random_chars(6)
  345. if ownable_name_exists(name):
  346. name = None
  347. cursor.execute('''
  348. INSERT INTO ownables(name)
  349. VALUES (?)
  350. ''', (name,))
  351. cursor.execute('''
  352. INSERT INTO news(title)
  353. VALUES (?)
  354. ''', ('A new stock can now be bought: ' + name,))
  355. if random.getrandbits(1):
  356. cursor.execute('''
  357. INSERT INTO news(title)
  358. VALUES (?)
  359. ''', ('Experts expect the price of ' + name + ' to fall',))
  360. else:
  361. cursor.execute('''
  362. INSERT INTO news(title)
  363. VALUES (?)
  364. ''', ('Experts expect the price of ' + name + ' to rise',))
  365. amount = random.randrange(100, 10000)
  366. price = random.randrange(10000, 20000) / amount
  367. ownable_id = ownable_id_by_name(name)
  368. own(bank_id(), name)
  369. bank_order(False,
  370. ownable_id,
  371. price,
  372. amount,
  373. timeout)
  374. return name
  375. def new_stocks(timeout=60, count=1):
  376. return [new_stock(timeout=timeout) for _ in range(count)]
  377. def ownable_id_by_name(ownable_name):
  378. connect()
  379. cursor.execute('''
  380. SELECT rowid
  381. FROM ownables
  382. WHERE name = ?
  383. ''', (ownable_name,))
  384. return cursor.fetchone()[0]
  385. def get_ownership_id(ownable_id, user_id):
  386. connect()
  387. cursor.execute('''
  388. SELECT rowid
  389. FROM ownership
  390. WHERE ownable_id = ?
  391. AND user_id = ?
  392. ''', (ownable_id, user_id,))
  393. return cursor.fetchone()[0]
  394. def currency_id():
  395. connect()
  396. cursor.execute('''
  397. SELECT rowid
  398. FROM ownables
  399. WHERE name = ?
  400. ''', (CURRENCY_NAME,))
  401. return cursor.fetchone()[0]
  402. def user_money(user_id):
  403. connect()
  404. cursor.execute('''
  405. SELECT amount
  406. FROM ownership
  407. WHERE user_id = ?
  408. AND ownable_id = ?
  409. ''', (user_id, currency_id()))
  410. return cursor.fetchone()[0]
  411. def delete_order(order_id):
  412. connect()
  413. cursor.execute('''
  414. DELETE FROM orders
  415. WHERE rowid = ?
  416. ''', (order_id,))
  417. def execute_orders(ownable_id):
  418. connect()
  419. while True:
  420. # find order to execute
  421. cursor.execute('''
  422. SELECT buy_order.*, sell_order.*, buyer.user_id, seller.user_id, buy_order.rowid, sell_order.rowid
  423. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  424. WHERE buy_order.buy AND NOT sell_order.buy
  425. AND buyer.rowid = buy_order.ownership_id
  426. AND seller.rowid = sell_order.ownership_id
  427. AND buyer.ownable_id = ?
  428. AND seller.ownable_id = ?
  429. AND (buy_order."limit" IS NULL
  430. OR sell_order."limit" IS NULL
  431. OR (sell_order."limit" <= buy_order."limit"
  432. AND NOT sell_order.stop_loss
  433. AND NOT buy_order.stop_loss))
  434. ORDER BY CASE WHEN sell_order."limit" IS NULL THEN 0 ELSE 1 END ASC,
  435. CASE WHEN buy_order."limit" IS NULL THEN 0 ELSE 1 END ASC,
  436. buy_order."limit" DESC,
  437. sell_order."limit" ASC,
  438. buy_order.ordered_amount - buy_order.executed_amount DESC,
  439. sell_order.ordered_amount - sell_order.executed_amount DESC
  440. LIMIT 1
  441. ''', (ownable_id, ownable_id,))
  442. matching_orders = cursor.fetchone()
  443. # return type: (ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  444. # ownership_id,buy,limit,stop_loss,ordered_amount,executed_amount,expiry_dt,
  445. # user_id,user_id,rowid,rowid)
  446. if not matching_orders:
  447. break
  448. # TODO continue and delete order if buyer has not enough money
  449. buy_ownership_id, _, buy_limit, _, buy_order_amount, buy_executed_amount, buy_expiry_dt, \
  450. sell_ownership_id, _, sell_limit, _, sell_order_amount, sell_executed_amount, sell_expiry_dt, \
  451. buyer_id, seller_id, buy_order_id, sell_order_id \
  452. = matching_orders
  453. if buy_limit is None and sell_limit is None:
  454. raise AssertionError() # TODO find a solution
  455. elif buy_limit is None:
  456. price = sell_limit
  457. elif sell_limit is None:
  458. price = buy_limit
  459. else: # both not NULL
  460. price = (float(sell_limit) + float(buy_limit)) / 2
  461. if price == 0:
  462. raise AssertionError()
  463. buyer_money = user_money(buyer_id)
  464. amount = min(buy_order_amount - buy_executed_amount,
  465. sell_order_amount - sell_executed_amount,
  466. floor(buyer_money / price))
  467. if amount == 0: # probable because buyer has not enough money
  468. delete_order(buy_order_id)
  469. continue
  470. buy_order_finished = (buy_order_amount - buy_executed_amount - amount <= 0) or (
  471. buyer_money - amount * price < price)
  472. sell_order_finished = (sell_order_amount - sell_executed_amount - amount <= 0)
  473. if price < 0 or amount <= 0:
  474. return AssertionError()
  475. # actually execute the order, but the bank does not send or receive anything
  476. if buyer_id != bank_id(): # buyer pays
  477. cursor.execute('''
  478. UPDATE ownership
  479. SET amount = amount - ?
  480. WHERE user_id = ?
  481. AND ownable_id = ?
  482. ''', (price * amount, buyer_id, currency_id()))
  483. if seller_id != bank_id(): # seller pays
  484. cursor.execute('''
  485. UPDATE ownership
  486. SET amount = amount - ?
  487. WHERE rowid = ?
  488. ''', (amount, sell_ownership_id))
  489. if buyer_id != bank_id(): # buyer receives
  490. cursor.execute('''
  491. UPDATE ownership
  492. SET amount = amount + ?
  493. WHERE rowid = ?
  494. ''', (amount, buy_ownership_id))
  495. if seller_id != bank_id(): # seller receives
  496. cursor.execute('''
  497. UPDATE ownership
  498. SET amount = amount + ?
  499. WHERE user_id = ?
  500. AND ownable_id = ?
  501. ''', (amount, seller_id, currency_id()))
  502. # update order execution state
  503. cursor.execute('''
  504. UPDATE orders
  505. SET executed_amount = executed_amount + ?
  506. WHERE rowid = ?
  507. OR rowid = ?
  508. ''', (amount, buy_order_id, sell_order_id))
  509. if buy_order_finished:
  510. delete_order(buy_order_id)
  511. if sell_order_finished:
  512. delete_order(sell_order_id)
  513. if seller_id != buyer_id: # prevent showing self-transactions
  514. cursor.execute('''
  515. INSERT INTO transactions
  516. (price, ownable_id, amount)
  517. VALUES(?, ?, ?)
  518. ''', (price, ownable_id, amount,))
  519. # trigger stop loss orders
  520. if buyer_id != seller_id:
  521. cursor.execute('''
  522. UPDATE orders
  523. SET stop_loss = NULL,
  524. "limit" = NULL
  525. WHERE stop_loss IS NOT NULL
  526. AND stop_loss
  527. AND ? IN (SELECT ownable_id FROM ownership WHERE rowid = ownership_id)
  528. AND ((buy AND "limit" < ?) OR (NOT buy AND "limit" > ?))
  529. ''', (ownable_id, price, price,))
  530. def ownable_id_by_ownership_id(ownership_id):
  531. connect()
  532. cursor.execute('''
  533. SELECT ownable_id
  534. FROM ownership
  535. WHERE rowid = ?
  536. ''', (ownership_id,))
  537. return cursor.fetchone()[0]
  538. def ownable_name_by_id(ownable_id):
  539. connect()
  540. cursor.execute('''
  541. SELECT name
  542. FROM ownables
  543. WHERE rowid = ?
  544. ''', (ownable_id,))
  545. return cursor.fetchone()[0]
  546. def bank_order(buy, ownable_id, limit, amount, time_until_expiration):
  547. if not limit:
  548. raise AssertionError('The bank does not give away anything.')
  549. place_order(buy,
  550. get_ownership_id(ownable_id, bank_id()),
  551. limit,
  552. False,
  553. amount,
  554. time_until_expiration)
  555. ownable_name = ownable_name_by_id(ownable_id)
  556. cursor.execute('''
  557. INSERT INTO news(title)
  558. VALUES (?)
  559. ''', ('External investors are selling ' + ownable_name + ' atm',))
  560. def current_time(): # might differ from datetime.datetime.now() for time zone reasons
  561. connect()
  562. cursor.execute('''
  563. SELECT datetime('now')
  564. ''')
  565. return cursor.fetchone()[0]
  566. def place_order(buy, ownership_id, limit, stop_loss, amount, time_until_expiration):
  567. connect()
  568. expiry = datetime.strptime(current_time(), '%Y-%m-%d %H:%M:%S') + timedelta(minutes=time_until_expiration)
  569. cursor.execute('''
  570. INSERT INTO orders
  571. (buy, ownership_id, "limit", stop_loss, ordered_amount, expiry_dt)
  572. VALUES (?, ?, ?, ?, ?, ?)
  573. ''', (buy, ownership_id, limit, stop_loss, amount, expiry))
  574. execute_orders(ownable_id_by_ownership_id(ownership_id))
  575. return True
  576. def transactions(ownable_id):
  577. connect()
  578. cursor.execute('''
  579. SELECT dt, amount, price
  580. FROM transactions
  581. WHERE ownable_id = ?
  582. ORDER BY dt DESC
  583. ''', (ownable_id,))
  584. return cursor.fetchall()
  585. def drop_expired_orders():
  586. connect()
  587. cursor.execute('''
  588. DELETE FROM orders
  589. WHERE expiry_dt < DATETIME('now')
  590. ''')
  591. return cursor.fetchall()
  592. def generate_keys(count=1):
  593. # source https://stackoverflow.com/questions/17049308/python-3-3-serial-key-generator-list-problems
  594. for i in range(count):
  595. key = '-'.join(random_chars(5) for _ in range(5))
  596. save_key(key)
  597. print(key)
  598. def user_has_order_with_id(session_id, order_id):
  599. connect()
  600. cursor.execute('''
  601. SELECT orders.rowid
  602. FROM orders, ownership, sessions
  603. WHERE orders.rowid = ?
  604. AND sessions.session_id = ?
  605. AND sessions.user_id = ownership.user_id
  606. AND ownership.rowid = orders.ownership_id
  607. ''', (order_id, session_id,))
  608. if cursor.fetchone():
  609. return True
  610. else:
  611. return False
  612. def leaderboard():
  613. connect()
  614. cursor.execute('''
  615. SELECT *
  616. FROM ( -- one score for each user
  617. SELECT
  618. username,
  619. SUM(CASE -- sum score for each of the users ownables
  620. WHEN ownership.ownable_id = ? THEN ownership.amount
  621. ELSE ownership.amount * (SELECT price
  622. FROM transactions
  623. WHERE ownable_id = ownership.ownable_id
  624. ORDER BY dt DESC
  625. LIMIT 1)
  626. END
  627. ) score
  628. FROM users, ownership
  629. WHERE ownership.user_id = users.rowid
  630. AND users.username != 'bank'
  631. ) AS scores
  632. ORDER BY score DESC
  633. LIMIT 50
  634. ''', (currency_id(),))
  635. return cursor.fetchall()
  636. def user_wealth(user_id):
  637. connect()
  638. cursor.execute('''
  639. SELECT SUM(
  640. CASE -- sum score for each of the users ownables
  641. WHEN ownership.ownable_id = ? THEN ownership.amount
  642. ELSE ownership.amount * (SELECT price
  643. FROM transactions
  644. WHERE ownable_id = ownership.ownable_id
  645. ORDER BY dt DESC
  646. LIMIT 1)
  647. END
  648. ) score
  649. FROM ownership
  650. WHERE ownership.user_id = ?
  651. ''', (currency_id(), user_id,))
  652. return cursor.fetchone()[0]