0bps

Strategy vault / Other

OKEx Contract Position Management System

Python strategy by FawkesPan.

PythonOther
Author
FawkesPan
Complexity
Advanced · 303 lines
Parameters
0

The original spec has no written description. Read the source below.

Source Python


import time
import math
import threading
import decimal
from uuid import uuid4 as _uuid

def genUUID():
    return str(_uuid())

class PositionManager:

    def __init__(self, exchange, symbol, contract, getDepth, tickSize=0.01):
        self.okex = exchange
        self.symbol = symbol
        self.contract = contract
        self.tickSize = tickSize
        self.isFMZ = False
        if 'Asks' in getDepth().keys():
            self.isFMZ = True
        self.okexGetDepth = getDepth
        self.orderLock = threading.Lock()
        self.orderLocks = {}
        self.orders = {}
        self.cancels = []
        self.completeds = []
        self.init()

    def init(self):
        self.okex.IO("currency", self.symbol.upper())
        self.okex.SetContractType(self.contract)
        self.order_logs = {
            'buySent': 0,
            'buyCanceled': 0,
            'sellSent': 0,
            'sellCanceled': 0
        }

    def toNearest(self, num):
        tickDec = decimal.Decimal(str(self.tickSize))
        return float((decimal.Decimal(round(num / self.tickSize, 0)) * tickDec))

    def getDepth(self):
        depth = self.okexGetDepth()
        if self.isFMZ:
            depth['asks'] = [[x['Price'], x['Amount']] for x in depth['Asks']]
            depth['bids'] = [[x['Price'], x['Amount']] for x in depth['Bids']]
        return depth

    def getOrderLogs(self):
        return self.order_logs

    def getBestPrices(self):
        bestPrices = {}
        depth = self.getDepth()
        ask, bid = depth['asks'][0][0], depth['bids'][0][0]
        spread = ask - bid
        if spread <= self.tickSize*2:
            bestPrices['ask'] = ask
            bestPrices['bid'] = bid
        elif spread <= self.tickSize*6:
            bestPrices['ask'] = ask-self.tickSize
            bestPrices['bid'] = bid+self.tickSize
        elif spread >= self.tickSize*20:
            bestPrices['ask'] = ask-self.tickSize*5
            bestPrices['bid'] = bid+self.tickSize*5
        else:
            midPrice = self.toNearest((ask+bid)/2)
            bestPrices['ask'] = midPrice+self.tickSize*2
            bestPrices['bid'] = midPrice-self.tickSize*2
        return {
            'ask': self.toNearest(bestPrices['ask']),
            'bid': self.toNearest(bestPrices['bid'])
        }

    def getPositions(self, noRetry=False, maxRetries=10):
        if noRetry:
            return self.okex.GetPosition()
        attempts = 0
        while True:
            time.sleep(0.4)
            positions = self.okex.GetPosition()
            attempts+=1
            if len(positions) != 0:
                return positions
            if attempts == maxRetries:
                return False

    def havePosition(self, type=None, noRetry=True, maxRetries=10):
        positions = self.getPositions(noRetry=True)
        if not positions:
            positions = []
        if type == None:
            pass
        elif type == 'buy':
            key = 'buy_amount'
        elif type == 'sell':
            key = 'sell_amount'
        for position in positions:
            if type is not None:
                if position['Info'][key] != 0:
                    return positions
            else:
                return positions
        if noRetry:
            return False
        attempts = 0
        while True:
            time.sleep(0.4)
            positions = self.getPositions(noRetry=True)
            if not positions:
                positions = []
            attempts+=1
            for position in positions:
                if type is not None:
                    if position['Info'][key] != 0:
                        return positions
                else:
                    return positions
            if attempts == maxRetries:
                return False

    def orderDone(self, orderID):
        orders = self.okex.GetOrders()
        for order in orders:
            if order['Info']['order_id'] == orderID:
                return order['Info']['price'], order['Info']['amount']-order['Info']['deal_amount']
        return True

    def getOrder(self, uuid):
        try:
            self.orders[uuid]
        except KeyError:
            Log('No order exists %s' % uuid)
            return 0
        return self.orders[uuid]

    def getOrderInfo(self, uuid):
        return self.okex.GetOrder(self.getOrder(uuid))

    def awaitOrder(self, uuid, timeout=None):
        start = time.time()
        try:
            self.orderLocks[uuid]
        except KeyError:
            return False
        while True:
            if self.orderLocks[uuid].acquire(False):
                del self.orderLocks[uuid]
                return self.getOrder(uuid)
            else:
                if timeout is not None:
                    if time.time()-timeout>start:
                        Log('Waiting for the order to be completed has timed out..')
                        return
            time.sleep(0.001)

    def isDone(self, uuid):
        if uuid in self.completeds:
            return True
        return False

    def cancel(self, uuid):
        self.cancels.append(uuid)
        _cancel = self.okex.CancelOrder(self.getOrder(uuid))
        self.orderLocks[uuid].acquire()
        self.orderLocks[uuid].release()
        if _cancel:
            Log(uuid, str(self.getOrder(uuid)), 'canceled.')
            return True
        Log(uuid, str(self.getOrder(uuid)), 'Cancel Failed.', str(self.okex.GetRawJSON()))
        return False

    def trade(self, type, price, amount, matchPrice=False, noRetry=False, uuid=None):
        if type == 'buy' or type == 'closesell':
            _trader = self.okex.Buy
            _side = 'buy'
            __side = 'bid'
        else:
            _trader = self.okex.Sell
            _side = 'sell'
            __side = 'ask'
        def trader(price, amount):
            self.orderLock.acquire()
            self.okex.SetDirection(type)
            order = _trader(price, amount)
            self.orders[uuid] = order
            self.orderLock.release()
            self.order_logs['%sSent' % _side]+=1
            return order
        def isBestPrice(currentPrice, depth):
            ret = False
            if _side == 'buy':
                if currentPrice > depth['bids'][1][0]:
                    ret = True
            else:
                if currentPrice < depth['asks'][1][0]:
                    ret = True
            return ret
        if price is None:
            price = self.getBestPrices()[__side]
        if matchPrice:
            price = -1
        order = trader(price, amount)
        if noRetry:
            self.completeds.append(uuid)
            self.orderLocks[uuid].release()
            return
        if order == False:
            if 20016 in str(self.okex.GetRawJSON()):
                order = trader(price, amount)
            if order == False:
                Log('%s Return value: %s', (uuid, str(self.okex.GetRawJSON())))
                self.completeds.append(uuid)
                self.orderLocks[uuid].release()
                return
        time.sleep(1)
        while True:
            if uuid in self.cancels:
                Log('%s Stopped trying to place orders.' % uuid)
                self.completeds.append(uuid)
                self.orderLocks[uuid].release()
                return
            time.sleep(1)
            depth = self.getDepth()
            isDone = self.orderDone(order)
            if isDone == True:
                self.completeds.append(uuid)
                self.orderLocks[uuid].release()
                return
            if isBestPrice(isDone[0], depth):
                continue
            if not self.okex.CancelOrder(order):
                time.sleep(1)
                if self.orderDone(order):
                    self.completeds.append(uuid)
                    self.orderLocks[uuid].release()
                    return
            self.order_logs['%sCanceled' % _side]+=1
            time.sleep(1)
            if price != -1:
                price = self.getBestPrices()[__side]
            if uuid in self.cancels:
                Log('%s Stopped trying to place orders.' % uuid)
                self.completeds.append(uuid)
                self.orderLocks[uuid].release()
                return
            order = trader(price, isDone[1])

    def openLong(self, amount, price=None, matchPrice=False, noRetry=False):
        uuid = genUUID()
        Log('%s Buy long, whether to use counterparty price %s Block until successful %s' % (uuid, str(matchPrice), str(not noRetry)))
        threading.Thread(target=self.trade, kwargs=dict({'type': 'buy',
                                                        'price': price,
                                                        'amount': amount,
                                                        'matchPrice': matchPrice,
                                                        'noRetry': noRetry,
                                                        'uuid': uuid}), daemon=True).start()
        self.orderLocks[uuid] = threading.Lock()
        self.orderLocks[uuid].acquire()
        return uuid

    def openShort(self, amount, price=None, matchPrice=False, noRetry=False):
        uuid = genUUID()
        Log('%s When selling short, whether to use the counterparty price? %s Block until successful %s' % (uuid, str(matchPrice), str(not noRetry)))
        threading.Thread(target=self.trade, kwargs=dict({'type': 'sell',
                                                        'price': price,
                                                        'amount': amount,
                                                        'matchPrice': matchPrice,
                                                        'noRetry': noRetry,
                                                        'uuid': uuid}), daemon=True).start()
        self.orderLocks[uuid] = threading.Lock()
        self.orderLocks[uuid].acquire()
        return uuid

    def coverLong(self, amount, price=None, matchPrice=False, noRetry=False):
        uuid = genUUID()
        Log('%s When selling long position, whether to use the counterparty price? %s Block until successful %s' % (uuid, str(matchPrice), str(not noRetry)))
        threading.Thread(target=self.trade, kwargs=dict({'type': 'closebuy',
                                                        'price': price,
                                                        'amount': amount,
                                                        'matchPrice': matchPrice,
                                                        'noRetry': noRetry,
                                                        'uuid': uuid}), daemon=True).start()
        self.orderLocks[uuid] = threading.Lock()
        self.orderLocks[uuid].acquire()
        return uuid

    def coverShort(self, amount, price=None, matchPrice=False, noRetry=False):
        uuid = genUUID()
        Log('%s Buy and close whether to use counterparty price %s Block until successful %s' % (uuid, str(matchPrice), str(not noRetry)))
        threading.Thread(target=self.trade, kwargs=dict({'type': 'closesell',
                                                        'price': price,
                                                        'amount': amount,
                                                        'matchPrice': matchPrice,
                                                        'noRetry': noRetry,
                                                        'uuid': uuid}), daemon=True).start()
        self.orderLocks[uuid] = threading.Lock()
        self.orderLocks[uuid].acquire()
        return uuid

ext.okexPositionManager = PositionManager

Published by FawkesPan on the FMZ strategy square · last modified 2019-05-16. Collected in the Quant Trading Vault. Unvalidated research: re-test with your own data and costs before trusting it.

Build this with AgenKit

A spec is not a trading system. Paste this into your coding agent and AgenKit runs a specialist team — architecture, test-first build, risk gates, review — with paper trading on by default.

in Claude Code, Cursor, Codex & 4 more
/agenkit build a production-grade trading bot with a backtest engine, risk controls and paper trading by default from strategies/OKEx-Contract-Position-Management-System.md
  1. npx agenkit install engineering-kit in your project
  2. Paste the command into your agent and approve each phase
  3. Get tests, backtest, risk controls and a paper-trading switch

Understand it first

Before you run this, read the lesson behind the idea and how to backtest it honestly. Specs like this one are the survivors of an unknown number of trials; deflate their results accordingly.

Related specs

esc

Try , , or .

↑↓ move↵ open