model.py 20 KB

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