Browse Source

work on orders and content generation

Eren Yilmaz 6 năm trước cách đây
mục cha
commit
de8942d61e
6 tập tin đã thay đổi với 289 bổ sung110 xóa
  1. 3 5
      admin_console.py
  2. 148 86
      db_setup.py
  3. 0 1
      letter_dist.csv
  4. 117 15
      model.py
  5. 4 1
      server.py
  6. 17 2
      util.py

+ 3 - 5
admin_console.py

@@ -1,14 +1,12 @@
-import random
-
 import model
-from util import chars
+from util import random_chars
 
 
 def generate_keys(count=1):
     # source https://stackoverflow.com/questions/17049308/python-3-3-serial-key-generator-list-problems
 
     for i in range(count):
-        key = '-'.join(''.join(random.choice(chars) for _ in range(5)) for _ in range(5))
+        key = '-'.join(random_chars(5) for _ in range(5))
         model.save_key(key)
         print(key)
 
@@ -24,7 +22,7 @@ def cleanup():
 
 
 if __name__ == '__main__':
-    # generate_keys(count=1)
+    generate_keys(count=1)
     print(model.new_stocks(count=3))
     # unused_keys()
     # model.drop_old_sessions()

+ 148 - 86
db_setup.py

@@ -1,20 +1,156 @@
 from game import CURRENCY_NAME
+from util import debug
 
 
 def setup(cursor):
     print('Database setup...')
 
-    replace = False
+    replace = False and debug
 
     if replace:
-        print(' - Dropping old tables...')
-        cursor.execute("DROP TABLE IF EXISTS users")
-        cursor.execute("DROP TABLE IF EXISTS ownables")
-        cursor.execute("DROP TABLE IF EXISTS ownership")
-        cursor.execute("DROP TABLE IF EXISTS sessions")
-        cursor.execute("DROP TABLE IF EXISTS orders")
-        cursor.execute("DROP TABLE IF EXISTS transactions")
+        drop_database(cursor)
 
+    tables(cursor)
+
+    integrity_checks(cursor)
+
+    if replace:  # TODO also seed new databases
+        seed(cursor)
+
+
+def drop_database(cursor):
+    print(' - Dropping old tables...')
+    cursor.execute("DROP TABLE IF EXISTS users")
+    cursor.execute("DROP TABLE IF EXISTS ownables")
+    cursor.execute("DROP TABLE IF EXISTS ownership")
+    cursor.execute("DROP TABLE IF EXISTS sessions")
+    cursor.execute("DROP TABLE IF EXISTS orders")
+    cursor.execute("DROP TABLE IF EXISTS transactions")
+    cursor.execute("DROP TABLE IF EXISTS keys")
+    cursor.execute("DROP TABLE IF EXISTS news")
+    cursor.execute("DROP TRIGGER IF EXISTS owned_amount_not_negative_after_insert")
+    cursor.execute("DROP TRIGGER IF EXISTS owned_amount_not_negative_after_update")
+    cursor.execute("DROP TRIGGER IF EXISTS order_limit_not_negative_after_insert")
+    cursor.execute("DROP TRIGGER IF EXISTS order_limit_not_negative_after_update")
+    cursor.execute("DROP TRIGGER IF EXISTS order_amount_positive_after_insert")
+    cursor.execute("DROP TRIGGER IF EXISTS order_amount_positive_after_update")
+
+
+def seed(cursor):
+    print(' - Seeding initial data...')
+    cursor.execute('''
+                    INSERT INTO ownables
+                    (name)
+                    VALUES (?)
+                    ''', (CURRENCY_NAME,))
+    cursor.execute('''
+                    INSERT INTO users
+                    (username,password)
+                    VALUES ('bank','')
+                    ''')
+    cursor.execute('''
+                    INSERT INTO ownership
+                    (user_id, ownable_id)
+                    VALUES ((SELECT rowid FROM users WHERE username = 'bank'), 
+                            (SELECT rowid FROM ownables WHERE name = ?))
+                    ''', (CURRENCY_NAME,))
+
+
+def integrity_checks(cursor):
+    print(' - Integrity checks...')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS owned_amount_not_negative_after_insert
+                AFTER INSERT
+                ON ownership
+                WHEN NEW.amount < 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not own an amount less than 0.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS owned_amount_not_negative_after_update
+                AFTER UPDATE
+                ON ownership
+                WHEN NEW.amount < 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not own an amount less than 0.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS amount_positive_after_insert
+                AFTER INSERT
+                ON transactions
+                WHEN NEW.amount <= 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not perform empty transactions.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS amount_positive_after_update
+                AFTER UPDATE
+                ON transactions
+                WHEN NEW.amount <= 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not perform empty transactions.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS order_limit_not_negative_after_insert
+                AFTER INSERT
+                ON orders
+                WHEN NEW."limit" IS NOT NULL AND NEW."limit" < 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not set a limit less than 0.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS order_limit_not_negative_after_update
+                AFTER UPDATE
+                ON orders
+                WHEN NEW."limit" IS NOT NULL AND NEW."limit" < 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not set a limit less than 0.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS order_amount_positive_after_insert
+                AFTER INSERT
+                ON orders
+                WHEN NEW.ordered_amount <= 0 OR NEW.executed_amount < 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not order 0 or less.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS order_amount_positive_after_update
+                AFTER UPDATE
+                ON orders
+                WHEN NEW.ordered_amount <= 0 OR NEW.executed_amount < 0
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not order 0 or less.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS not_more_executed_than_ordered_after_insert
+                AFTER INSERT
+                ON orders
+                WHEN NEW.ordered_amount < NEW.executed_amount
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not execute more than ordered.');
+                END
+                ''')
+    cursor.execute('''
+                CREATE TRIGGER IF NOT EXISTS not_more_executed_than_ordered_after_update
+                AFTER UPDATE
+                ON orders
+                WHEN NEW.ordered_amount < NEW.executed_amount
+                BEGIN
+                    SELECT RAISE(ROLLBACK, 'Can not execute more than ordered.');
+                END
+                ''')
+
+
+def tables(cursor):
     print(' - Creating tables...')
     cursor.execute('''
                 CREATE TABLE IF NOT EXISTS users(
@@ -48,16 +184,17 @@ def setup(cursor):
                     buy BOOLEAN NOT NULL,
                     "limit" CURRENCY,
                     stop_loss BOOLEAN,
-                    ordered_amount CURRENCY,
-                    executed_amount CURRENCY DEFAULT 0,
+                    ordered_amount CURRENCY NOT NULL,
+                    executed_amount CURRENCY DEFAULT 0 NOT NULL,
                     FOREIGN KEY (ownership_id) REFERENCES ownership(rowid)
                 )
                 ''')
     cursor.execute('''
                 CREATE TABLE IF NOT EXISTS transactions(
-                    dt DATETIME NOT NULL,
+                    dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                     price CURRENCY NOT NULL,
                     ownable_id INTEGER NOT NULL,
+                    amount CURRENCY NOT NULL,
                     FOREIGN KEY (ownable_id) REFERENCES ownable(rowid)
                 )
                 ''')
@@ -74,78 +211,3 @@ def setup(cursor):
                     title VARCHAR(50) NOT NULL
                 )
                 ''')
-
-    print(' - Integrity checks...')
-    cursor.execute('''
-                CREATE TRIGGER IF NOT EXISTS owned_amount_not_negative_after_insert
-                AFTER INSERT
-                ON ownership
-                WHEN NEW.amount < 0
-                BEGIN
-                    SELECT RAISE(ROLLBACK, 'Can not own an amount less than 0.');
-                END
-                ''')
-    cursor.execute('''
-                CREATE TRIGGER IF NOT EXISTS owned_amount_not_negative_after_update
-                AFTER UPDATE
-                ON ownership
-                WHEN NEW.amount < 0
-                BEGIN
-                    SELECT RAISE(ROLLBACK, 'Can not own an amount less than 0.');
-                END
-                ''')
-    cursor.execute('''
-                CREATE TRIGGER IF NOT EXISTS order_limit_not_negative_after_insert
-                AFTER INSERT
-                ON orders
-                WHEN "limit" IS NOT NULL AND "limit" < 0
-                BEGIN
-                    SELECT RAISE(ROLLBACK, 'Can not set a limit less than 0.');
-                END
-                ''')
-    cursor.execute('''
-                CREATE TRIGGER IF NOT EXISTS order_limit_not_negative_after_update
-                AFTER UPDATE
-                ON orders
-                WHEN "limit" IS NOT NULL AND "limit" < 0
-                BEGIN
-                    SELECT RAISE(ROLLBACK, 'Can not set a limit less than 0.');
-                END
-                ''')
-    cursor.execute('''
-                CREATE TRIGGER IF NOT EXISTS order_amount_not_negative_after_insert
-                AFTER INSERT
-                ON orders
-                WHEN ordered_amount <= 0 OR executed_amount < 0
-                BEGIN
-                    SELECT RAISE(ROLLBACK, 'Can not order 0 or less.');
-                END
-                ''')
-    cursor.execute('''
-                CREATE TRIGGER IF NOT EXISTS order_amount_not_negative_after_update
-                AFTER UPDATE
-                ON orders
-                WHEN ordered_amount <= 0 OR executed_amount < 0
-                BEGIN
-                    SELECT RAISE(ROLLBACK, 'Can not order 0 or less.');
-                END
-                ''')
-
-    if replace:  # TODO also seed new databases
-        print(' - Seeding initial data...')
-        cursor.execute('''
-                    INSERT INTO ownables
-                    (name)
-                    VALUES (?)
-                    ''', (CURRENCY_NAME,))
-        cursor.execute('''
-                    INSERT INTO users
-                    (username,password)
-                    VALUES ('bank','')
-                    ''')
-        cursor.execute('''
-                    INSERT INTO ownership
-                    (user_id, ownable_id)
-                    VALUES ((SELECT rowid FROM users WHERE username = 'bank'), 
-                            (SELECT rowid FROM ownables WHERE name = ?))
-                    ''', (CURRENCY_NAME,))

+ 0 - 1
letter_dist.csv

@@ -1,4 +1,3 @@
-Letter,Count,Frequency
 E,21912,12.02
 T,16587,9.1
 A,14810,8.12

+ 117 - 15
model.py

@@ -1,4 +1,3 @@
-import random
 import re
 import sqlite3 as db
 import sys
@@ -6,10 +5,10 @@ import uuid
 
 import db_setup
 from game import CURRENCY_NAME
-from util import debug, chars
+from util import debug, random_chars
 
-connection = None
-cursor = None
+connection: db.Connection = None
+cursor: db.Cursor = None
 db_name = None
 
 
@@ -310,17 +309,24 @@ def get_user_orders(user_id):
 
     cursor.execute('''
         SELECT 
-            orders.buy, 
+            CASE 
+                WHEN orders.buy  THEN 'Buy'
+                ELSE 'Sell'
+            END,
             ownables.name, 
             orders.ordered_amount - orders.executed_amount, 
             orders."limit", 
-            orders.stop_loss, 
+            CASE 
+                WHEN orders."limit" IS NULL THEN NULL 
+                WHEN orders.stop_loss THEN 'Yes'
+                ELSE 'No'
+            END, 
             orders.ordered_amount
         FROM orders, ownables, ownership
         WHERE ownership.user_id = ?
         AND ownership.ownable_id = ownables.rowid
         AND orders.ownership_id = ownership.rowid
-        ORDER BY orders.buy, ownables.name
+        ORDER BY orders.buy DESC, ownables.name ASC
         ''', (user_id,))
 
     return cursor.fetchall()
@@ -393,7 +399,7 @@ def new_stock(name=None):
     connect()
 
     while name is None:
-        name = ''.join(random.choice(chars) for _ in range(6))
+        name = random_chars(6)
         if ownable_name_exists(name):
             name = None
 
@@ -445,17 +451,113 @@ def currency_id():
     return cursor.fetchone()[0]
 
 
-def execute_orders():
+def execute_orders(ownable_id):
     connect()
-    while True:
+    executed_any = True
+    while executed_any:
         executed_any = False
+        # find order to execute
+        cursor.execute('''
+            SELECT buy_order.*, sell_order.*, buyer.*, seller.*
+            FROM orders buy_order, orders sell_order, ownership buyer, ownership seller
+            WHERE buy_order.buy AND NOT sell_order.buy
+            AND buyer.rowid = buy_order.ownership_id
+            AND seller.rowid = sell_order.ownership_id
+            AND buyer.ownable_id = ?
+            AND seller.ownable_id = ?
+            AND (buy_order."limit" IS NULL
+                OR sell_order."limit" IS NULL
+                OR (sell_order."limit" < buy_order."limit"
+                    AND NOT sell_order.stop_loss
+                    AND NOT buy_order.stop_loss))
+            ORDER BY COALESCE(sell_order."limit", 0) ASC, 
+                     COALESCE(buy_order."limit", 0) DESC
+            LIMIT 1
+            ''', (ownable_id, ownable_id,))
+
+        matching_orders = cursor.fetchone()
+        if not matching_orders:
+            continue
+
+        # TODO compute execution price, amount, buyer_id and seller_id from matching_orders
+        price = -1
+
+        if price < 0 or amount < 0:
+            return AssertionError()
+
+        # actually execute the order
+        cursor.execute('''
+            UPDATE ownership 
+            SET amount = amount - ?
+            WHERE user_id = ?
+            ''', (price, buyer_id))
+        if not cursor.fetchone():
+            raise AssertionError()
 
-        # TODO execute one orders
+        cursor.execute('''
+            UPDATE ownership 
+            SET amount = amount - ?
+            WHERE user_id = ?
+            ''', (amount, seller_id))
 
-        # TODO trigger stop loss orders
+        if not cursor.fetchone():
+            raise AssertionError()
+        cursor.execute('''
+            UPDATE ownership 
+            SET amount = amount + ?
+            WHERE user_id = ?
+            ''', (amount, buyer_id))
 
-        if not executed_any:
-            return
+        if not cursor.fetchone():
+            raise AssertionError()
+        cursor.execute('''
+            UPDATE ownership 
+            SET amount = amount + ?
+            WHERE user_id = ?
+            ''', (price, seller_id))
+        if not cursor.fetchone():
+            raise AssertionError()
+        cursor.execute('''
+            UPDATE orders 
+            SET executed_amount = executed_amount + ?
+            WHERE rowid = ?
+            OR rowid = ?
+            ''', (amount, buy_order_id, sell_order_id))
+        if not cursor.fetchone():
+            raise AssertionError()
+
+        executed_any = True
+
+        if seller_id != buyer_id:  # prevent showing self-transactions to keep the price reasonable
+            cursor.execute('''
+                INSERT INTO transactions
+                (price, ownable_id, amount)
+                VALUES(?, ?, ?)
+                ''', (price, ownable_id, amount,))
+
+        # trigger stop loss orders
+        cursor.execute('''
+            UPDATE orders
+            SET stop_loss = FALSE,
+                "limit" = NULL
+            WHERE stop_loss
+            AND (buy AND "limit" > ?)
+                OR (NOT buy AND "limit" < ?)
+            (price, ownable_id, amount)
+            VALUES(?, ?, ?)
+            ''', (price, price,))
+
+
+def ownable_id_by_ownership_id(ownership_id):
+    connect()
+
+    cursor.execute('''
+        SELECT ownable_id
+        FROM ownership
+        WHERE rowid = ?
+        ''', (ownership_id,))
+
+    return cursor.fetchone()[0]
 
 
 def place_order(buy, ownership_id, limit, stop_loss, amount):
@@ -467,5 +569,5 @@ def place_order(buy, ownership_id, limit, stop_loss, amount):
                 VALUES (?, ?, ?, ?, ?)
                 ''', (buy, ownership_id, limit, stop_loss, amount))
 
-    execute_orders()
+    execute_orders(ownable_id_by_ownership_id(ownership_id))
     return True

+ 4 - 1
server.py

@@ -24,7 +24,10 @@ if __name__ == '__main__':
         method_to_call = getattr(server_controller, path)
         try:
             resp = method_to_call()
-            model.connection.commit()
+            if response.status_code == 200:
+                model.connection.commit()
+            else:
+                model.connection.rollback()
             return resp
         except sqlite3.IntegrityError as e:
             print(e)

+ 17 - 2
util.py

@@ -1,3 +1,18 @@
+import csv
+import numpy
+
 debug = True
-# noinspection SpellCheckingInspection
-chars = "ABCDFGHJIKLMNPQRSTUVWXYZ123456789"
+chars = [str(d) for d in range(1, 10)]
+
+p = [1 for _ in chars]
+with open('letter_dist.csv', newline='') as csvfile:
+    reader = csv.reader(csvfile, delimiter=',')
+    sp = sum(p)
+    for row in reader:
+        chars.append(row[0])
+        p.append(float(row[2]))
+p = numpy.array(p) / sum(p)
+
+
+def random_chars(count):
+    return ''.join(numpy.random.choice(chars, p=p) for _ in range(count))