model.py 19 KB

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