model.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. import datetime
  2. import random
  3. import re
  4. import sqlite3 as db
  5. import sys
  6. import uuid
  7. import db_setup
  8. from game import CURRENCY_NAME
  9. from util import debug, random_chars
  10. connection: db.Connection = None
  11. cursor: db.Cursor = None
  12. db_name = None
  13. def query_save_name():
  14. global db_name
  15. if debug:
  16. db_name = 'test.db'
  17. return
  18. while True:
  19. save_name = input('Name of the savegame: ')
  20. if re.match(r"[A-Za-z0-9.-]{0,50}", save_name):
  21. db_name = save_name + '.db'
  22. return
  23. else:
  24. print('Must match "[A-Za-z0-9.-]{0,50}"')
  25. def connect(reconnect=False):
  26. global connection
  27. global cursor
  28. global db_name
  29. if reconnect:
  30. connection.commit()
  31. connection.close()
  32. cursor = None
  33. connection = None
  34. db_name = None
  35. if connection is None or cursor is None:
  36. query_save_name()
  37. try:
  38. connection = db.connect(db_name)
  39. cursor = connection.cursor()
  40. except db.Error as e:
  41. print("Database error %s:" % e.args[0])
  42. sys.exit(1)
  43. # finally:
  44. # if con is not None:
  45. # con.close()
  46. def setup():
  47. connect()
  48. db_setup.setup(cursor)
  49. connection.commit()
  50. def login(username, password):
  51. connect()
  52. # do not allow login as bank
  53. if password == '':
  54. return None
  55. cursor.execute('''
  56. SELECT rowid
  57. FROM users
  58. WHERE username = ?
  59. AND password = ?
  60. ''', (username, password))
  61. user_id = cursor.fetchone()
  62. if user_id:
  63. return new_session(user_id)
  64. else:
  65. return None
  66. def register(username, password, game_key):
  67. connect()
  68. if username == '':
  69. return False
  70. if password == '':
  71. return False
  72. cursor.execute('''
  73. INSERT INTO users
  74. (username, password)
  75. VALUES (? , ?)
  76. ''', (username, password))
  77. own(get_user_id_by_name(username), CURRENCY_NAME)
  78. if game_key != '':
  79. if valid_key(game_key):
  80. activate_key(game_key, get_user_id_by_name(username))
  81. return True
  82. def own(user_id, ownable_name):
  83. cursor.execute('''
  84. WITH one_ownable_id AS (SELECT rowid FROM ownables WHERE name = ?),
  85. one_user_id AS (SELECT ?)
  86. INSERT INTO ownership (user_id, ownable_id)
  87. SELECT *
  88. FROM one_user_id, one_ownable_id
  89. WHERE NOT EXISTS (
  90. SELECT * FROM ownership
  91. WHERE ownership.user_id IN one_user_id
  92. AND ownership.ownable_id IN one_ownable_id
  93. )
  94. ''', (ownable_name, user_id,))
  95. def send_ownable(from_user_id, to_user_id, ownable_name, amount):
  96. connect()
  97. if amount < 0:
  98. return False
  99. if from_user_id != bank_id():
  100. cursor.execute('''
  101. UPDATE ownership
  102. SET amount = amount - ?
  103. WHERE user_id = ?
  104. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  105. ''', (amount, from_user_id, ownable_name,))
  106. if not cursor.fetchone():
  107. return False
  108. cursor.execute('''
  109. UPDATE ownership
  110. SET amount = amount + ?
  111. WHERE user_id = ?
  112. AND ownable_id = (SELECT rowid FROM ownables WHERE name = ?)
  113. ''', (amount, to_user_id, ownable_name))
  114. if cursor.rowcount == 0:
  115. return False
  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 ids is None:
  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. if cursor.rowcount == 0:
  212. raise AssertionError
  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. orders.expiry_dt
  251. FROM orders, ownables, ownership
  252. WHERE ownership.user_id = ?
  253. AND ownership.ownable_id = ownables.rowid
  254. AND orders.ownership_id = ownership.rowid
  255. ORDER BY orders.buy DESC, ownables.name ASC
  256. ''', (user_id,))
  257. return cursor.fetchall()
  258. def sell_ordered_amount(user_id, ownable_id):
  259. connect()
  260. # if ownable_id == currency_id():
  261. # return 0
  262. cursor.execute('''
  263. SELECT SUM(orders.ordered_amount - orders.executed_amount)
  264. FROM orders, ownership
  265. WHERE ownership.rowid = orders.ownership_id
  266. AND ownership.user_id = ?
  267. AND ownership.ownable_id = ?
  268. AND NOT orders.buy
  269. ''', (user_id, ownable_id))
  270. return cursor.fetchone()[0]
  271. def user_owns_at_least(amount, user_id, ownable_id):
  272. connect()
  273. cursor.execute('''
  274. SELECT rowid
  275. FROM ownership
  276. WHERE user_id = ?
  277. AND ownable_id = ?
  278. AND amount - ? >= ?
  279. ''', (user_id, ownable_id, sell_ordered_amount(user_id, ownable_id), amount))
  280. if cursor.fetchone():
  281. return True
  282. else:
  283. return False
  284. def news():
  285. connect()
  286. cursor.execute('''
  287. SELECT *
  288. FROM news
  289. ORDER BY dt DESC
  290. LIMIT 20
  291. ''')
  292. return cursor.fetchall()
  293. def ownable_name_exists(name):
  294. connect()
  295. cursor.execute('''
  296. SELECT rowid
  297. FROM ownables
  298. WHERE name = ?
  299. ''', (name,))
  300. if cursor.fetchone():
  301. return True
  302. else:
  303. return False
  304. def new_stock(name=None):
  305. connect()
  306. while name is None:
  307. name = random_chars(6)
  308. if ownable_name_exists(name):
  309. name = None
  310. cursor.execute('''
  311. INSERT INTO ownables(name)
  312. VALUES (?)
  313. ''', (name,))
  314. cursor.execute('''
  315. INSERT INTO news(title)
  316. VALUES (?)
  317. ''', ('A new stock can now be bought: ' + name,))
  318. if random.getrandbits(1):
  319. cursor.execute('''
  320. INSERT INTO news(title)
  321. VALUES (?)
  322. ''', ('Experts expect the price of ' + name + ' to fall',))
  323. else:
  324. cursor.execute('''
  325. INSERT INTO news(title)
  326. VALUES (?)
  327. ''', ('Experts expect the price of ' + name + ' to rise',))
  328. amount = random.randrange(100, 10000)
  329. price = random.randrange(10000, 20000) / amount
  330. bank_order(True,
  331. ownable_id_by_name(name),
  332. price,
  333. amount,
  334. 60)
  335. return name
  336. def new_stocks(count=1):
  337. return [new_stock() for _ in range(count)]
  338. def ownable_id_by_name(ownable_name):
  339. connect()
  340. cursor.execute('''
  341. SELECT rowid
  342. FROM ownables
  343. WHERE name = ?
  344. ''', (ownable_name,))
  345. return cursor.fetchone()[0]
  346. def get_ownership_id(ownable_id, user_id):
  347. connect()
  348. cursor.execute('''
  349. SELECT rowid
  350. FROM ownership
  351. WHERE ownable_id = ?
  352. AND user_id = ?
  353. ''', (ownable_id, user_id,))
  354. return cursor.fetchone()[0]
  355. def currency_id():
  356. connect()
  357. cursor.execute('''
  358. SELECT rowid
  359. FROM ownables
  360. WHERE name = ?
  361. ''', (CURRENCY_NAME,))
  362. return cursor.fetchone()[0]
  363. def execute_orders(ownable_id):
  364. connect()
  365. executed_any = True
  366. while executed_any:
  367. executed_any = False
  368. # find order to execute
  369. cursor.execute('''
  370. SELECT buy_order.*, sell_order.*, buyer.*, seller.*
  371. FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
  372. WHERE buy_order.buy AND NOT sell_order.buy
  373. AND buyer.rowid = buy_order.ownership_id
  374. AND seller.rowid = sell_order.ownership_id
  375. AND buyer.ownable_id = ?
  376. AND seller.ownable_id = ?
  377. AND (buy_order."limit" IS NULL
  378. OR sell_order."limit" IS NULL
  379. OR (sell_order."limit" < buy_order."limit"
  380. AND NOT sell_order.stop_loss
  381. AND NOT buy_order.stop_loss))
  382. ORDER BY COALESCE(sell_order."limit", 0) ASC,
  383. COALESCE(buy_order."limit", 0) DESC
  384. LIMIT 1
  385. ''', (ownable_id, ownable_id,))
  386. matching_orders = cursor.fetchone()
  387. if not matching_orders:
  388. continue
  389. # TODO compute execution price, amount, buyer_id and seller_id from matching_orders
  390. price = -1
  391. if price < 0 or amount < 0:
  392. return AssertionError()
  393. # actually execute the order
  394. cursor.execute('''
  395. UPDATE ownership
  396. SET amount = amount - ?
  397. WHERE user_id = ?
  398. ''', (price, buyer_id))
  399. if not cursor.fetchone():
  400. raise AssertionError()
  401. cursor.execute('''
  402. UPDATE ownership
  403. SET amount = amount - ?
  404. WHERE user_id = ?
  405. ''', (amount, seller_id))
  406. if not cursor.fetchone():
  407. raise AssertionError()
  408. cursor.execute('''
  409. UPDATE ownership
  410. SET amount = amount + ?
  411. WHERE user_id = ?
  412. ''', (amount, buyer_id))
  413. if not cursor.fetchone():
  414. raise AssertionError()
  415. cursor.execute('''
  416. UPDATE ownership
  417. SET amount = amount + ?
  418. WHERE user_id = ?
  419. ''', (price, seller_id))
  420. if not cursor.fetchone():
  421. raise AssertionError()
  422. cursor.execute('''
  423. UPDATE orders
  424. SET executed_amount = executed_amount + ?
  425. WHERE rowid = ?
  426. OR rowid = ?
  427. ''', (amount, buy_order_id, sell_order_id))
  428. if not cursor.fetchone():
  429. raise AssertionError()
  430. executed_any = True
  431. if seller_id != buyer_id: # prevent showing self-transactions to keep the price reasonable
  432. cursor.execute('''
  433. INSERT INTO transactions
  434. (price, ownable_id, amount)
  435. VALUES(?, ?, ?)
  436. ''', (price, ownable_id, amount,))
  437. # trigger stop loss orders
  438. cursor.execute('''
  439. UPDATE orders
  440. SET stop_loss = FALSE,
  441. "limit" = NULL
  442. WHERE stop_loss
  443. AND (buy AND "limit" > ?)
  444. OR (NOT buy AND "limit" < ?)
  445. (price, ownable_id, amount)
  446. VALUES(?, ?, ?)
  447. ''', (price, price,))
  448. def ownable_id_by_ownership_id(ownership_id):
  449. connect()
  450. cursor.execute('''
  451. SELECT ownable_id
  452. FROM ownership
  453. WHERE rowid = ?
  454. ''', (ownership_id,))
  455. return cursor.fetchone()[0]
  456. def bank_order(buy, ownable_id, limit, amount, time_until_expiration):
  457. if limit is None:
  458. raise AssertionError()
  459. place_order(buy,
  460. get_ownership_id(ownable_id, bank_id()),
  461. limit,
  462. False,
  463. amount,
  464. time_until_expiration)
  465. ownable_name = ownable_name_by_id(ownable_id)
  466. cursor.execute('''
  467. INSERT INTO news(title)
  468. VALUES (?)
  469. ''', ('External investors are selling ' + ownable_name))
  470. def place_order(buy, ownership_id, limit, stop_loss, amount, time_until_expiration):
  471. connect()
  472. expiry = datetime.datetime.now() + datetime.timedelta(minutes=time_until_expiration)
  473. cursor.execute('''
  474. INSERT INTO orders
  475. (buy, ownership_id, "limit", stop_loss, ordered_amount, expiry_dt)
  476. VALUES (?, ?, ?, ?, ?, ?)
  477. ''', (buy, ownership_id, limit, stop_loss, amount, expiry))
  478. execute_orders(ownable_id_by_ownership_id(ownership_id))
  479. return True
  480. def transactions(ownable_id):
  481. connect()
  482. cursor.execute('''
  483. SELECT dt, amount, price
  484. FROM transactions
  485. WHERE ownable_id = ?
  486. ORDER BY dt DESC
  487. ''', (ownable_id,))
  488. return cursor.fetchall()