import json import requests from datetime import datetime class Trade: def __init__(self, symbol, side, order_type, position_size, price): """ :param symbol: The symbol of the trading pair. :param side: BUY|SELL :param order_type: LIMIT|MARKET :param position_size: Value in USD :param price: Asking price for LIMIT orders. """ # Returns a datetime object containing the local date and time self.timestamp = datetime.now() self.symbol = symbol self.side = side self.order_type = order_type self.position_size = position_size # Flag set once order is placed successfully. self.order_placed = False # Order info returned when order is placed. self.order = None # Flag set once order is filled self.order_filled = False # This flag is set when the trade is closed out. self.trade_closed = False # Opening value of the asset. self.opening_price = price # Current value self.value = 0 # Profit or Loss self.profit_loss = 0 # Profit or Loss in percentage. self.pl_percentage = 0 def update(self, current_price): # Utility function. def percent(part, whole): if whole == 0: return 0 pct = 100 * float(part) / float(whole) return pct # Set the current value and profit/loss initial_value = self.position_size * self.opening_price self.value = self.position_size * current_price if self.side == 'buy': self.profit_loss = self.value - initial_value else: self.profit_loss = initial_value - self.value self.pl_percentage = percent(self.profit_loss, initial_value) return self.profit_loss class Trades: def __init__(self, client): """ :param client: The socket connection to the exchange. """ # Socket connection to the exchange. self.client = client # For automating limit orders offset the current price by 1/100 percent. self.offset_amount = 0.0001 # Exchange fees. Maybe these can be fetched from the server? self.exchange_fees = {'maker': 0.01, 'taker': 0.05} # Hedge mode allows long and shorts to be placed simultaneously. self.hedge_mode = False # If hedge mode is disabled this is either {'buy','sell'}. self.side = None # A list of unfilled trades. self.unfilled_trades = [] # A list of filled trades. self.filled_trades = [] # A completed trades. self.closed_trades = [] # Number of open trades. self.num_trades = 0 # The quantity sum of all open trades. self.total_position = 0 # The value of all open trades in USD. self.total_position_value = 0 # Info on all futures symbols self.info = client.futures_exchange_info() # Dictionary of places the exchange requires after the decimal for all symbols. self.symbols_n_precision = {} for item in self.info['symbols']: self.symbols_n_precision[item['symbol']] = item['quantityPrecision'] def update(self, cdata): r_update = [] # Check if any unfilled orders are now filled. for trade in self.unfilled_trades: if not trade.order_filled: order = self.client.get_order(symbol=trade.symbol, orderId=trade.order['orderId']) if order['status'] == 'FILLED': trade.order_filled = True self.filled_trades.append(trade) r_update.append({trade.timestamp: 'filled'}) # Delete filled trades from unfilled_trades self.unfilled_trades[:] = (t for t in self.unfilled_trades if t.order_filled is False) for trade in self.filled_trades: # Update the open trades. ret = trade.update(cdata['close']) r_update.append({trade.timestamp: {'pl': ret}}) # If a trade has been closed... if trade.trade_closed: self.closed_trades.append(trade) # Notify caller r_update.append({trade.timestamp: 'closed'}) # Delete closed trades from filled_trades self.filled_trades[:] = (t for t in self.filled_trades if t.trade_closed is False) return r_update def new_trade(self, symbol, side, order_type, usd, price=None): # If hedge mode is disabled return False if trade is on opposite side. if self.hedge_mode is False: if self.side is None: self.side = side if self.side != side: return False # If no price is given, set the asking price to be offset by a small amount. if price is None: offset = self.get_price(symbol) * self.offset_amount if side == 'buy': price = f'-{offset}' else: price = f'+{offset}' # A relative limit order may be set by passing a string preceded with a +/- if type(price) == str: if ('+' in price) or ('-' in price): price = self.get_price(symbol) + float(price) else: price = float(price) position_size = usd / price # The required level of precision for this trading pair. precision = self.symbols_n_precision[symbol] # String representing the order amount formatted to the level of precision defined above. order_amount = "{:0.0{}f}".format(position_size, precision) # Create a trade and place the order. trade = Trade(symbol, side, order_type, order_amount, price) order = self.place_order(trade) # If the order successfully placed store the trade. if order: # Set th order placed flag. trade.order_placed = True # Save the order info in the trade object. trade.order = order # Update the trades instance. self.num_trades += 1 self.unfilled_trades.append(trade) print(order) return trade else: return False @staticmethod def get_price(symbol): # Todo: Make sure im getting the correct market price for futures. request = requests.get(f'https://api.binance.com/api/v3/ticker/price?symbol={symbol}') json_obj = json.loads(request.text) return json_obj['price'] def place_order(self, trade): if trade.order_type == 'MARKET': try: order = self.client.create_test_order(symbol=trade.symbol, side=trade.side, type=trade.order_type, quantity=trade.position_size) print('!!!Order created!!!') return order # Report error if order fails except Exception as e: print(e, "error") return None elif trade.order_type == 'LIMIT': try: order = self.client.create_test_order( symbol=trade.symbol, side=trade.side, type=trade.order_type, timeInForce='GTC', quantity=trade.position_size, price=trade.opening_price) return order # If order fails except Exception as e: print(e, "error") return None else: print(f'Trade: No Implementation for trade.order: {trade.order_type}') return None