├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── algorithmScaling.py ├── clients.py ├── controller.py ├── deribit_V2_API_Websocket.py ├── deribit_api.py ├── gatherData.py ├── generateSignals.py ├── main.py ├── order.py ├── orderManagement.py ├── positionManagement.py └── requirements.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.paypal.com/donate?hosted_button_id=WAJRVR3TBB4FN'] 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .idea 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a Algo/script trading for Deribit. 2 | 3 | You need an account with deribit, to test out the code I would recommend you make an account on the testnet environment first 4 | 5 | - Download / Clone 6 | - create a Virtual environment 7 | - install the websocket dependency in "requirements.txt" 8 | - add creds in client.py - default is the test environment test.deribit 9 | - RUN from main.py 10 | 11 | ## Check the video below for more details: 12 | 13 | [![Watch the video](https://img.youtube.com/vi/KqKqdvqhSCA/0.jpg)](https://www.youtube.com/watch?v=KqKqdvqhSCA) 14 | 15 | 16 | ## [Subscribe](https://www.youtube.com/channel/UCsQqV_wq5yPrw5YIpvwmjvQ) to my youtube channel to find similar content. 17 | 18 | ## update test.deribit.com is having some issues with SSL certificates on their side - I assume the fix will be done soon - 30/12/2021 - 08:24am -------------------------------------------------------------------------------- /algorithmScaling.py: -------------------------------------------------------------------------------- 1 | from gatherData import GatherData 2 | from generateSignals import GenerateSignals 3 | from orderManagement import OrderManagement 4 | from positionManagement import PositionManagement 5 | from order import Order 6 | from decimal import Decimal 7 | import math 8 | 9 | 10 | class AlgorithmScaling(): 11 | def __init__(self): 12 | self.counterSinceOrderWasMade = 0 13 | self.orderManagementObject = None 14 | self.indiceName = "BTC-PERPETUAL" 15 | self.fundingAmount = 20 16 | # new tick size put in place 17 | self.differenceInPrice = Decimal(0.5) 18 | 19 | self.exitingOrder = False 20 | self.enteringOrder = False 21 | 22 | self.timeLimit = 30 23 | # these two below change base on how much money we have in our account 24 | self.numberOfScalingOrders = 24 25 | # the price difference between each order -- additional functionality added to increase spread 26 | self.priceMovementUnit = 2 27 | # used to multiple by the price to increase the spread 28 | self.rateOfChange = 12 29 | 30 | # turn into none 31 | self.orderManagementObject = None 32 | # turn into none // trade cycles set to two 33 | self.numberOfTradeCyclesLimit = 200 34 | self.numberOfTradeCycles = 0 35 | 36 | self.emptyPosition = True 37 | 38 | self.orders = {"sell":[], "buy":[]} 39 | self.listOfMapOfOrdersExit = [] 40 | self.listOfMapOfOrdersEnter = [] 41 | 42 | self.exitingSize = 10 43 | 44 | 45 | 46 | def run(self, data, mapOfSignals, positionManagementObject, orderManagementObject): 47 | 48 | if not isinstance(orderManagementObject, OrderManagement): return 49 | if not isinstance(positionManagementObject, PositionManagement): return 50 | 51 | self.orders = {"sell": [], "buy": []} 52 | self.orderManagementObject = orderManagementObject 53 | 54 | ''' 55 | Order checking 56 | ''' 57 | # check if orders are present for exiting 58 | mapOfOutcomesExit = self.orderPresent(listOfOrdersMaps=self.listOfMapOfOrdersExit, switchIfOrderIsPresent=self.exitingOrder) 59 | 60 | ''' 61 | Order exit/enter order canceling 62 | ''' 63 | # if all the exiting orders are filled then remove the entering orders 64 | if mapOfOutcomesExit["fullyFilled"]: 65 | 66 | # here check our position 67 | currentPositionStatus = positionManagementObject.findOurCurrentPosition() 68 | if (currentPositionStatus != None) and ("error" in currentPositionStatus): return 69 | self.emptyPosition = currentPositionStatus == None 70 | 71 | # exit order has been completed 72 | self.exitingOrder = False 73 | 74 | # if we still have no position right now then go ahead and exit all our orders that are resting 75 | if self.emptyPosition: 76 | self.removeAllOrdersAndResetFlags() 77 | ''' 78 | Order exit/enter order canceling 79 | ''' 80 | # check if orders are present for entering 81 | mapOfOutcomesEnter = self.orderPresent(listOfOrdersMaps=self.listOfMapOfOrdersEnter, switchIfOrderIsPresent=self.enteringOrder) 82 | ''' 83 | Order checking and editing above the if statement 84 | ''' 85 | 86 | 87 | ''' 88 | Position checking 89 | ''' 90 | currentPositionStatus = positionManagementObject.findOurCurrentPosition() 91 | if (currentPositionStatus != None) and ("error" in currentPositionStatus): return 92 | self.emptyPosition = currentPositionStatus == None 93 | 94 | mapOfExitSignals = { 95 | "buy" : False, 96 | "sell" : False 97 | } 98 | if currentPositionStatus != None: 99 | spread = data["ask"] - data["bid"] 100 | if currentPositionStatus["direction"] == "sell": 101 | if (data["ask"]+spread) < currentPositionStatus["average_price"]: 102 | mapOfExitSignals["buy"] = True 103 | elif currentPositionStatus["direction"] == "buy": 104 | if (data["bid"]-spread) > currentPositionStatus["average_price"]: 105 | mapOfExitSignals["sell"] = True 106 | ''' 107 | Position checking 108 | ''' 109 | 110 | ''' 111 | Order checking 112 | ''' 113 | 114 | ''' 115 | Order exit order canceling 116 | ''' 117 | self.counterSinceOrderWasMade += 1 118 | # check if we need to cancel anything 119 | if self.counterSinceOrderWasMade > self.timeLimit: 120 | # remove the exit positions cancel 121 | # if self.exitingOrder: 122 | self.cancelTakeOutPositionOrders() 123 | self.exitingOrder = False 124 | self.counterSinceOrderWasMade = 0 125 | 126 | ''' 127 | The enter orders did not hit and the price moved away------- 128 | ''' 129 | if self.emptyPosition: 130 | self.removeAllOrdersAndResetFlags() 131 | 132 | ''' 133 | Order exit order canceling 134 | ''' 135 | 136 | # remove the previous list of orders 137 | if len(self.listOfMapOfOrdersExit) > 2: del(self.listOfMapOfOrdersExit[0]) 138 | if len(self.listOfMapOfOrdersEnter) > 2: del(self.listOfMapOfOrdersEnter[0]) 139 | 140 | # if we find a buy signal and we are not in a position and we have not made any orders to enter 141 | intialStartBuy = mapOfSignals["buy"] and self.emptyPosition and (not self.enteringOrder) 142 | # if we find a buy signal and we are in a position and the position is a short and we have not made any orders to exit 143 | exitPositionBuy = mapOfExitSignals["buy"] and (not self.emptyPosition) and currentPositionStatus["direction"] == "sell" and (not self.exitingOrder) 144 | 145 | # if we find a sell signal and we are not in a position and we have not made any orders to enter 146 | intialStartSell = mapOfSignals["sell"] and self.emptyPosition and (not self.enteringOrder) 147 | # if we find a sell signal and we are in a position and the position is a long and we have not made any orders to exit 148 | exitPositionSell = mapOfExitSignals["sell"] and (not self.emptyPosition) and currentPositionStatus["direction"] == "buy" and (not self.exitingOrder) 149 | 150 | 151 | if intialStartBuy or intialStartSell: 152 | # check the number of trade cycles that have occurred 153 | self.exitClause() 154 | print("out of position entering both buy and sell orders") 155 | self.enterOrderScaling(side="buy", size=self.fundingAmount, price=data["bid"], listOfMapOrders=self.listOfMapOfOrdersEnter) 156 | self.enterOrderScaling(side="sell", size=self.fundingAmount, price=data["ask"], listOfMapOrders=self.listOfMapOfOrdersEnter) 157 | self.enteringOrder = True 158 | 159 | self.counterSinceOrderWasMade = 0 160 | 161 | elif exitPositionBuy or exitPositionSell: 162 | if exitPositionSell: 163 | # reverse the side as we are exiting the position 164 | print("in position") 165 | self.exitOrderScaling(side="sell", price=data["ask"], orderStatus=currentPositionStatus, listOfMapOrders=self.listOfMapOfOrdersExit) 166 | self.exitingOrder = True 167 | 168 | elif exitPositionBuy: 169 | # reverse the side as we are exiting the position 170 | print("in position") 171 | self.exitOrderScaling(side="buy", price=data["bid"], orderStatus=currentPositionStatus, listOfMapOrders=self.listOfMapOfOrdersExit) 172 | self.exitingOrder = True 173 | 174 | self.counterSinceOrderWasMade = 0 175 | 176 | 177 | 178 | # -----------------------when entering an order we have times our sizes by 10 to increase the size but the price distribution is still the same 179 | def enterOrderScaling(self, side, size, price, listOfMapOrders): 180 | newPrice = price 181 | newSize = size 182 | 183 | for counter in range(self.numberOfScalingOrders): 184 | if side == "buy": 185 | deribitOrderObject=self.orderManagementObject.buyOrder(indiceName=self.indiceName, size= newSize ,price=newPrice) 186 | self.createACustomOrderObject(orderList=self.orders["buy"], order=deribitOrderObject) 187 | # new functionality orders are scaled up but not exponentially 188 | rateOfScalingOrders = self.rateOfChange * counter 189 | newPrice= newPrice - (self.priceMovementUnit + rateOfScalingOrders) 190 | # new functionality orders are scaled up but not exponentially 191 | 192 | elif side == "sell": 193 | deribitOrderObject = self.orderManagementObject.sellOrder(indiceName=self.indiceName, size= newSize , price=newPrice) 194 | self.createACustomOrderObject(orderList=self.orders["sell"], order=deribitOrderObject) 195 | # new functionality orders are scaled up but not exponentially 196 | rateOfScalingOrders = self.rateOfChange * counter 197 | newPrice = newPrice + (self.priceMovementUnit + rateOfScalingOrders) 198 | # new functionality orders are scaled up but not exponentially 199 | 200 | listOfMapOrders.append(self.orders) 201 | 202 | 203 | 204 | 205 | def exitOrderScaling(self, side, price, orderStatus, listOfMapOrders): 206 | newPrice = price 207 | newSize = self.exitingSize 208 | numberOrders = int(abs(orderStatus["size"])/newSize) 209 | 210 | for counter in range(numberOrders): 211 | 212 | if side == "buy": 213 | deribitOrderObject=self.orderManagementObject.buyOrder(indiceName=self.indiceName, size=newSize,price=newPrice, reduce_only=True) 214 | self.createACustomOrderObject(orderList=self.orders["buy"], order=deribitOrderObject) 215 | # newPrice -= self.differenceInPrice 216 | newPrice = newPrice - self.differenceInPrice 217 | elif side == "sell": 218 | deribitOrderObject = self.orderManagementObject.sellOrder(indiceName=self.indiceName, size=newSize, price=newPrice, reduce_only=True) 219 | self.createACustomOrderObject(orderList=self.orders["sell"], order=deribitOrderObject) 220 | # newPrice += self.differenceInPrice 221 | newPrice = newPrice + self.differenceInPrice 222 | 223 | 224 | listOfMapOrders.append(self.orders) 225 | 226 | 227 | def orderPresent(self, listOfOrdersMaps, switchIfOrderIsPresent): 228 | 229 | mapOfStatus = {"open": True, "fullyFilled": True} 230 | # if it is false then we can deduce that there are no orders present, however if true we only work out what orders are still 231 | if switchIfOrderIsPresent: 232 | listOfOrders = listOfOrdersMaps[-1]["sell"] if listOfOrdersMaps[-1]["sell"] else listOfOrdersMaps[-1]["buy"] 233 | 234 | outcome = self.orderManagementObject.orderStatus(listOfOrders=listOfOrders, instrument=self.indiceName) 235 | if outcome["open"] or outcome["partFilled"]: 236 | mapOfStatus = {"open": True, "fullyFilled": False} 237 | return mapOfStatus 238 | 239 | # assume that cancelled and fullyFilled will be True and that works for what we have 240 | mapOfStatus = {"open": False, "fullyFilled": True} 241 | return mapOfStatus 242 | return {"open": False, "fullyFilled":False} 243 | 244 | 245 | def createACustomOrderObject(self, orderList, order): 246 | customOrder = Order() 247 | if order == None: 248 | return 249 | customOrder.setOrder(order) 250 | orderList.append(customOrder) 251 | 252 | 253 | def cancelTakeOutPositionOrders(self): 254 | # print("canceling exit position orders") 255 | listOfOrders = self.orderManagementObject.getOpenOrders(self.indiceName) 256 | for order in listOfOrders: 257 | if order["amount"] == (self.exitingSize): 258 | # takes in a custom order object 259 | orderObject = Order() 260 | orderObject.setOrder(order) 261 | self.orderManagementObject.cancelOrder(orderObject=orderObject) 262 | 263 | def removeAllOrdersAndResetFlags(self): 264 | self.orderManagementObject.cancelAll() 265 | # as there are no orders so they can't be in one 266 | self.exitingOrder = False 267 | self.enteringOrder = False 268 | 269 | 270 | 271 | 272 | def exitClause(self): 273 | self.numberOfTradeCycles += 1 274 | if self.numberOfTradeCycles >= self.numberOfTradeCyclesLimit: 275 | print("end program") 276 | exit(0) 277 | -------------------------------------------------------------------------------- /clients.py: -------------------------------------------------------------------------------- 1 | from deribit_api import RestClient 2 | 3 | class Clients: 4 | def __init__(self): 5 | pass 6 | 7 | 8 | def authenticate(self): 9 | # testing version 10 | url = "wss://test.deribit.com/ws/api/v2" 11 | # real 12 | # url = "wss://www.deribit.com/ws/api/v2" 13 | deribitClient = RestClient(key="client_id", secret="client_secret",url=url) 14 | 15 | return {"deribit": deribitClient, "url":url} 16 | 17 | 18 | -------------------------------------------------------------------------------- /controller.py: -------------------------------------------------------------------------------- 1 | from gatherData import GatherData 2 | from generateSignals import GenerateSignals 3 | from orderManagement import OrderManagement 4 | from positionManagement import PositionManagement 5 | from clients import Clients 6 | from algorithmScaling import AlgorithmScaling 7 | import time 8 | 9 | class Controller: 10 | def __init__(self): 11 | self.gatherDataObject = GatherData() 12 | self.generateSignalObject = GenerateSignals() 13 | self.orderManagementObject = OrderManagement() 14 | self.positionManagementObject = PositionManagement() 15 | self.client = Clients() 16 | self.algorithm = AlgorithmScaling() 17 | 18 | def run(self): 19 | clientMap = self.client.authenticate() 20 | self.gatherDataObject.setting_url(clientMap) 21 | self.positionManagementObject.setClients(clientMap) 22 | self.gatherDataObject.start() 23 | 24 | while(True): 25 | # modular objects, this way all objects are created externally and can be appened on 26 | data = self.gatherDataObject.run(clientMap) 27 | 28 | # problem with data access and waiting a while and resetting the connection would be a good idea 29 | if data == None: continue 30 | 31 | positionStatus = self.positionManagementObject.findOurCurrentPosition() 32 | 33 | if (positionStatus != None) and ("error" in positionStatus): continue 34 | 35 | self.orderManagementObject.run(clientMap) 36 | 37 | mapOfSignals = self.generateSignalObject.run(data,positionStatus, self.orderManagementObject) 38 | 39 | self.algorithm.run(data,mapOfSignals, self.positionManagementObject, self.orderManagementObject) -------------------------------------------------------------------------------- /deribit_V2_API_Websocket.py: -------------------------------------------------------------------------------- 1 | from websocket import create_connection, WebSocketConnectionClosedException 2 | import json 3 | import time 4 | 5 | class Deribitv2API(): 6 | 7 | def __init__(self): 8 | self.msg = \ 9 | {"jsonrpc": "2.0", 10 | "method": "public/subscribe", 11 | "id": 42, 12 | "params": { 13 | "channels": ["quote.BTC-PERPETUAL"]} 14 | } 15 | 16 | self.listOfNotifications = [] 17 | self.ws = None 18 | self.url = None 19 | 20 | def setting_url(self, url): 21 | self.url = url 22 | 23 | def startSocket(self): 24 | # try again after a failure 25 | while True: 26 | try: 27 | self.connect() 28 | self.listen() 29 | except Exception as e: 30 | self.endSocketDueToError(e) 31 | 32 | 33 | def connect(self): 34 | self.ws = create_connection(self.url) 35 | self.ws.send(json.dumps(self.msg)) 36 | 37 | def listen(self): 38 | # trigger whilst true keep listening 39 | while self.ws.connected: 40 | try: 41 | # puts all the incoming data into a list of a map 42 | mapData = json.loads(self.ws.recv()) 43 | self.listOfNotifications.append(mapData) 44 | except Exception as e: 45 | self.endSocketDueToError(e) 46 | return 47 | 48 | def disconnect(self): 49 | try: 50 | if self.ws.connected: 51 | self.ws.close() 52 | except WebSocketConnectionClosedException as e: 53 | print(str(e) + " error on closing") 54 | print("Socket Closed") 55 | # send the data to the logicalEngine 56 | 57 | def endSocketDueToError(self, e): 58 | print(e, " closing websocket") 59 | self.disconnect() 60 | 61 | def getListOfData(self): 62 | if len(self.listOfNotifications) > 1: 63 | self.listOfNotifications = self.listOfNotifications[-2:] 64 | # returns the last value in the list 65 | return self.listOfNotifications[-1] 66 | # since we have no data don't return anything 67 | return None 68 | -------------------------------------------------------------------------------- /deribit_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import time, hashlib, base64 4 | import json 5 | from collections import OrderedDict 6 | from websocket import create_connection, WebSocketConnectionClosedException 7 | 8 | class RestClient(object): 9 | def __init__(self, key=None, secret=None, url=None): 10 | self.key = key 11 | self.secret = secret 12 | 13 | if url: 14 | self.url = url 15 | else: 16 | self.url = "wss://www.deribit.com/ws/api/v2" 17 | 18 | 19 | self.auth_creds = { 20 | "jsonrpc" : "2.0", 21 | "id" : 0, 22 | "method" : "public/auth", 23 | "params" : { 24 | "grant_type" : "client_credentials", 25 | "client_id" : self.key, 26 | "client_secret" : self.secret 27 | } 28 | } 29 | 30 | def request(self, msg): 31 | auth_data = None 32 | try: 33 | ws = create_connection(self.url) 34 | if msg["method"].startswith("private/"): 35 | ws.send(json.dumps(self.auth_creds)) 36 | while ws.connected: 37 | auth_data = json.loads(ws.recv()) 38 | if "refresh_token" in auth_data["result"]: 39 | pass 40 | elif "error" in auth_data: 41 | raise ValueError(auth_data, "issue in authorisation") 42 | break 43 | except Exception as e: 44 | print(e) 45 | 46 | try: 47 | ws.send(json.dumps(msg)) 48 | while ws.connected: 49 | data = json.loads(ws.recv()) 50 | if "error" in data: 51 | raise ValueError(data , "issue with loading api ") 52 | break 53 | return data["result"] 54 | except Exception as e: 55 | print(e) 56 | 57 | return {"error"} 58 | 59 | 60 | 61 | 62 | def getorderbook(self, instrument, depth=5): 63 | msg = {} 64 | 65 | params = { 66 | "instrument_name": instrument, 67 | "depth": depth 68 | } 69 | msg["method"] = "public/get_order_book" 70 | msg["params"] = params 71 | 72 | return self.request(msg) 73 | 74 | 75 | def getcurrencies(self): 76 | 77 | return self.request("api/v2/public/get_currencies", {}) 78 | 79 | 80 | def getlasttrades(self, instrument, count=None): 81 | options = { 82 | 'instrument': instrument 83 | } 84 | 85 | if count: 86 | options['count'] = count 87 | 88 | return self.request("/api/v2/public/get_last_trades_by_instrument", options) 89 | 90 | 91 | def getsummary(self, currency="BTC"): 92 | return self.request("/api/v2/public/get_account_summary", {"currency": currency, "extended" : 'true'}) 93 | 94 | 95 | def index(self, currency="BTC"): 96 | return self.request("/api/v2/public/get_index", {"currency": currency}) 97 | 98 | # put a hidden option in 99 | def buy(self, instrument, quantity, price, postOnly=None, reduce_only=None, hidden=None, label=None): 100 | 101 | msg = {} 102 | 103 | params = { 104 | "instrument_name": instrument, 105 | "amount": quantity, 106 | "price": price 107 | } 108 | 109 | # and hidden will be a variable in signature of the function 110 | if hidden: 111 | params["hidden"] = 'true' 112 | if label: 113 | params["label"] = label 114 | 115 | if postOnly: 116 | params["post_only"] = 'true' 117 | 118 | if reduce_only: 119 | params["reduce_only"] = 'true' 120 | 121 | msg["method"] = "private/buy" 122 | msg["params"] = params 123 | 124 | return self.request(msg) 125 | 126 | # quantities are done in multiple of 10 127 | def sell(self, instrument, quantity, price, postOnly=None, reduce_only=None, hidden=None, label=None): 128 | msg = {} 129 | params = { 130 | "instrument_name": instrument, 131 | "amount": quantity, 132 | "price": price 133 | } 134 | 135 | # and hidden will be a variable in signature of the function 136 | if hidden: 137 | params["hidden"] = 'true' 138 | if label: 139 | params["label"] = label 140 | 141 | if postOnly: 142 | params["post_only"] = 'true' 143 | 144 | if reduce_only: 145 | params["reduce_only"] = 'true' 146 | 147 | msg["method"] = "private/sell" 148 | msg["params"] = params 149 | 150 | return self.request(msg) 151 | 152 | 153 | def cancel(self, orderId): 154 | msg = {} 155 | params = { 156 | "order_id": orderId 157 | } 158 | 159 | msg["method"] = "private/cancel" 160 | msg["params"] = params 161 | 162 | return self.request(msg) 163 | 164 | 165 | def cancelall(self, instrument="BTC-PERPETUAL", typeDef="all"): 166 | msg = {} 167 | params = { 168 | "type": typeDef, 169 | "instrument_name": instrument 170 | } 171 | 172 | msg = {} 173 | msg["method"] = "private/cancelall" 174 | msg["params"] = params 175 | 176 | return self.request(msg) 177 | 178 | 179 | def getopenorders(self, instrument): 180 | params = {} 181 | 182 | if instrument: 183 | params["instrument_name"] = instrument 184 | 185 | msg = {} 186 | msg["method"] = "private/get_open_orders_by_instrument" 187 | msg["params"] = params 188 | 189 | return self.request(msg) 190 | 191 | 192 | def position(self, instrument="BTC-PERPETUAL"): 193 | msg = {} 194 | params = { 195 | "instrument_name": instrument 196 | } 197 | msg["method"] = "private/get_position" 198 | msg["params"] = params 199 | 200 | return self.request(msg) 201 | 202 | 203 | def orderstate(self, orderId=None): 204 | params = {} 205 | if orderId: 206 | params["order_id"] = orderId 207 | 208 | msg = {} 209 | msg["method"] = "private/get_order_state" 210 | msg["params"] = params 211 | 212 | return self.request(msg) 213 | 214 | 215 | -------------------------------------------------------------------------------- /gatherData.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal, ROUND_HALF_DOWN 2 | import time 3 | import math 4 | from deribit_api import RestClient as DERIBITClassClient 5 | from deribit_V2_API_Websocket import Deribitv2API 6 | from threading import Thread 7 | import json 8 | 9 | class GatherData(): 10 | def __init__(self): 11 | self.deribitWebSocket = Deribitv2API() 12 | self.previousQuoteString = '' 13 | 14 | def setting_url(self, map_client): 15 | url = map_client["url"] 16 | self.deribitWebSocket.setting_url(url) 17 | 18 | def start(self): 19 | # parsing a delegate to the thread for this function and then say to start the thread later on 20 | self.APIThread = Thread(target=self.deribitWebSocket.startSocket) 21 | self.APIThread.start() 22 | 23 | def run(self, clientMap): 24 | clientDERIBIT = clientMap["deribit"] 25 | if not (isinstance(clientDERIBIT, DERIBITClassClient)): return 26 | # get data and sort it and return none is the data is the same as before 27 | newQuote = self.sortData(self.deribitWebSocket.getListOfData()) 28 | 29 | if newQuote == None: return None 30 | 31 | # newQuoteString = str(newQuote["bid"]) + "-" + str(newQuote["ask"]) 32 | # # incase the quotes have not changed 33 | # if newQuoteString == self.previousQuoteString: 34 | # return None 35 | # # setting the strings 36 | # self.previousQuoteString = newQuoteString 37 | 38 | # return the map of quotes 39 | return newQuote 40 | 41 | 42 | def errorFound(self,e): 43 | print(str(e) + " -+waiting for data so sleeping for 15s") 44 | time.sleep(15) 45 | return None 46 | 47 | def sortData(self, mapOfData): 48 | 49 | bestPrices = None 50 | 51 | if (mapOfData != None) and\ 52 | "params" in mapOfData and "data" in mapOfData["params"] \ 53 | and ("best_bid_price" in mapOfData['params']['data'] and "best_ask_price" in mapOfData['params']['data']): 54 | 55 | bestPrices = {"bid": Decimal(mapOfData['params']['data']['best_bid_price']), 56 | "ask": Decimal(mapOfData['params']['data']['best_ask_price'])} 57 | bestPrices["mean"] = (bestPrices["bid"] + bestPrices["ask"])/Decimal(2) 58 | 59 | # after map = json.loads(self.listOfNotifications[-1]) -> map['params']['data']['best_bid_price'] 60 | # map['params']['data']['best_ask_price'] 61 | 62 | return bestPrices 63 | -------------------------------------------------------------------------------- /generateSignals.py: -------------------------------------------------------------------------------- 1 | from orderManagement import OrderManagement 2 | from decimal import Decimal 3 | import math 4 | 5 | class GenerateSignals(): 6 | def __init__(self): 7 | # it means that the list will hold upto 30 minutes of data 8 | self.indexLimitForLists = 30 9 | 10 | self.incrementOverPeriodToCount = 0 11 | self.limitToExitPosition = Decimal(80) 12 | 13 | 14 | 15 | def run(self, mapOfData, positionStatus, orderManagementObject): 16 | # changed it from the close price of previous trades to the best quotes 17 | meanPriceFromQuotes = mapOfData["mean"] 18 | 19 | tempMap = {"close": meanPriceFromQuotes} 20 | mapOfSignals = self.generateSignal(tempMap) 21 | 22 | signalToSell = mapOfSignals["sell"] 23 | signalToBuy = mapOfSignals["buy"] 24 | 25 | # stop exit if order is below average price 26 | if positionStatus != None: 27 | enteredPrice = Decimal(positionStatus['average_price']) 28 | # shorting - if the price we entered is smaller then what we want to exit with then stop - we want sell high buy low 29 | if positionStatus['direction'] == "sell": 30 | if meanPriceFromQuotes > (enteredPrice): 31 | # print("price is too high turn signalToBuy to false") 32 | signalToBuy = False 33 | # long - if the price we entered is larger then what we want to exit with then stop - we want sell high buy low 34 | elif positionStatus['direction'] == "buy": 35 | if meanPriceFromQuotes < (enteredPrice): 36 | # print("price is too low turn signalToSell to false") 37 | signalToSell = False 38 | 39 | 40 | 41 | ''' 42 | Could be more simple but testing so keep separate other wise integrate with whats above if block 43 | ''' 44 | # exit as soon as you surpass average price 45 | if positionStatus != None: 46 | enteredPrice = Decimal(positionStatus['average_price']) 47 | 48 | if positionStatus['direction'] == "sell": 49 | if meanPriceFromQuotes < (enteredPrice): 50 | signalToBuy = True 51 | 52 | elif positionStatus['direction'] == "buy": 53 | if meanPriceFromQuotes > (enteredPrice): 54 | signalToSell = True 55 | # exit as soon as you surpass average price 56 | 57 | """ 58 | hints at possible loses 59 | """ 60 | 61 | # exit order quickly 62 | if positionStatus != None: 63 | 64 | enteredPrice = Decimal(positionStatus['average_price']) 65 | # shorting 66 | if positionStatus['direction'] == "sell": 67 | if meanPriceFromQuotes > (enteredPrice + self.limitToExitPosition): 68 | ''' 69 | if orders of the same position are still there then we do not send out a emergency signal 70 | but RETURNS TRUE IF ORDER IS PRESENT 71 | ''' 72 | signalToBuy = not self.checkIfAnyOrdersAreStillPresent(positionStatus, orderManagementObject) 73 | 74 | if signalToBuy: 75 | print("buy out, Lossing sell") 76 | 77 | # long 78 | elif positionStatus['direction'] == "buy": 79 | if meanPriceFromQuotes < (enteredPrice - self.limitToExitPosition): 80 | ''' 81 | RETURNS TRUE IF ORDER IS PRESENT 82 | ''' 83 | signalToSell = not self.checkIfAnyOrdersAreStillPresent(positionStatus, orderManagementObject) 84 | 85 | if signalToSell: 86 | print("sell out, Lossing buy") 87 | 88 | return {"sell": signalToSell, "buy": signalToBuy} 89 | 90 | # checks if orders are present on our current position thus moving the average price in our favour 91 | def checkIfAnyOrdersAreStillPresent(self, positionStatus, orderManagementObject): 92 | listOfPresentOrders = orderManagementObject.getOpenOrders(indiceName="BTC-PERPETUAL") 93 | # no orders present 94 | if len(listOfPresentOrders) == 0: return False 95 | if positionStatus != None: 96 | # going short 97 | if positionStatus['direction'] == "sell": 98 | # returns true if order is present 99 | return self.findItemByLoopingThroughPresentOrders(listOfPresentOrders, "sell") 100 | elif positionStatus['direction'] == "buy": 101 | return self.findItemByLoopingThroughPresentOrders(listOfPresentOrders, "buy") 102 | 103 | # finds if we still have orders on the same side as the position as it could fill later on increase/decreasing our average price 104 | def findItemByLoopingThroughPresentOrders(self, listOfOrders, side): 105 | for order in listOfOrders: 106 | if order["direction"] == side: 107 | return True 108 | return False 109 | 110 | # this means we are always going to enter orders, as the signal is always true 111 | def generateSignal(self, mapOfDataPrice): 112 | 113 | buySignal = True 114 | sellSignal = True 115 | 116 | return {"sell": sellSignal, "buy": buySignal} -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from controller import Controller 2 | def main(): 3 | Controller().run() 4 | 5 | if __name__ == "__main__": 6 | main() -------------------------------------------------------------------------------- /order.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | class Order(): 4 | def __init__(self): 5 | self._lastOrder = None 6 | self.size = Decimal(0) 7 | self._oldOrder = None 8 | 9 | def setOrder(self, lastOrder): 10 | self._oldOrder = self._lastOrder 11 | self._lastOrder = lastOrder 12 | 13 | def getOldOrder(self): 14 | if (self._oldOrder != None): 15 | temp = Order() 16 | temp.setOrder(self._oldOrder) 17 | return temp 18 | return Order() 19 | 20 | def getCurrentOrder(self): 21 | return self._lastOrder 22 | 23 | def ifContains(self, item): 24 | if item in self._lastOrder: 25 | return True 26 | return False 27 | 28 | def setCoinSize(self, size): 29 | self._lastOrder['size'] = size 30 | 31 | def getCoinSize(self): 32 | return self._lastOrder['size'] 33 | 34 | def getSize(self): 35 | return Decimal(self._lastOrder.get('quantity')) 36 | 37 | def setSize(self, size): 38 | self._lastOrder["quantity"] = size 39 | 40 | def getPrice(self): 41 | return Decimal(self._lastOrder.get('price')) 42 | 43 | def getId(self): 44 | return str(self._lastOrder.get('orderId')) 45 | 46 | def getSide(self): 47 | return self._lastOrder.get('direction') 48 | 49 | def isSideBuy(self): 50 | temp = self._lastOrder["direction"] 51 | return (temp == "Buy" or temp == "buy") 52 | 53 | def isSideSell(self): 54 | temp = self._lastOrder["direction"] 55 | return (temp == "Sell" or temp == "sell") 56 | 57 | def isOrderNone(self): 58 | if self._lastOrder == None: 59 | return True 60 | return False 61 | 62 | def getStatusIsRejected(self): 63 | if self._lastOrder.get('state') == 'Canceled': 64 | return True 65 | return False 66 | -------------------------------------------------------------------------------- /orderManagement.py: -------------------------------------------------------------------------------- 1 | from order import Order 2 | import time 3 | from decimal import Decimal 4 | 5 | class OrderManagement(): 6 | def __init__(self): 7 | self._clientDERIBIT = None 8 | 9 | def run(self, clientMap): 10 | self._clientDERIBIT = clientMap["deribit"] 11 | 12 | def cancelAll(self): 13 | try: 14 | orders = self._clientDERIBIT.cancelall() 15 | print(str(orders) + " any outstanding orders are cancelled") 16 | except Exception as e: 17 | self.exceptionHandler(e, " error on cancelling all") 18 | 19 | def cancelOrder(self, orderObject): 20 | if not isinstance(orderObject, Order): 21 | return 22 | orderObjectCancelled = None 23 | try: 24 | orderObjectCancelled = self._clientDERIBIT.cancel(orderObject.getId()) 25 | print("cancel order " + str(orderObjectCancelled)) 26 | except Exception as e: 27 | self.exceptionHandler(e, " Cancelling order Error, trying again") 28 | return orderObjectCancelled 29 | 30 | def buyOrder(self, indiceName,size, price, reduce_only=False): 31 | try: 32 | 33 | lastOrderObject = self._clientDERIBIT.buy(instrument=indiceName, quantity=str(size),price=str(price),reduce_only=reduce_only, postOnly=True, hidden=True, label=None)["order"] 34 | print("Buy order sent - " + str(lastOrderObject)) 35 | return lastOrderObject 36 | except Exception as e: 37 | self.exceptionHandler(e, " Error creating an order buy, exiting") 38 | return None 39 | 40 | def sellOrder(self, indiceName,size, price, reduce_only=False): 41 | # try using bulk orders to create these orders 42 | try: 43 | lastOrderObject = self._clientDERIBIT.sell(instrument=indiceName, quantity=str(size),price=str(price), reduce_only=reduce_only, postOnly=True, hidden=True, label=None)["order"] 44 | print("Sell order sent - " + str(lastOrderObject)) 45 | return lastOrderObject 46 | except Exception as e: 47 | self.exceptionHandler(e, " Error creating an order sell, exiting") 48 | return None 49 | 50 | def orderStatus(self, listOfOrders, instrument): 51 | 52 | orderItem = None 53 | mapOfOutcomes = {"open":False ,"cancelled": False, "fullyFilled": False, "partFilled": False, "orderStatus": orderItem} 54 | 55 | while(True): 56 | try: 57 | orderMapList = self._clientDERIBIT.getopenorders(instrument=instrument) 58 | # orders from Deribit 59 | for order in orderMapList: 60 | # order we have save from when we sent them 61 | for localOrder in listOfOrders: 62 | 63 | # some order may come back as a None due to the connection timing out 64 | if localOrder == None or localOrder.isOrderNone(): 65 | continue 66 | 67 | if order["order_id"] == localOrder.getId(): 68 | # print("order cancelled") 69 | mapOfOutcomes["open"] = True 70 | mapOfOutcomes["fullyFilled"] = False 71 | return mapOfOutcomes 72 | else: 73 | mapOfOutcomes["fullyFilled"] = True 74 | return mapOfOutcomes 75 | 76 | except Exception as e: 77 | self.exceptionHandler(e, " order status function throw an error") 78 | 79 | 80 | def getOpenOrders(self, indiceName): 81 | while(True): 82 | try: 83 | orders = self._clientDERIBIT.getopenorders(instrument=indiceName) 84 | return orders 85 | except Exception as e: 86 | self.exceptionHandler(e, " getOpenOrders function throw an error") 87 | 88 | def exceptionHandler(self, e, task): 89 | print(str(e), " error occured when doing " + task) 90 | time.sleep(5) 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /positionManagement.py: -------------------------------------------------------------------------------- 1 | class PositionManagement(): 2 | def __init__(self): 3 | self._clientDERIBIT = None 4 | self.currentPosition = None 5 | self.indiceName = "BTC-PERPETUAL" 6 | 7 | def setClients(self, clientMap): 8 | self._clientDERIBIT = clientMap["deribit"] 9 | 10 | def findOurCurrentPosition(self): 11 | try: 12 | #result = self._clientDERIBIT.getorderbook(instrument=self.indiceName, depth=5) 13 | #result = self._clientDERIBIT.getinstruments(currency="BTC", doesExpire=False, kind="future") 14 | currentPosition = self._clientDERIBIT.position(self.indiceName) 15 | if currentPosition == None: 16 | return None 17 | if currentPosition["size"] == 0: 18 | return None 19 | else: 20 | return currentPosition 21 | except Exception as e: 22 | print(str(e) + " error occured in the position management class") 23 | return {"error"} 24 | 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | websocket-client~=1.2.3 --------------------------------------------------------------------------------