trading_bot.py 4.8 KB

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