trading_bot.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import random
  2. from datetime import timedelta, datetime
  3. from math import log2, ceil
  4. import model
  5. from debug import debug
  6. from game import DEFAULT_ORDER_EXPIRY
  7. def place_order(ownable_id):
  8. """
  9. places a new order according to the algorithm described in `assets/follower.py`
  10. :param ownable_id: on which ownable to place the order
  11. :return: True iff a new order was placed
  12. """
  13. best_buy_order, cheapest_sell_order = model.abs_spread(ownable_id)
  14. if best_buy_order is None or cheapest_sell_order is None:
  15. return False
  16. investors_id = model.bank_id()
  17. ownership_id = model.get_ownership_id(ownable_id, investors_id)
  18. if debug: # the bot should only have one order
  19. model.cursor.execute('''
  20. SELECT COUNT(*) = 0
  21. FROM orders
  22. WHERE ownership_id = ?
  23. ''', (ownership_id,))
  24. if not model.cursor.fetchone()[0]:
  25. raise AssertionError('The bot should no orders at this point.')
  26. amounts = model.cursor.execute('''
  27. SELECT ordered_amount
  28. FROM orders, ownership
  29. WHERE orders.ownership_id = ownership.rowid
  30. AND ownership.ownable_id = ?
  31. ''', (ownable_id,)).fetchall()
  32. if len(amounts) < 2:
  33. raise AssertionError('We should have found at least two orders.')
  34. amounts = [random.choice(amounts)[0] for _ in range(int(ceil(log2(len(amounts)))))]
  35. amount = ceil(sum(amounts) / len(amounts))
  36. expiry = datetime.strptime(model.current_db_time(), '%Y-%m-%d %H:%M:%S') + timedelta(minutes=DEFAULT_ORDER_EXPIRY)
  37. limit = round(random.uniform(best_buy_order, cheapest_sell_order) * 10000) / 10000
  38. if limit - best_buy_order < cheapest_sell_order - limit:
  39. model.place_order(buy=True,
  40. ownership_id=ownership_id,
  41. limit=limit,
  42. stop_loss=False,
  43. amount=amount,
  44. expiry=expiry)
  45. else:
  46. model.place_order(buy=False,
  47. ownership_id=ownership_id,
  48. limit=limit,
  49. stop_loss=False,
  50. amount=amount,
  51. expiry=expiry)
  52. return True
  53. def notify_expired_orders(orders):
  54. for order in orders:
  55. # order_id = order[0]
  56. ownership_id = order[1]
  57. # check if that was one of the bots orders
  58. bank_ownership_id = model.get_ownership_id(model.ownable_id_by_ownership_id(ownership_id), model.bank_id())
  59. if ownership_id != bank_ownership_id:
  60. continue
  61. # create a new order
  62. ownable_id = model.ownable_id_by_ownership_id(ownership_id)
  63. place_order(ownable_id)
  64. def notify_order_traded(ownable_id):
  65. """
  66. Called after a trade has been done and now the auctions are finished.
  67. :param ownable_id: the ownable that was traded
  68. :return: True iff a new order was placed
  69. """
  70. model.connect()
  71. if ownable_id == model.currency_id():
  72. return False
  73. ownership_id = model.get_ownership_id(ownable_id, model.bank_id())
  74. if debug: # the bot should only have one order
  75. model.cursor.execute('''
  76. SELECT COUNT(*) <= 1
  77. FROM orders
  78. WHERE ownership_id = ?
  79. ''', (ownership_id,))
  80. if not model.cursor.fetchone()[0]:
  81. raise AssertionError('The bot should have at most one order.')
  82. model.cursor.execute('''
  83. SELECT rowid, ordered_amount, expiry_dt
  84. FROM orders
  85. WHERE ownership_id = ?
  86. -- no need for ORDER since the bot should have only one order
  87. UNION ALL
  88. SELECT * FROM (
  89. SELECT NULL, ordered_amount, expiry_dt
  90. FROM order_history
  91. WHERE ownership_id = ?
  92. ORDER BY rowid DESC -- equivalent to ordering by time created
  93. )
  94. LIMIT 1
  95. ''', (ownership_id, ownership_id,))
  96. data = model.cursor.fetchall()
  97. if not data:
  98. return place_order(ownable_id)
  99. my_last_order = data[0]
  100. last_order_open = my_last_order[0] is not None
  101. last_order_id = my_last_order[0]
  102. last_amount = my_last_order[1]
  103. expiry = my_last_order[2]
  104. dt_order_placed = datetime.strptime(expiry, '%Y-%m-%d %H:%M:%S') - timedelta(minutes=DEFAULT_ORDER_EXPIRY)
  105. model.cursor.execute('''
  106. SELECT COALESCE(SUM(amount), 0) >= 2 * ?
  107. FROM transactions
  108. WHERE ownable_id = ?
  109. AND dt > ? -- interestingly >= would be problematic
  110. ''', (last_amount, ownable_id, dt_order_placed))
  111. if model.cursor.fetchone()[0]:
  112. if last_order_open:
  113. model.delete_order(last_order_id, 'Canceled')
  114. return place_order(ownable_id)
  115. return False
  116. def main():
  117. """the initial part of the trading bot algorithm"""
  118. if model.get_user_orders(model.bank_id()):
  119. raise AssertionError('The trading bot already has some orders.')
  120. if input('Are you sure you want to place the initial orders? (type in "yes" or something else):') == 'yes':
  121. for ownable_id in model.ownable_ids():
  122. if ownable_id != model.currency_id():
  123. place_order(ownable_id)
  124. else:
  125. print('Not placing orders.')
  126. model.cleanup()
  127. if __name__ == '__main__':
  128. main()