trading_bot.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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 have 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 = model.current_db_timestamp() + 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. ioc=False)
  46. else:
  47. model.place_order(buy=False,
  48. ownership_id=ownership_id,
  49. limit=limit,
  50. stop_loss=False,
  51. amount=amount,
  52. expiry=expiry,
  53. ioc=False)
  54. return True
  55. def notify_expired_orders(orders):
  56. for order in orders:
  57. # order_id = order[0]
  58. ownership_id = order[1]
  59. # check if that was one of the bots orders
  60. bank_ownership_id = model.get_ownership_id(model.ownable_id_by_ownership_id(ownership_id), model.bank_id())
  61. if ownership_id != bank_ownership_id:
  62. continue
  63. # create a new order
  64. ownable_id = model.ownable_id_by_ownership_id(ownership_id)
  65. place_order(ownable_id)
  66. def notify_order_traded(ownable_id):
  67. """
  68. Called after a trade has been done and now the auctions are finished.
  69. :param ownable_id: the ownable that was traded
  70. :return: True iff a new order was placed
  71. """
  72. model.connect()
  73. if ownable_id == model.currency_id():
  74. return False
  75. ownership_id = model.get_ownership_id(ownable_id, model.bank_id())
  76. if debug: # the bot should only have one order
  77. model.cursor.execute('''
  78. SELECT COUNT(*) <= 1
  79. FROM orders
  80. WHERE ownership_id = ?
  81. ''', (ownership_id,))
  82. if not model.cursor.fetchone()[0]:
  83. raise AssertionError('The bot should have at most one order.')
  84. model.cursor.execute('''
  85. SELECT rowid, ordered_amount, expiry_dt
  86. FROM orders
  87. WHERE ownership_id = ?
  88. -- no need for ORDER since the bot should have only one order
  89. UNION ALL
  90. SELECT * FROM (
  91. SELECT NULL, ordered_amount, expiry_dt
  92. FROM order_history
  93. WHERE ownership_id = ?
  94. ORDER BY rowid DESC -- equivalent to ordering by time created
  95. )
  96. LIMIT 1
  97. ''', (ownership_id, ownership_id,))
  98. data = model.cursor.fetchall()
  99. if not data:
  100. return place_order(ownable_id)
  101. my_last_order = data[0]
  102. last_order_open = my_last_order[0] is not None
  103. last_order_id = my_last_order[0]
  104. last_amount = my_last_order[1]
  105. expiry = my_last_order[2]
  106. dt_order_placed = datetime.strptime(expiry, '%Y-%m-%d %H:%M:%S') - timedelta(minutes=DEFAULT_ORDER_EXPIRY)
  107. model.cursor.execute('''
  108. SELECT COALESCE(SUM(amount), 0) >= 2 * ?
  109. FROM transactions
  110. WHERE ownable_id = ?
  111. AND dt > ? -- interestingly >= would be problematic
  112. ''', (last_amount, ownable_id, dt_order_placed))
  113. if model.cursor.fetchone()[0]:
  114. if last_order_open:
  115. model.delete_order(last_order_id, 'Canceled')
  116. return place_order(ownable_id)
  117. return False
  118. def main():
  119. """the initial part of the trading bot algorithm"""
  120. if model.get_user_orders(model.bank_id()):
  121. raise AssertionError('The trading bot already has some orders.')
  122. if input('Are you sure you want to place the initial orders? (type in "yes" or something else):') == 'yes':
  123. for ownable_id in model.ownable_ids():
  124. if ownable_id != model.currency_id():
  125. place_order(ownable_id)
  126. else:
  127. print('Not placing orders.')
  128. model.cleanup()
  129. if __name__ == '__main__':
  130. main()