brighter-trading/trade.py

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