def sellOutOptions(self): """ METHOD SELLS OUT OPTIONS IF ONE DAY BEFORE EXPIRATION """ open_positions = self.open_positions.find({ "Trader": self.user["Name"], "Asset_Type": "OPTION" }) dt = getDatetime() for position in open_positions: day_before = (position["Exp_Date"] - timedelta(days=1)).strftime("%Y-%m-%d") if day_before == dt.strftime("%Y-%m-%d"): trade_data = { "Symbol": position["Symbol"], "Pre_Symbol": position["Pre_Symbol"], "Side": "SELL_TO_CLOSE", "Aggregation": position["Aggregation"], "Strategy": position["Strategy"], "Asset_Type": position["Asset_Type"], "Exp_Date": position["Exp_Date"] } self.placeOrder(trade_data, position)
def buyOrder(self, symbol): try: aggregation = symbol["Aggregation"] strategy = symbol["Strategy"] symbol = symbol["Symbol"] resp = self.tdameritrade.getQuote(symbol) price = float(resp[symbol]["lastPrice"]) shares = 1 obj = { "Symbol": symbol, "Qty": shares, "Buy_Price": price, "Date": getDatetime(), "Strategy": strategy, "Aggregation": aggregation } # ADD TO OPEN POSITIONS self.open_positions.insert_one(obj) # print("BUY") # pprint(obj) except Exception as e: print("SIM TRADER - buyOrder", e)
def sellOrder(self, symbol, position): try: aggregation = symbol["Aggregation"] strategy = symbol["Strategy"] symbol = symbol["Symbol"] qty = position["Qty"] position_price = position["Buy_Price"] position_date = position["Date"] resp = self.tdameritrade.getQuote(symbol) price = float(resp[symbol]["lastPrice"]) sell_price = round(price * qty, 2) buy_price = round(position_price * qty, 2) if buy_price != 0: rov = round(((sell_price / buy_price) - 1) * 100, 2) else: rov = 0 obj = { "Symbol": symbol, "Qty": qty, "Buy_Price": position_price, "Buy_Date": position_date, "Sell_Price": price, "Sell_Date": getDatetime(), "Strategy": strategy, "Aggregation": aggregation, "ROV": rov } # ADD TO CLOSED POSITIONS self.closed_positions.insert_one(obj) # REMOVE FROM OPEN POSITIONS self.open_positions.delete_one({ "Symbol": symbol, "Strategy": strategy }) # print("SELL") # pprint(obj) except Exception as e: print("SIM TRADER - sellOrder", e)
def updateSystemInfo(self): system = list(self.mongo.system.find({}))[0] self.mongo.system.update_one({"_id": ObjectId(system["_id"])}, { "$set": { "Threads_Running": threading.active_count(), "Last_Updated": getDatetime() } })
def killQueueOrder(self): """ METHOD QUERIES ORDERS IN QUEUE AND LOOKS AT INSERTION TIME. IF QUEUE ORDER INSERTION TIME GREATER THAN TWO HOURS, THEN THE ORDER IS CANCELLED. """ # CHECK ALL QUEUE ORDERS AND CANCEL ORDER IF GREATER THAN TWO HOURS OLD queue_orders = self.queue.find({ "Trader": self.user["Name"], "Asset_Type": self.asset_type, "Account_ID": self.account_id }) dt = datetime.now(tz=pytz.UTC).replace(microsecond=0) dt_central = dt.astimezone(pytz.timezone('US/Central')) two_hours_ago = datetime.strptime( datetime.strftime(dt_central - timedelta(hours=2), "%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S") ten_minutes_ago = datetime.strptime( datetime.strftime(dt_central - timedelta(minutes=10), "%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S") for order in queue_orders: order_date = order["Date"] order_type = order["Order_Type"] id = order["Order_ID"] forbidden = ["REJECTED", "CANCELED", "FILLED"] if two_hours_ago > order_date and ( order_type == "BUY" or order_type == "BUY_TO_OPEN" ) and id != None and order["Order_Status"] not in forbidden: # FIRST CANCEL ORDER resp = self.tdameritrade.cancelOrder(id) if resp.status_code == 200 or resp.status_code == 201: other = { "Symbol": order["Symbol"], "Order_Type": order["Order_Type"], "Order_Status": "CANCELED", "Strategy": order["Strategy"], "Account_ID": self.account_id, "Aggregation": order["Aggregation"], "Trader": self.user["Name"], "Date": getDatetime() } if self.asset_type == "OPTION": other["Pre_Symbol"] = order["Pre_Symbol"] other["Exp_Date"] = order["Exp_Date"] self.other.insert_one(other) self.queue.delete_one({ "Trader": self.user["Name"], "Symbol": order["Symbol"], "Strategy": order["Strategy"], "Asset_Type": self.asset_type }) self.logger.INFO( f"CANCELED ORDER FOR {order['Symbol']} - TRADER: {self.user['Name']}", True) # IF QUEUE ORDER DATE GREATER THAN 10 MINUTES OLD AND ORDER ID EQUALS NONE, SEND ALERT if ten_minutes_ago > order_date and order[ "Order_ID"] == None and order[ "Account_ID"] == self.account_id: if order["Symbol"] not in self.no_ids_list: self.logger.ERROR( "QUEUE ORDER ID ERROR", f"ORDER ID FOR {order['Symbol']} NOT FOUND - TRADER: {self.user['Name']} - ACCOUNT ID: {self.account_id}" ) self.no_ids_list.append(order["Symbol"]) else: if order["Symbol"] in self.no_ids_list: self.no_ids_list.remove(order["Symbol"])
def placeOrder(self, trade_data, position_data=None, orderType="LIMIT"): """ METHOD IS USED TO PLACE TRADES (BUY/SELL ORDER). Args: trade_data ([dict]): [CONSISTS OF TRADE DATA FOR EACH SYMBOL (SYMBOL, STRATEGY, AGGREGATION, ASSET TYPE, ACCOUNT ID, ORDER TYPE)] position_data ([dict], optional): [CONSISTS OF OPEN POSITION DATA IF SELL ORDER]. Defaults to None. orderType (str, optional): [EITHER A LIMIT ORDER OR MARKET ORDER]. Defaults to "LIMIT". """ symbol = trade_data["Symbol"] side = trade_data["Side"] aggregation = trade_data["Aggregation"] strategy = trade_data["Strategy"] asset_type = trade_data["Asset_Type"] resp = self.tdameritrade.getQuote(symbol) if asset_type == "EQUITY": price = float(resp[symbol]["lastPrice"]) if orderType == "LIMIT": duration = "GOOD_TILL_CANCEL" session = "SEAMLESS" else: duration = "DAY" session = "NORMAL" if side == "BUY": price = price + (price * self.limit_offset) elif side == "SELL": price = price - (price * self.limit_offset) if price < 1: price = round(price, 4) else: price = round(price, 2) elif asset_type == "OPTION": price = round(float(resp[symbol]["mark"]), 2) duration = "DAY" session = "NORMAL" symbol = trade_data["Pre_Symbol"] order = { "orderType": orderType, "session": session, "price": price, "duration": duration, "orderStrategyType": "SINGLE", "orderLegCollection": [{ "instruction": side, "quantity": None, "instrument": { "symbol": symbol, "assetType": asset_type } }] } if orderType == "MARKET": del order["price"] obj = { "Symbol": symbol, "Qty": None, "Date": getDatetime(), "Strategy": strategy, "Aggregation": aggregation, "Trader": self.user["Name"], "Order_ID": None, "Order_Status": None, "Asset_Type": asset_type, "Order_Type": side, "Account_ID": self.account_id } if side == "BUY" or side == "BUY_TO_OPEN": # GET SHARES FOR PARTICULAR STRATEGY strategies = self.user["Accounts"][self.account_id]["Strategies"] shares = int(strategies[strategy]["Shares"]) active_strategy = strategies[strategy]["Active"] if active_strategy and shares > 0: if asset_type == "EQUITY": min_price = self.user["Accounts"][ self.account_id]["Price_Range"]["Min"] max_price = self.user["Accounts"][ self.account_id]["Price_Range"]["Max"] if price < min_price or price > max_price: return order["orderLegCollection"][0]["quantity"] = shares obj["Limit_Price"] = price obj["Last_Price"] = price obj["Qty"] = shares else: return elif side == "SELL" or side == "SELL_TO_CLOSE": buy_qty = position_data["Qty"] buy_price = position_data["Buy_Price"] buy_date = position_data["Date"] order["orderLegCollection"][0]["quantity"] = buy_qty obj["Limit_Price"] = price obj["Buy_Price"] = buy_price obj["Buy_Date"] = buy_date obj["Qty"] = buy_qty if asset_type == "OPTION": obj["Pre_Symbol"] = trade_data["Pre_Symbol"] obj["Exp_Date"] = trade_data["Exp_Date"] # PLACE ORDER ################################################ resp = self.tdameritrade.placeTDAOrder(order) status_code = resp.status_code acceptable_status = [200, 201] if status_code not in acceptable_status: other = { "Symbol": symbol, "Order_Type": side, "Order_Status": "REJECTED", "Strategy": strategy, "Aggregation": aggregation, "Trader": self.user["Name"], "Date": getDatetime(), "Asset_Type": asset_type, "Account_ID": self.account_id } self.logger.INFO(f"{symbol} REJECTED For {self.user['Name']}", True) if asset_type == "OPTION": pprint(resp.json()) print(resp.status_code) other["Pre_Symbol"] = trade_data["Pre_Symbol"] other["Exp_Date"] = trade_data["Exp_Date"] self.other.insert_one(other) return # GETS ORDER ID FROM RESPONSE HEADERS LOCATION obj["Order_ID"] = int( (resp.headers["Location"]).split("/")[-1].strip()) obj["Order_Status"] = "QUEUED" self.queueOrder(obj) response_msg = f"{side} ORDER RESPONSE: {resp.status_code} - SYMBOL: {symbol} - TRADER: {self.user['Name']} - ASSET TYPE: {asset_type} - ACCOUNT ID: {self.account_id}" self.logger.INFO(response_msg)
def pushOrder(self, queue_order, spec_order): """ METHOD PUSHES ORDER TO EITHER OPEN POSITIONS OR CLOSED POSITIONS COLLECTION IN MONGODB. IF BUY ORDER, THEN PUSHES TO OPEN POSITIONS. IF SELL ORDER, THEN PUSHES TO CLOSED POSITIONS. Args: queue_order ([dict]): [QUEUE ORDER DATA FROM QUEUE] spec_order ([dict(json)]): [ORDER DATA FROM TDAMERITRADE] """ symbol = queue_order["Symbol"] shares = int(spec_order["quantity"]) price = spec_order["orderActivityCollection"][0]["executionLegs"][0][ "price"] if price < 1: price = round(price, 4) else: price = round(price, 2) strategy = queue_order["Strategy"] aggregation = queue_order["Aggregation"] asset_type = queue_order["Asset_Type"] order_type = queue_order["Order_Type"] account_id = queue_order["Account_ID"] obj = { "Symbol": symbol, "Strategy": strategy, "Aggregation": aggregation, "Trader": self.user["Name"], "Asset_Type": asset_type, "Account_ID": account_id } if asset_type == "OPTION": obj["Pre_Symbol"] = queue_order["Pre_Symbol"] obj["Exp_Date"] = queue_order["Exp_Date"] if order_type == "BUY" or order_type == "BUY_TO_OPEN": obj["Qty"] = shares obj["Buy_Price"] = price obj["Last_Price"] = price obj["High_Price"] = price obj["Opening_Price"] = price obj["Date"] = getDatetime() # ADD TO OPEN POSITIONS try: self.open_positions.insert_one(obj) except writeConcernError: self.logger.ERROR( f"INITIAL FAIL OF INSERTING OPEN POSITION FOR SYMBOL {symbol} - DATE/TIME: {getDatetime()} - DATA: {obj} - writeConcernError" ) self.open_positions.insert_one(obj) except writeError: self.logger.ERROR( f"INITIAL FAIL OF INSERTING OPEN POSITION FOR SYMBOL {symbol} - DATE/TIME: {getDatetime()} - DATA: {obj} - writeError" ) self.open_positions.insert_one(obj) except Exception: self.logger.ERROR() msg = f"____ \n Side: {order_type} \n Symbol: {symbol} \n Qty: {shares} \n Price: ${price} \n Strategy: {strategy} \n Aggregation: {aggregation} \n Date: {getDatetime()} \n Asset Type: {asset_type} \n Trader: {self.user['Name']} \n" self.logger.INFO( f"{order_type} ORDER For {symbol} - TRADER: {self.user['Name']}", True) elif order_type == "SELL" or order_type == "SELL_TO_CLOSE": position = self.open_positions.find_one({ "Trader": self.user["Name"], "Symbol": symbol, "Strategy": strategy }) obj["Qty"] = position["Qty"] obj["Buy_Price"] = position["Buy_Price"] obj["Buy_Date"] = position["Date"] obj["Sell_Price"] = price obj["Sell_Date"] = getDatetime() obj["High_Price"] = position["High_Price"] sell_price = round(price * position["Qty"], 2) buy_price = round(position["Buy_Price"] * position["Qty"], 2) if buy_price != 0: rov = round(((sell_price / buy_price) - 1) * 100, 2) else: rov = 0 if rov > 0 or sell_price - buy_price > 0: sold_for = "GAIN" elif rov < 0 or sell_price - buy_price < 0: sold_for = "LOSS" else: sold_for = "NONE" obj["ROV"] = rov msg = f"____ \n Side: {order_type} \n Symbol: {symbol} \n Qty: {position['Qty']} \n Buy Price: ${position['Buy_Price']} \n Buy Date: {position['Date']} \n Sell Price: ${price} \n Sell Date: {getDatetime()} \n Strategy: {strategy} \n Aggregation: {aggregation} \n ROV: {rov}% \n Sold For: {sold_for} \n Asset Type: {asset_type} \n Trader: {self.user['Name']} \n" # ADD TO CLOSED POSITIONS try: self.closed_positions.insert_one(obj) except writeConcernError: self.logger.ERROR( f"INITIAL FAIL OF INSERTING CLOSED POSITION FOR SYMBOL {symbol} - DATE/TIME: {getDatetime()} - DATA: {obj} - writeConcernError" ) self.closed_positions.insert_one(obj) except writeError: self.logger.ERROR( f"INITIAL FAIL OF INSERTING CLOSED POSITION FOR SYMBOL {symbol} - DATE/TIME: {getDatetime()} - DATA: {obj} - writeError" ) self.closed_positions.insert_one(obj) except Exception: self.logger.ERROR() # REMOVE FROM OPEN POSITIONS is_removed = self.open_positions.delete_one({ "Trader": self.user["Name"], "Symbol": symbol, "Strategy": strategy, "Asset_Type": self.asset_type }) try: if int(is_removed.deleted_count) == 0: self.logger.ERROR( f"INITIAL FAIL OF DELETING OPEN POSITION FOR SYMBOL {symbol} - DATE/TIME: {getDatetime()} - DATA: {obj}" ) self.open_positions.delete_one({ "Trader": self.user["Name"], "Symbol": symbol, "Strategy": strategy, "Asset_Type": self.asset_type }) except Exception: self.logger.ERROR() self.logger.INFO( f"{order_type} ORDER For {symbol} - TRADER: {self.user['Name']}", True) # REMOVE FROM QUEUE self.queue.delete_one({ "Trader": self.user["Name"], "Symbol": symbol, "Strategy": strategy, "Asset_Type": asset_type, "Account_ID": self.account_id }) self.push.send(msg)
def updateStatus(self): """ METHOD QUERIES THE QUEUED ORDERS AND USES THE ORDER ID TO QUERY TDAMERITRADES ORDERS FOR ACCOUNT TO CHECK THE ORDERS CURRENT STATUS. INITIALLY WHEN ORDER IS PLACED, THE ORDER STATUS ON TDAMERITRADES END IS SET TO WORKING OR QUEUED. THREE OUTCOMES THAT I AM LOOKING FOR ARE FILLED, CANCELED, REJECTED. IF FILLED, THEN QUEUED ORDER IS REMOVED FROM QUEUE AND THE pushOrder METHOD IS CALLED. IF REJECTED OR CANCELED, THEN QUEUED ORDER IS REMOVED FROM QUEUE AND SENT TO OTHER COLLECTION IN MONGODB. """ queued_orders = self.queue.find({ "Trader": self.user["Name"], "Order_ID": { "$ne": None }, "Asset_Type": self.asset_type, "Account_ID": self.account_id }) for queue_order in queued_orders: spec_order = self.tdameritrade.getSpecificOrder( queue_order["Order_ID"]) new_status = spec_order["status"] order_type = queue_order["Order_Type"] # CHECK IF QUEUE ORDER ID EQUALS TDA ORDER ID if queue_order["Order_ID"] == spec_order["orderId"]: if new_status == "FILLED": self.pushOrder(queue_order, spec_order) elif new_status == "CANCELED" or new_status == "REJECTED": # REMOVE FROM QUEUE self.queue.delete_one({ "Trader": self.user["Name"], "Symbol": queue_order["Symbol"], "Strategy": queue_order["Strategy"], "Asset_Type": self.asset_type, "Account_ID": self.account_id }) other = { "Symbol": queue_order["Symbol"], "Order_Type": order_type, "Order_Status": new_status, "Strategy": queue_order["Strategy"], "Aggregation": queue_order["Aggregation"], "Trader": self.user["Name"], "Date": getDatetime(), "Asset_Type": queue_order["Asset_Type"], "Account_ID": self.account_id } if self.asset_type == "OPTION": other["Pre_Symbol"] = queue_order["Pre_Symbol"] other["Exp_Date"] = queue_order["Exp_Date"] self.other.insert_one(other) self.logger.INFO( f"{new_status.upper()} ORDER For {queue_order['Symbol']} - TRADER: {self.user['Name']}", True) else: self.queue.update_one( { "Trader": self.user["Name"], "Symbol": queue_order["Symbol"], "Strategy": queue_order["Strategy"], "Asset_Type": self.asset_type }, {"$set": { "Order_Status": new_status }})
tm = dt_central.strftime("%H:%M:%S") weekdays = ["Sat", "Sun"] # IF CURRENT TIME GREATER THAN 8PM AND LESS THAN 4AM, OR DAY IS WEEKEND, THEN RETURN 60 SECONDS if tm > "20:00" or tm < "04:00" or day in weekdays: return 60 # ELSE RETURN 5 SECONDS return 5 main = Main() # UPDATE SYSTEM RUN DATETIME FIELD TO CURRENT DATETIME # THIS TELLS US WHEN THE SYSTEM FIRST STARTED UP system = list(main.mongo.system.find({}))[0] main.mongo.system.update_one({"_id": ObjectId(system["_id"])}, {"$set": { "Run_Start": getDatetime() }}) while True: main.run() main.updateSystemInfo() time.sleep(selectSleep())
day = dt_central.strftime("%a") tm = dt_central.strftime("%H:%M:%S") weekdays = ["Sat", "Sun"] # IF CURRENT TIME GREATER THAN 8PM AND LESS THAN 4AM, OR DAY IS WEEKEND, THEN RETURN 60 SECONDS if tm > "20:00" or tm < "04:00" or day in weekdays: return 60 # ELSE RETURN 5 SECONDS return 5 main = Main() # UPDATE SYSTEM RUN DATETIME FIELD TO CURRENT DATETIME # THIS TELLS US WHEN THE SYSTEM FIRST STARTED UP system = list(main.mongo.system.find({}))[0] main.mongo.system.update_one({"_id": ObjectId(system["_id"])}, { "$set": {"Run_Start": getDatetime()}}) while True: main.run() main.updateSystemInfo() time.sleep(selectSleep())