201 lines
7.6 KiB
Python
201 lines
7.6 KiB
Python
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
|