model.py 23 KB

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