python-okx 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
okx/Account.py CHANGED
@@ -27,28 +27,30 @@ class AccountAPI(OkxClient):
27
27
  params = {'instType': instType, 'instId': instId, 'posId': posId}
28
28
  return self._request_with_params(GET, POSITION_INFO, params)
29
29
 
30
- def position_builder(self, acctLv=None,inclRealPosAndEq=False, lever=None, greeksType=None, simPos=None,
31
- simAsset=None):
30
+ def position_builder(self, acctLv=None, inclRealPosAndEq=None, lever=None, greeksType=None, simPos=None,
31
+ simAsset=None, idxVol=None):
32
32
  params = {}
33
33
  if acctLv is not None:
34
34
  params['acctLv'] = acctLv
35
35
  if inclRealPosAndEq is not None:
36
36
  params['inclRealPosAndEq'] = inclRealPosAndEq
37
37
  if lever is not None:
38
- params['spotOffsetType'] = lever
38
+ params['lever'] = lever
39
39
  if greeksType is not None:
40
- params['greksType'] = greeksType
40
+ params['greeksType'] = greeksType
41
41
  if simPos is not None:
42
42
  params['simPos'] = simPos
43
43
  if simAsset is not None:
44
44
  params['simAsset'] = simAsset
45
+ if idxVol is not None:
46
+ params['idxVol'] = idxVol
45
47
  return self._request_with_params(POST, POSITION_BUILDER, params)
46
48
 
47
49
  # Get Bills Details (recent 7 days)
48
50
  def get_account_bills(self, instType='', ccy='', mgnMode='', ctType='', type='', subType='', after='', before='',
49
- limit=''):
51
+ limit='', begin='', end=''):
50
52
  params = {'instType': instType, 'ccy': ccy, 'mgnMode': mgnMode, 'ctType': ctType, 'type': type,
51
- 'subType': subType, 'after': after, 'before': before, 'limit': limit}
53
+ 'subType': subType, 'after': after, 'before': before, 'limit': limit, 'begin': begin, 'end': end}
52
54
  return self._request_with_params(GET, BILLS_DETAIL, params)
53
55
 
54
56
  # Get Bills Details (recent 3 months)
@@ -74,14 +76,18 @@ class AccountAPI(OkxClient):
74
76
  return self._request_with_params(POST, SET_LEVERAGE, params)
75
77
 
76
78
  # Get Maximum Tradable Size For Instrument
77
- def get_max_order_size(self, instId, tdMode, ccy='', px=''):
79
+ def get_max_order_size(self, instId, tdMode, ccy='', px='', tradeQuoteCcy=None):
78
80
  params = {'instId': instId, 'tdMode': tdMode, 'ccy': ccy, 'px': px}
81
+ if tradeQuoteCcy is not None:
82
+ params['tradeQuoteCcy'] = tradeQuoteCcy
79
83
  return self._request_with_params(GET, MAX_TRADE_SIZE, params)
80
84
 
81
85
  # Get Maximum Available Tradable Amount
82
- def get_max_avail_size(self, instId, tdMode, ccy='', reduceOnly='', unSpotOffset='', quickMgnType=''):
86
+ def get_max_avail_size(self, instId, tdMode, ccy='', reduceOnly='', unSpotOffset='', quickMgnType='', tradeQuoteCcy=None):
83
87
  params = {'instId': instId, 'tdMode': tdMode, 'ccy': ccy, 'reduceOnly': reduceOnly,
84
88
  'unSpotOffset': unSpotOffset, 'quickMgnType': quickMgnType}
89
+ if tradeQuoteCcy is not None:
90
+ params['tradeQuoteCcy'] = tradeQuoteCcy
85
91
  return self._request_with_params(GET, MAX_AVAIL_SIZE, params)
86
92
 
87
93
  # Increase / Decrease margin
@@ -100,13 +106,17 @@ class AccountAPI(OkxClient):
100
106
  return self._request_with_params(GET, GET_INSTRUMENTS, params)
101
107
 
102
108
  # Get the maximum loan of isolated MARGIN
103
- def get_max_loan(self, instId, mgnMode, mgnCcy=''):
109
+ def get_max_loan(self, instId, mgnMode, mgnCcy='', tradeQuoteCcy=None):
104
110
  params = {'instId': instId, 'mgnMode': mgnMode, 'mgnCcy': mgnCcy}
111
+ if tradeQuoteCcy is not None:
112
+ params['tradeQuoteCcy'] = tradeQuoteCcy
105
113
  return self._request_with_params(GET, MAX_LOAN, params)
106
114
 
107
115
  # Get Fee Rates
108
- def get_fee_rates(self, instType, instId='', uly='', category='', instFamily=''):
116
+ def get_fee_rates(self, instType, instId='', uly='', category='', instFamily='', groupId=None):
109
117
  params = {'instType': instType, 'instId': instId, 'uly': uly, 'category': category, 'instFamily': instFamily}
118
+ if groupId is not None:
119
+ params['groupId'] = groupId
110
120
  return self._request_with_params(GET, FEE_RATES, params)
111
121
 
112
122
  # Get interest-accrued
@@ -323,3 +333,36 @@ class AccountAPI(OkxClient):
323
333
  def spot_borrow_repay_history(self, ccy='', type='', after='', before='', limit=''):
324
334
  params = {'ccy': ccy, 'type': type, 'after': after, 'before': before, 'limit': limit}
325
335
  return self._request_with_params(GET, GET_BORROW_REPAY_HISTORY, params)
336
+
337
+ def set_auto_earn(self, ccy, action, earnType=None):
338
+ params = {'ccy': ccy, 'action': action}
339
+ if earnType is not None:
340
+ params['earnType'] = earnType
341
+ return self._request_with_params(POST, SET_AUTO_EARN, params)
342
+
343
+ # Set trading config (BROK-1724). type is required (e.g. 'stgyType');
344
+ # stgyType (0=general, 1=delta neutral) applies only when type == 'stgyType'.
345
+ def set_trading_config(self, type='', stgyType=''):
346
+ params = {'type': type}
347
+ if stgyType != '':
348
+ params['stgyType'] = stgyType
349
+ return self._request_with_params(POST, SET_TRADING_CONFIG, params)
350
+
351
+ # Pre-check whether the delta-neutral switch is allowed (BROK-1724)
352
+ def precheck_set_delta_neutral(self, stgyType=''):
353
+ params = {'stgyType': stgyType}
354
+ return self._request_with_params(GET, PRECHECK_SET_DELTA_NEUTRAL, params)
355
+
356
+ # Get the list of supported bill subtypes (BROK-1728)
357
+ def get_bill_type(self, type=''):
358
+ params = {}
359
+ if type != '':
360
+ params['type'] = type
361
+ return self._request_with_params(GET, BILL_TYPE, params)
362
+
363
+ # Apply for an asynchronous bill history archive by year + quarter (BROK-1728)
364
+ def apply_bills(self, year='', quarter='', type=''):
365
+ params = {'year': year, 'quarter': quarter}
366
+ if type != '':
367
+ params['type'] = type
368
+ return self._request_with_params(POST, BILLS_APPLY, params)
okx/Convert.py CHANGED
@@ -10,16 +10,22 @@ class ConvertAPI(OkxClient):
10
10
  params = {}
11
11
  return self._request_with_params(GET, GET_CURRENCIES, params)
12
12
 
13
- def get_currency_pair(self, fromCcy='', toCcy=''):
13
+ def get_currency_pair(self, fromCcy='', toCcy='', convertMode=None):
14
14
  params = {"fromCcy": fromCcy, 'toCcy': toCcy}
15
+ if convertMode is not None:
16
+ params['convertMode'] = convertMode
15
17
  return self._request_with_params(GET, GET_CURRENCY_PAIR, params)
16
18
 
17
- def estimate_quote(self, baseCcy = '', quoteCcy = '', side = '', rfqSz = '', rfqSzCcy = '', clQReqId = '',tag=''):
19
+ def estimate_quote(self, baseCcy = '', quoteCcy = '', side = '', rfqSz = '', rfqSzCcy = '', clQReqId = '',tag='', convertMode=None):
18
20
  params = {'baseCcy': baseCcy, 'quoteCcy': quoteCcy, 'side':side, 'rfqSz':rfqSz, 'rfqSzCcy':rfqSzCcy, 'clQReqId':clQReqId,'tag':tag}
21
+ if convertMode is not None:
22
+ params['convertMode'] = convertMode
19
23
  return self._request_with_params(POST, ESTIMATE_QUOTE, params)
20
24
 
21
- def convert_trade(self, quoteId = '', baseCcy = '', quoteCcy = '', side = '', sz = '', szCcy = '', clTReqId = '',tag=''):
25
+ def convert_trade(self, quoteId = '', baseCcy = '', quoteCcy = '', side = '', sz = '', szCcy = '', clTReqId = '',tag='', convertMode=None):
22
26
  params = {'quoteId': quoteId, 'baseCcy': baseCcy, 'quoteCcy':quoteCcy, 'side':side, 'sz':sz, 'szCcy':szCcy, 'clTReqId':clTReqId,'tag':tag}
27
+ if convertMode is not None:
28
+ params['convertMode'] = convertMode
23
29
  return self._request_with_params(POST, CONVERT_TRADE, params)
24
30
 
25
31
  def get_convert_history(self, after = '', before = '', limit = '',tag=''):
okx/DualInvest.py ADDED
@@ -0,0 +1,59 @@
1
+ from .okxclient import OkxClient
2
+ from .consts import (
3
+ GET,
4
+ POST,
5
+ DUAL_INVEST_CURRENCY_PAIRS,
6
+ DUAL_INVEST_PRODUCT_INFO,
7
+ DUAL_INVEST_REQUEST_QUOTE,
8
+ DUAL_INVEST_TRADE,
9
+ DUAL_INVEST_REQUEST_REDEEM_QUOTE,
10
+ DUAL_INVEST_REDEEM,
11
+ DUAL_INVEST_ORDER_STATE,
12
+ DUAL_INVEST_ORDER_HISTORY,
13
+ )
14
+
15
+
16
+ class DualInvestAPI(OkxClient):
17
+ def __init__(self, api_key='-1', api_secret_key='-1', passphrase='-1', use_server_time=None, flag='1', domain='https://www.okx.com', debug=False, proxy=None): # NOSONAR - '-1' is the SDK-wide "no credential supplied" sentinel (auth is gated on api_key != '-1' in OkxClient), not a hard-coded secret
18
+ OkxClient.__init__(self, api_key, api_secret_key, passphrase, use_server_time, flag, domain, debug, proxy)
19
+
20
+ # GET /api/v5/finance/sfp/dcd/currency-pair
21
+ def get_currency_pairs(self):
22
+ return self._request_without_params(GET, DUAL_INVEST_CURRENCY_PAIRS)
23
+
24
+ # GET /api/v5/finance/sfp/dcd/products
25
+ def get_product_info(self, baseCcy='', quoteCcy='', optType=''):
26
+ params = {'baseCcy': baseCcy, 'quoteCcy': quoteCcy, 'optType': optType}
27
+ return self._request_with_params(GET, DUAL_INVEST_PRODUCT_INFO, params)
28
+
29
+ # POST /api/v5/finance/sfp/dcd/quote
30
+ def request_quote(self, productId='', notionalSz='', notionalCcy=''):
31
+ params = {'productId': productId, 'notionalSz': notionalSz, 'notionalCcy': notionalCcy}
32
+ return self._request_with_params(POST, DUAL_INVEST_REQUEST_QUOTE, params)
33
+
34
+ # POST /api/v5/finance/sfp/dcd/trade
35
+ def trade(self, quoteId=''):
36
+ params = {'quoteId': quoteId}
37
+ return self._request_with_params(POST, DUAL_INVEST_TRADE, params)
38
+
39
+ # POST /api/v5/finance/sfp/dcd/redeem-quote
40
+ def request_redeem_quote(self, ordId=''):
41
+ params = {'ordId': ordId}
42
+ return self._request_with_params(POST, DUAL_INVEST_REQUEST_REDEEM_QUOTE, params)
43
+
44
+ # POST /api/v5/finance/sfp/dcd/redeem
45
+ def redeem(self, ordId='', quoteId=''):
46
+ params = {'ordId': ordId, 'quoteId': quoteId}
47
+ return self._request_with_params(POST, DUAL_INVEST_REDEEM, params)
48
+
49
+ # GET /api/v5/finance/sfp/dcd/order-status
50
+ def get_order_state(self, ordId=''):
51
+ params = {'ordId': ordId}
52
+ return self._request_with_params(GET, DUAL_INVEST_ORDER_STATE, params)
53
+
54
+ # GET /api/v5/finance/sfp/dcd/order-history
55
+ def get_order_history(self, ordId='', productId='', uly='', state='',
56
+ beginId='', endId='', begin='', end='', limit=''):
57
+ params = {'ordId': ordId, 'productId': productId, 'uly': uly, 'state': state,
58
+ 'beginId': beginId, 'endId': endId, 'begin': begin, 'end': end, 'limit': limit}
59
+ return self._request_with_params(GET, DUAL_INVEST_ORDER_HISTORY, params)
okx/Funding.py CHANGED
@@ -35,9 +35,11 @@ class FundingAPI(OkxClient):
35
35
  return self._request_with_params(POST, FUNDS_TRANSFER, params)
36
36
 
37
37
  # Withdrawal
38
- def withdrawal(self, ccy, amt, dest, toAddr, chain='', areaCode='', clientId=''):
38
+ def withdrawal(self, ccy, amt, dest, toAddr, chain='', areaCode='', clientId='', toAddrType=None):
39
39
  params = {'ccy': ccy, 'amt': amt, 'dest': dest, 'toAddr': toAddr, 'chain': chain,
40
40
  'areaCode': areaCode, 'clientId': clientId}
41
+ if toAddrType is not None:
42
+ params['toAddrType'] = toAddrType
41
43
  return self._request_with_params(POST, WITHDRAWAL_COIN, params)
42
44
 
43
45
  # Get Deposit History
@@ -46,10 +48,7 @@ class FundingAPI(OkxClient):
46
48
  'depId': depId, 'fromWdId': fromWdId}
47
49
  return self._request_with_params(GET, DEPOSIT_HISTORY, params)
48
50
 
49
- # Get Withdrawal History
50
- def get_withdrawal_history(self, ccy='', wdId='', state='', after='', before='', limit='',txId=''):
51
- params = {'ccy': ccy, 'wdId': wdId, 'state': state, 'after': after, 'before': before, 'limit': limit,'txId':txId}
52
- return self._request_with_params(GET, WITHDRAWAL_HISTORY, params)
51
+
53
52
 
54
53
  # Get Currencies
55
54
  def get_currencies(self, ccy=''):
@@ -113,7 +112,9 @@ class FundingAPI(OkxClient):
113
112
  return self._request_with_params(GET, GET_DEPOSIT_WITHDrAW_STATUS, params)
114
113
 
115
114
  #Get withdrawal history
116
- def get_withdrawal_history(self, ccy='', wdId='', clientId='', txId='', type='', state='', after='', before ='', limit=''):
115
+ def get_withdrawal_history(self, ccy='', wdId='', clientId='', txId='', type='', state='', after='', before='', limit='', toAddrType=None):
117
116
  params = {'ccy': ccy, 'wdId': wdId, 'clientId': clientId, 'txId': txId, 'type': type, 'state': state, 'after': after, 'before': before, 'limit': limit}
117
+ if toAddrType is not None:
118
+ params['toAddrType'] = toAddrType
118
119
  return self._request_with_params(GET, GET_WITHDRAWAL_HISTORY, params)
119
120
 
okx/Grid.py CHANGED
@@ -7,11 +7,13 @@ class GridAPI(OkxClient):
7
7
  OkxClient.__init__(self, api_key, api_secret_key, passphrase, use_server_time, flag, domain, debug, proxy)
8
8
 
9
9
  def grid_order_algo(self, instId='', algoOrdType='', maxPx='', minPx='', gridNum='', runType='', tpTriggerPx='',
10
- slTriggerPx='', tag='', quoteSz='', baseSz='', sz='', direction='', lever='', basePos=''):
10
+ slTriggerPx='', tag='', quoteSz='', baseSz='', sz='', direction='', lever='', basePos='', tradeQuoteCcy=None):
11
11
  params = {'instId': instId, 'algoOrdType': algoOrdType, 'maxPx': maxPx, 'minPx': minPx, 'gridNum': gridNum,
12
12
  'runType': runType, 'tpTriggerPx': tpTriggerPx, 'slTriggerPx': slTriggerPx, 'tag': tag,
13
13
  'quoteSz': quoteSz, 'baseSz': baseSz, 'sz': sz, 'direction': direction, 'lever': lever,
14
14
  'basePos': basePos}
15
+ if tradeQuoteCcy is not None:
16
+ params['tradeQuoteCcy'] = tradeQuoteCcy
15
17
  return self._request_with_params(POST, GRID_ORDER_ALGO, params)
16
18
 
17
19
  def grid_amend_order_algo(self, algoId='', instId='', slTriggerPx='', tpTriggerPx=''):
@@ -79,11 +81,13 @@ class GridAPI(OkxClient):
79
81
 
80
82
  # - Place recurring buy order
81
83
  def place_recurring_buy_order(self, stgyName='', recurringList=[], period='', recurringDay='', recurringTime='',
82
- timeZone='', amt='', investmentCcy='', tdMode='', algoClOrdId='', tag=''):
84
+ timeZone='', amt='', investmentCcy='', tdMode='', algoClOrdId='', tag='', tradeQuoteCcy=None):
83
85
  params = {'stgyName': stgyName, 'recurringList': recurringList, 'period': period, 'recurringDay': recurringDay,
84
86
  'recurringTime': recurringTime,
85
87
  'timeZone': timeZone, 'amt': amt, 'investmentCcy': investmentCcy, 'tdMode': tdMode,
86
88
  'algoClOrdId': algoClOrdId, 'tag': tag}
89
+ if tradeQuoteCcy is not None:
90
+ params['tradeQuoteCcy'] = tradeQuoteCcy
87
91
  return self._request_with_params(POST, PLACE_RECURRING_BUY_ORDER, params)
88
92
 
89
93
  # - Amend recurring buy order
okx/PublicData.py CHANGED
@@ -122,3 +122,23 @@ class PublicAPI(OkxClient):
122
122
  'optType': optType
123
123
  }
124
124
  return self._request_with_params(GET, GET_OPTION_TRADES, params)
125
+
126
+ # Get historical market data
127
+ def get_market_data_history(self, module, instType, dateAggrType, begin, end, instIdList=None, instFamilyList=None):
128
+ params = {
129
+ 'module': module,
130
+ 'instType': instType,
131
+ 'dateAggrType': dateAggrType,
132
+ 'begin': begin,
133
+ 'end': end
134
+ }
135
+ if instIdList is not None:
136
+ params['instIdList'] = instIdList
137
+ if instFamilyList is not None:
138
+ params['instFamilyList'] = instFamilyList
139
+ return self._request_with_params(GET, MARKET_DATA_HISTORY, params)
140
+
141
+ # Get announcements (BROK-1730)
142
+ def get_announcements(self, annType='', page=''):
143
+ params = {'annType': annType, 'page': page}
144
+ return self._request_with_params(GET, ANNOUNCEMENTS, params)
okx/Trade.py CHANGED
@@ -11,11 +11,20 @@ class TradeAPI(OkxClient):
11
11
  OkxClient.__init__(self, api_key, api_secret_key, passphrase, use_server_time, flag, domain, debug, proxy)
12
12
 
13
13
  # Place Order
14
- def place_order(self, instId, tdMode, side, ordType, sz, ccy='', clOrdId='', tag='', posSide='', px='',
15
- reduceOnly='', tgtCcy='', stpMode='', attachAlgoOrds=None, pxUsd='', pxVol='', banAmend=''):
14
+ def place_order(self, instId, tdMode, side, ordType, sz, ccy='', clOrdId='', tag='', posSide='', px='', # NOSONAR - public OKX v5 place-order API surface; the parameter list mirrors the REST endpoint 1:1 and cannot be reduced without breaking backward compatibility
15
+ reduceOnly='', tgtCcy='', stpMode='', attachAlgoOrds=None, pxUsd='', pxVol='', banAmend='', tradeQuoteCcy=None, pxAmendType=None,
16
+ isElpTakerAccess=None, instIdCode=None):
16
17
  params = {'instId': instId, 'tdMode': tdMode, 'side': side, 'ordType': ordType, 'sz': sz, 'ccy': ccy,
17
18
  'clOrdId': clOrdId, 'tag': tag, 'posSide': posSide, 'px': px, 'reduceOnly': reduceOnly,
18
19
  'tgtCcy': tgtCcy, 'stpMode': stpMode, 'pxUsd': pxUsd, 'pxVol': pxVol, 'banAmend': banAmend}
20
+ if tradeQuoteCcy is not None:
21
+ params['tradeQuoteCcy'] = tradeQuoteCcy
22
+ if pxAmendType is not None:
23
+ params['pxAmendType'] = pxAmendType
24
+ if isElpTakerAccess is not None:
25
+ params['isElpTakerAccess'] = isElpTakerAccess
26
+ if instIdCode is not None:
27
+ params['instIdCode'] = instIdCode
19
28
  params['attachAlgoOrds'] = attachAlgoOrds
20
29
  return self._request_with_params(POST, PLACR_ORDER, params)
21
30
 
@@ -33,13 +42,20 @@ class TradeAPI(OkxClient):
33
42
  return self._request_with_params(POST, CANCEL_BATCH_ORDERS, orders_data)
34
43
 
35
44
  # Amend Order
36
- def amend_order(self, instId, cxlOnFail='', ordId='', clOrdId='', reqId='', newSz='', newPx='', newTpTriggerPx='',
45
+ def amend_order(self, instId, cxlOnFail='', ordId='', clOrdId='', reqId='', newSz='', newPx='', newTpTriggerPx='', # NOSONAR - public OKX v5 amend-order API surface; the parameter list mirrors the REST endpoint 1:1 and cannot be reduced without breaking backward compatibility
37
46
  newTpOrdPx='', newSlTriggerPx='', newSlOrdPx='', newTpTriggerPxType='', newSlTriggerPxType='',
38
- attachAlgoOrds='', newTriggerPx='', newOrdPx=''):
47
+ attachAlgoOrds='', newTriggerPx='', newOrdPx='', pxAmendType=None,
48
+ newTpTriggerRatio=None, newSlTriggerRatio=None):
39
49
  params = {'instId': instId, 'cxlOnFail': cxlOnFail, 'ordId': ordId, 'clOrdId': clOrdId, 'reqId': reqId,
40
50
  'newSz': newSz, 'newPx': newPx, 'newTpTriggerPx': newTpTriggerPx, 'newTpOrdPx': newTpOrdPx,
41
51
  'newSlTriggerPx': newSlTriggerPx, 'newSlOrdPx': newSlOrdPx, 'newTpTriggerPxType': newTpTriggerPxType,
42
52
  'newSlTriggerPxType': newSlTriggerPxType, 'newTriggerPx': newTriggerPx, 'newOrdPx': newOrdPx}
53
+ if pxAmendType is not None:
54
+ params['pxAmendType'] = pxAmendType
55
+ if newTpTriggerRatio is not None:
56
+ params['newTpTriggerRatio'] = newTpTriggerRatio
57
+ if newSlTriggerRatio is not None:
58
+ params['newSlTriggerRatio'] = newSlTriggerRatio
43
59
  params['attachAlgoOrds'] = attachAlgoOrds
44
60
  return self._request_with_params(POST, AMEND_ORDER, params)
45
61
 
@@ -88,15 +104,16 @@ class TradeAPI(OkxClient):
88
104
  return self._request_with_params(GET, ORDER_FILLS, params)
89
105
 
90
106
  # Place Algo Order
91
- def place_algo_order(self, instId='', tdMode='', side='', ordType='', sz='', ccy='',
107
+ def place_algo_order(self, instId='', tdMode='', side='', ordType='', sz='', ccy='', # NOSONAR - public OKX v5 place-algo-order API surface; the parameter list mirrors the REST endpoint 1:1 and cannot be reduced without breaking backward compatibility
92
108
  posSide='', reduceOnly='', tpTriggerPx='',
93
109
  tpOrdPx='', slTriggerPx='', slOrdPx='',
94
110
  triggerPx='', orderPx='', tgtCcy='', pxVar='',
95
111
  pxSpread='',
96
112
  szLimit='', pxLimit='', timeInterval='', tpTriggerPxType='', slTriggerPxType='',
97
113
  callbackRatio='', callbackSpread='', activePx='', tag='', triggerPxType='', closeFraction=''
98
- , quickMgnType='', algoClOrdId='', tradeQuoteCcy='', tpOrdKind='', cxlOnClosePos=''
99
- , chaseType='', chaseVal='', maxChaseType='', maxChaseVal='', attachAlgoOrds=[]):
114
+ , quickMgnType='', algoClOrdId='', tradeQuoteCcy=None, tpOrdKind='', cxlOnClosePos=''
115
+ , chaseType='', chaseVal='', maxChaseType='', maxChaseVal='', attachAlgoOrds=[], pxAmendType=None
116
+ , advanceOrdType=None, tpTriggerRatio=None, slTriggerRatio=None):
100
117
  params = {'instId': instId, 'tdMode': tdMode, 'side': side, 'ordType': ordType, 'sz': sz, 'ccy': ccy,
101
118
  'posSide': posSide, 'reduceOnly': reduceOnly, 'tpTriggerPx': tpTriggerPx, 'tpOrdPx': tpOrdPx,
102
119
  'slTriggerPx': slTriggerPx, 'slOrdPx': slOrdPx, 'triggerPx': triggerPx, 'orderPx': orderPx,
@@ -105,14 +122,34 @@ class TradeAPI(OkxClient):
105
122
  'pxSpread': pxSpread, 'tpTriggerPxType': tpTriggerPxType, 'slTriggerPxType': slTriggerPxType,
106
123
  'callbackRatio': callbackRatio, 'callbackSpread': callbackSpread, 'activePx': activePx,
107
124
  'tag': tag, 'triggerPxType': triggerPxType, 'closeFraction': closeFraction,
108
- 'quickMgnType': quickMgnType, 'algoClOrdId': algoClOrdId, 'tradeQuoteCcy': tradeQuoteCcy,
125
+ 'quickMgnType': quickMgnType, 'algoClOrdId': algoClOrdId,
109
126
  'tpOrdKind': tpOrdKind, 'cxlOnClosePos': cxlOnClosePos, 'chaseType': chaseType, 'chaseVal': chaseVal,
110
127
  'maxChaseType': maxChaseType, 'maxChaseVal': maxChaseVal, 'attachAlgoOrds': attachAlgoOrds}
128
+ if tradeQuoteCcy is not None:
129
+ params['tradeQuoteCcy'] = tradeQuoteCcy
130
+ if pxAmendType is not None:
131
+ params['pxAmendType'] = pxAmendType
132
+ if advanceOrdType is not None:
133
+ params['advanceOrdType'] = advanceOrdType
134
+ if tpTriggerRatio is not None:
135
+ params['tpTriggerRatio'] = tpTriggerRatio
136
+ if slTriggerRatio is not None:
137
+ params['slTriggerRatio'] = slTriggerRatio
111
138
  return self._request_with_params(POST, PLACE_ALGO_ORDER, params)
112
139
 
113
140
  # Cancel Algo Order
114
- def cancel_algo_order(self, params):
115
- return self._request_with_params(POST, CANCEL_ALGOS, params)
141
+ def cancel_algo_order(self, orders_data=None, params=None):
142
+ """
143
+ Cancel algo orders — POST /api/v5/trade/cancel-algos.
144
+
145
+ :param orders_data: list of {'instId': str, 'algoId': str},
146
+ e.g. [{'instId': 'BTC-USDT', 'algoId': '590xxxx'}]
147
+ :param params: DEPRECATED alias for orders_data (kept for backward compatibility).
148
+ """
149
+ data = orders_data if orders_data is not None else params
150
+ if data is None:
151
+ raise ValueError("orders_data is required: list of {'instId', 'algoId'}")
152
+ return self._request_with_params(POST, CANCEL_ALGOS, data)
116
153
 
117
154
  # Get Algo Order List
118
155
  def order_algos_list(self, ordType='', algoId='', instType='', instId='', after='', before='', limit=''):
@@ -177,11 +214,11 @@ class TradeAPI(OkxClient):
177
214
  return self._request_with_params(GET, GET_ALGO_ORDER_DETAILS, params)
178
215
 
179
216
  # Amend algo order
180
- def amend_algo_order(self, instId='', algoId='', algoClOrdId='', cxlOnFail='', reqId='', newSz='',
217
+ def amend_algo_order(self, instId='', algoId='', algoClOrdId='', cxlOnFail='', reqId='', newSz='', newTriggerPx='', newOrdPx='',
181
218
  newTpTriggerPx='', newTpOrdPx='', newSlTriggerPx='', newSlOrdPx='', newTpTriggerPxType='',
182
219
  newSlTriggerPxType=''):
183
220
  params = {'instId': instId, 'algoId': algoId, 'algoClOrdId': algoClOrdId, 'cxlOnFail': cxlOnFail,
184
- 'reqId': reqId, 'newSz': newSz, 'newTpTriggerPx': newTpTriggerPx, 'newTpOrdPx': newTpOrdPx,
221
+ 'reqId': reqId, 'newSz': newSz, 'newTriggerPx': newTriggerPx, 'newOrdPx': newOrdPx, 'newTpTriggerPx': newTpTriggerPx, 'newTpOrdPx': newTpOrdPx,
185
222
  'newSlTriggerPx': newSlTriggerPx, 'newSlOrdPx': newSlOrdPx,
186
223
  'newTpTriggerPxType': newTpTriggerPxType, 'newSlTriggerPxType': newSlTriggerPxType}
187
224
  return self._request_with_params(POST, AMEND_ALGO_ORDER, params)
okx/TradingData.py CHANGED
@@ -47,6 +47,31 @@ class TradingDataAPI(OkxClient):
47
47
  params = {'ccy': ccy, 'period': period}
48
48
  return self._request_with_params(GET, TAKER_FLOW, params)
49
49
 
50
+ def get_open_interest_history(self, instId, period=None, begin=None, end=None, limit=None):
51
+ """
52
+ Get contract open interest history
53
+ Retrieve the contract open interest statistics of futures and perp.
54
+ Rate limit: 10 requests per 2 seconds
55
+ Rate limit rule: IP + Instrument ID
56
+
57
+ :param instId: Instrument ID, e.g. BTC-USDT-SWAP. Only applicable to FUTURES, SWAP
58
+ :param period: Bar size, the default is 5m, e.g. [5m/15m/30m/1H/2H/4H]
59
+ :param begin: Return records newer than the requested ts
60
+ :param end: Pagination of data to return records earlier than the requested ts
61
+ :param limit: Number of results per request. The maximum is 100. The default is 100
62
+ :return: API response
63
+ """
64
+ params = {'instId': instId}
65
+ if period is not None:
66
+ params['period'] = period
67
+ if begin is not None:
68
+ params['begin'] = begin
69
+ if end is not None:
70
+ params['end'] = end
71
+ if limit is not None:
72
+ params['limit'] = limit
73
+ return self._request_with_params(GET, CONTRACTS_OPEN_INTEREST_HISTORY, params)
74
+
50
75
 
51
76
 
52
77
 
okx/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
  Python SDK for the OKX API v5
3
3
 
4
4
  """
5
- __version__="0.4.0"
5
+ __version__="0.4.2"
okx/consts.py CHANGED
@@ -66,6 +66,11 @@ BORROWING_ORDERS_LIST='/api/v5/account/fixed-loan/borrowing-orders-list'
66
66
  MANUAL_REBORROW_REPAY = '/api/v5/account/spot-manual-borrow-repay'
67
67
  SET_AUTO_REPAY='/api/v5/account/set-auto-repay'
68
68
  GET_BORROW_REPAY_HISTORY='/api/v5/account/spot-borrow-repay-history'
69
+ SET_AUTO_EARN='/api/v5/account/set-auto-earn'
70
+ SET_TRADING_CONFIG = '/api/v5/account/set-trading-config'
71
+ PRECHECK_SET_DELTA_NEUTRAL = '/api/v5/account/precheck-set-delta-neutral'
72
+ BILL_TYPE = '/api/v5/account/subtypes'
73
+ BILLS_APPLY = '/api/v5/account/bills-history-archive'
69
74
 
70
75
  # Funding
71
76
  NON_TRADABLE_ASSETS = '/api/v5/asset/non-tradable-assets'
@@ -81,7 +86,6 @@ BILLS_INFO = '/api/v5/asset/bills'
81
86
  DEPOSIT_LIGHTNING = '/api/v5/asset/deposit-lightning'
82
87
  WITHDRAWAL_LIGHTNING = '/api/v5/asset/withdrawal-lightning'
83
88
  CANCEL_WITHDRAWAL = '/api/v5/asset/cancel-withdrawal'
84
- WITHDRAWAL_HISTORY = '/api/v5/asset/withdrawal-history'
85
89
  CONVERT_DUST_ASSETS = '/api/v5/asset/convert-dust-assets'
86
90
  ASSET_VALUATION = '/api/v5/asset/asset-valuation'
87
91
  GET_WITHDRAWAL_HISTORY = '/api/v5/asset/withdrawal-history'
@@ -130,6 +134,7 @@ INSURANCE_FUND = '/api/v5/public/insurance-fund'
130
134
  CONVERT_CONTRACT_COIN = '/api/v5/public/convert-contract-coin'
131
135
  GET_OPTION_TICKBANDS = '/api/v5/public/instrument-tick-bands'
132
136
  GET_OPTION_TRADES = '/api/v5/public/option-trades'
137
+ MARKET_DATA_HISTORY = '/api/v5/public/market-data-history'
133
138
 
134
139
  # Trading data
135
140
  SUPPORT_COIN = '/api/v5/rubik/stat/trading-data/support-coin'
@@ -142,6 +147,8 @@ PUT_CALL_RATIO = '/api/v5/rubik/stat/option/open-interest-volume-ratio'
142
147
  OPEN_INTEREST_VOLUME_EXPIRY = '/api/v5/rubik/stat/option/open-interest-volume-expiry'
143
148
  INTEREST_VOLUME_STRIKE = '/api/v5/rubik/stat/option/open-interest-volume-strike'
144
149
  TAKER_FLOW = '/api/v5/rubik/stat/option/taker-block-volume'
150
+ CONTRACTS_OPEN_INTEREST_HISTORY = '/api/v5/rubik/stat/contracts/open-interest-history'
151
+ ANNOUNCEMENTS = '/api/v5/support/announcements'
145
152
 
146
153
  # Trade
147
154
  PLACR_ORDER = '/api/v5/trade/order'
@@ -300,4 +307,15 @@ FINANCE_LOAN_INFO = '/api/v5/finance/flexible-loan/loan-info'
300
307
  FINANCE_LOAN_HISTORY = '/api/v5/finance/flexible-loan/loan-history'
301
308
  FINANCE_INTEREST_ACCRUED = '/api/v5/finance/flexible-loan/interest-accrued'
302
309
 
310
+ # Dual Investment (DCD) — under /api/v5/finance/sfp/dcd/
311
+ DUAL_INVEST_CURRENCY_PAIRS = '/api/v5/finance/sfp/dcd/currency-pair'
312
+ DUAL_INVEST_PRODUCT_INFO = '/api/v5/finance/sfp/dcd/products'
313
+ DUAL_INVEST_REQUEST_QUOTE = '/api/v5/finance/sfp/dcd/quote'
314
+ DUAL_INVEST_TRADE = '/api/v5/finance/sfp/dcd/trade'
315
+ DUAL_INVEST_REQUEST_REDEEM_QUOTE = '/api/v5/finance/sfp/dcd/redeem-quote'
316
+ DUAL_INVEST_REDEEM = '/api/v5/finance/sfp/dcd/redeem'
317
+ DUAL_INVEST_ORDER_STATE = '/api/v5/finance/sfp/dcd/order-status'
318
+ DUAL_INVEST_ORDER_HISTORY = '/api/v5/finance/sfp/dcd/order-history'
319
+
320
+
303
321
 
okx/okxclient.py CHANGED
@@ -14,7 +14,16 @@ from . import consts as c, utils, exceptions
14
14
  class OkxClient(Client):
15
15
 
16
16
  def __init__(self, api_key='-1', api_secret_key='-1', passphrase='-1', use_server_time=None, flag='1',base_api=c.API_URL, debug=False, proxy=None):
17
- super().__init__(base_url=base_api, http2=True, proxy=proxy)
17
+ # Compatible with different versions of httpx
18
+ # New versions (0.24.0+) use proxy, older versions use proxies
19
+ try:
20
+ super().__init__(base_url=base_api, http2=True, proxy=proxy)
21
+ except TypeError:
22
+ # Older versions of httpx use proxies parameter
23
+ if proxy:
24
+ super().__init__(base_url=base_api, http2=True, proxies={'http://': proxy, 'https://': proxy})
25
+ else:
26
+ super().__init__(base_url=base_api, http2=True)
18
27
  self.API_KEY = api_key
19
28
  self.API_SECRET_KEY = api_secret_key
20
29
  self.PASSPHRASE = passphrase
@@ -1,6 +1,9 @@
1
1
  import asyncio
2
2
  import json
3
3
  import logging
4
+ import warnings
5
+
6
+ from websockets.exceptions import ConnectionClosedError
4
7
 
5
8
  from okx.websocket import WsUtils
6
9
  from okx.websocket.WebSocketFactory import WebSocketFactory
@@ -9,7 +12,7 @@ logger = logging.getLogger(__name__)
9
12
 
10
13
 
11
14
  class WsPrivateAsync:
12
- def __init__(self, apiKey, passphrase, secretKey, url, useServerTime):
15
+ def __init__(self, apiKey, passphrase, secretKey, url, useServerTime=None, debug=False):
13
16
  self.url = url
14
17
  self.subscriptions = set()
15
18
  self.callback = None
@@ -18,27 +21,49 @@ class WsPrivateAsync:
18
21
  self.apiKey = apiKey
19
22
  self.passphrase = passphrase
20
23
  self.secretKey = secretKey
21
- self.useServerTime = useServerTime
24
+ self.useServerTime = False
25
+ self.websocket = None
26
+ self.debug = debug
27
+
28
+ # Set log level
29
+ if debug:
30
+ logger.setLevel(logging.DEBUG)
31
+
32
+ # Deprecation warning for useServerTime parameter
33
+ if useServerTime is not None:
34
+ warnings.warn("useServerTime parameter is deprecated. Please remove it.", DeprecationWarning)
22
35
 
23
36
  async def connect(self):
24
37
  self.websocket = await self.factory.connect()
25
38
 
26
39
  async def consume(self):
27
- async for message in self.websocket:
28
- logger.debug("Received message: {%s}", message)
40
+ try:
41
+ async for message in self.websocket:
42
+ if self.debug:
43
+ logger.debug("Received message: {%s}", message)
44
+ if self.callback:
45
+ self.callback(message)
46
+ except ConnectionClosedError as e:
47
+ logger.error(f"WebSocket connection closed: {e}")
29
48
  if self.callback:
30
- self.callback(message)
49
+ self.callback(json.dumps({"event": "error", "code": "ConnClosed", "msg": str(e)}))
50
+ raise
31
51
 
32
- async def subscribe(self, params: list, callback):
52
+ async def subscribe(self, params: list, callback, id: str = None):
33
53
  self.callback = callback
34
54
 
35
55
  logRes = await self.login()
36
56
  await asyncio.sleep(5)
37
57
  if logRes:
38
- payload = json.dumps({
58
+ payload_dict = {
39
59
  "op": "subscribe",
40
60
  "args": params
41
- })
61
+ }
62
+ if id is not None:
63
+ payload_dict["id"] = id
64
+ payload = json.dumps(payload_dict)
65
+ if self.debug:
66
+ logger.debug(f"subscribe: {payload}")
42
67
  await self.websocket.send(payload)
43
68
  # await self.consume()
44
69
 
@@ -49,28 +74,139 @@ class WsPrivateAsync:
49
74
  passphrase=self.passphrase,
50
75
  secretKey=self.secretKey
51
76
  )
77
+ if self.debug:
78
+ logger.debug(f"login: {loginPayload}")
52
79
  await self.websocket.send(loginPayload)
53
80
  return True
54
81
 
55
- async def unsubscribe(self, params: list, callback):
82
+ async def unsubscribe(self, params: list, callback, id: str = None):
56
83
  self.callback = callback
57
- payload = json.dumps({
84
+ payload_dict = {
58
85
  "op": "unsubscribe",
59
86
  "args": params
60
- })
61
- logger.info(f"unsubscribe: {payload}")
87
+ }
88
+ if id is not None:
89
+ payload_dict["id"] = id
90
+ payload = json.dumps(payload_dict)
91
+ if self.debug:
92
+ logger.debug(f"unsubscribe: {payload}")
93
+ else:
94
+ logger.info(f"unsubscribe: {payload}")
62
95
  await self.websocket.send(payload)
63
- # for param in params:
64
- # self.subscriptions.discard(param)
96
+
97
+ async def send(self, op: str, args: list, callback=None, id: str = None):
98
+ """
99
+ Generic send method
100
+ :param op: Operation type
101
+ :param args: Parameter list
102
+ :param callback: Callback function
103
+ :param id: Optional request ID
104
+ """
105
+ if callback:
106
+ self.callback = callback
107
+ payload_dict = {
108
+ "op": op,
109
+ "args": args
110
+ }
111
+ if id is not None:
112
+ payload_dict["id"] = id
113
+ payload = json.dumps(payload_dict)
114
+ if self.debug:
115
+ logger.debug(f"send: {payload}")
116
+ await self.websocket.send(payload)
117
+
118
+ async def place_order(self, args: list, callback=None, id: str = None):
119
+ """
120
+ Place order
121
+ :param args: Order parameter list
122
+ :param callback: Callback function
123
+ :param id: Optional request ID
124
+ """
125
+ if callback:
126
+ self.callback = callback
127
+ await self.send("order", args, id=id)
128
+
129
+ async def batch_orders(self, args: list, callback=None, id: str = None):
130
+ """
131
+ Batch place orders
132
+ :param args: Batch order parameter list
133
+ :param callback: Callback function
134
+ :param id: Optional request ID
135
+ """
136
+ if callback:
137
+ self.callback = callback
138
+ await self.send("batch-orders", args, id=id)
139
+
140
+ async def cancel_order(self, args: list, callback=None, id: str = None):
141
+ """
142
+ Cancel order
143
+ :param args: Cancel order parameter list
144
+ :param callback: Callback function
145
+ :param id: Optional request ID
146
+ """
147
+ if callback:
148
+ self.callback = callback
149
+ await self.send("cancel-order", args, id=id)
150
+
151
+ async def batch_cancel_orders(self, args: list, callback=None, id: str = None):
152
+ """
153
+ Batch cancel orders
154
+ :param args: Batch cancel order parameter list
155
+ :param callback: Callback function
156
+ :param id: Optional request ID
157
+ """
158
+ if callback:
159
+ self.callback = callback
160
+ await self.send("batch-cancel-orders", args, id=id)
161
+
162
+ async def amend_order(self, args: list, callback=None, id: str = None):
163
+ """
164
+ Amend order
165
+ :param args: Amend order parameter list
166
+ :param callback: Callback function
167
+ :param id: Optional request ID
168
+ """
169
+ if callback:
170
+ self.callback = callback
171
+ await self.send("amend-order", args, id=id)
172
+
173
+ async def batch_amend_orders(self, args: list, callback=None, id: str = None):
174
+ """
175
+ Batch amend orders
176
+ :param args: Batch amend order parameter list
177
+ :param callback: Callback function
178
+ :param id: Optional request ID
179
+ """
180
+ if callback:
181
+ self.callback = callback
182
+ await self.send("batch-amend-orders", args, id=id)
183
+
184
+ async def mass_cancel(self, args: list, callback=None, id: str = None):
185
+ """
186
+ Mass cancel orders
187
+ Note: This method is for /ws/v5/business channel, rate limit: 1 request/second
188
+ :param args: Cancel parameter list, contains instType and instFamily
189
+ :param callback: Callback function
190
+ :param id: Optional request ID
191
+ """
192
+ if callback:
193
+ self.callback = callback
194
+ await self.send("mass-cancel", args, id=id)
65
195
 
66
196
  async def stop(self):
67
197
  await self.factory.close()
68
- self.loop.stop()
69
198
 
70
199
  async def start(self):
71
- logger.info("Connecting to WebSocket...")
200
+ if self.debug:
201
+ logger.debug("Connecting to WebSocket...")
202
+ else:
203
+ logger.info("Connecting to WebSocket...")
72
204
  await self.connect()
73
- self.loop.create_task(self.consume())
205
+ return self.loop.create_task(self.consume())
74
206
 
75
207
  def stop_sync(self):
76
- self.loop.run_until_complete(self.stop())
208
+ if self.loop.is_running():
209
+ future = asyncio.run_coroutine_threadsafe(self.stop(), self.loop)
210
+ future.result(timeout=10)
211
+ else:
212
+ self.loop.run_until_complete(self.stop())
@@ -2,54 +2,132 @@ import asyncio
2
2
  import json
3
3
  import logging
4
4
 
5
+ from websockets.exceptions import ConnectionClosedError
6
+
7
+ from okx.websocket import WsUtils
5
8
  from okx.websocket.WebSocketFactory import WebSocketFactory
6
9
 
7
10
  logger = logging.getLogger(__name__)
8
11
 
9
12
 
10
13
  class WsPublicAsync:
11
- def __init__(self, url):
14
+ def __init__(self, url, apiKey='', passphrase='', secretKey='', debug=False):
12
15
  self.url = url
13
16
  self.subscriptions = set()
14
17
  self.callback = None
15
18
  self.loop = asyncio.get_event_loop()
16
19
  self.factory = WebSocketFactory(url)
20
+ self.websocket = None
21
+ self.debug = debug
22
+ # Credentials for business channel login
23
+ self.apiKey = apiKey
24
+ self.passphrase = passphrase
25
+ self.secretKey = secretKey
26
+ self.isLoggedIn = False
27
+
28
+ # Set log level
29
+ if debug:
30
+ logger.setLevel(logging.DEBUG)
17
31
 
18
32
  async def connect(self):
19
33
  self.websocket = await self.factory.connect()
20
34
 
21
35
  async def consume(self):
22
- async for message in self.websocket:
23
- logger.debug("Received message: {%s}", message)
36
+ try:
37
+ async for message in self.websocket:
38
+ if self.debug:
39
+ logger.debug("Received message: {%s}", message)
40
+ if self.callback:
41
+ self.callback(message)
42
+ except ConnectionClosedError as e:
43
+ logger.error(f"WebSocket connection closed: {e}")
24
44
  if self.callback:
25
- self.callback(message)
45
+ self.callback(json.dumps({"event": "error", "code": "ConnClosed", "msg": str(e)}))
46
+ raise
47
+
48
+ async def login(self):
49
+ """
50
+ Login method for business channel that requires authentication (e.g. /ws/v5/business)
51
+ """
52
+ if not self.apiKey or not self.secretKey or not self.passphrase:
53
+ raise ValueError("apiKey, secretKey and passphrase are required for login")
26
54
 
27
- async def subscribe(self, params: list, callback):
55
+ loginPayload = WsUtils.initLoginParams(
56
+ useServerTime=False,
57
+ apiKey=self.apiKey,
58
+ passphrase=self.passphrase,
59
+ secretKey=self.secretKey
60
+ )
61
+ if self.debug:
62
+ logger.debug(f"login: {loginPayload}")
63
+ await self.websocket.send(loginPayload)
64
+ self.isLoggedIn = True
65
+ return True
66
+
67
+ async def subscribe(self, params: list, callback, id: str = None):
28
68
  self.callback = callback
29
- payload = json.dumps({
69
+ payload_dict = {
30
70
  "op": "subscribe",
31
71
  "args": params
32
- })
72
+ }
73
+ if id is not None:
74
+ payload_dict["id"] = id
75
+ payload = json.dumps(payload_dict)
76
+ if self.debug:
77
+ logger.debug(f"subscribe: {payload}")
33
78
  await self.websocket.send(payload)
34
79
  # await self.consume()
35
80
 
36
- async def unsubscribe(self, params: list, callback):
81
+ async def unsubscribe(self, params: list, callback, id: str = None):
37
82
  self.callback = callback
38
- payload = json.dumps({
83
+ payload_dict = {
39
84
  "op": "unsubscribe",
40
85
  "args": params
41
- })
42
- logger.info(f"unsubscribe: {payload}")
86
+ }
87
+ if id is not None:
88
+ payload_dict["id"] = id
89
+ payload = json.dumps(payload_dict)
90
+ if self.debug:
91
+ logger.debug(f"unsubscribe: {payload}")
92
+ else:
93
+ logger.info(f"unsubscribe: {payload}")
94
+ await self.websocket.send(payload)
95
+
96
+ async def send(self, op: str, args: list, callback=None, id: str = None):
97
+ """
98
+ Generic send method
99
+ :param op: Operation type
100
+ :param args: Parameter list
101
+ :param callback: Callback function
102
+ :param id: Optional request ID
103
+ """
104
+ if callback:
105
+ self.callback = callback
106
+ payload_dict = {
107
+ "op": op,
108
+ "args": args
109
+ }
110
+ if id is not None:
111
+ payload_dict["id"] = id
112
+ payload = json.dumps(payload_dict)
113
+ if self.debug:
114
+ logger.debug(f"send: {payload}")
43
115
  await self.websocket.send(payload)
44
116
 
45
117
  async def stop(self):
46
118
  await self.factory.close()
47
- self.loop.stop()
48
119
 
49
120
  async def start(self):
50
- logger.info("Connecting to WebSocket...")
121
+ if self.debug:
122
+ logger.debug("Connecting to WebSocket...")
123
+ else:
124
+ logger.info("Connecting to WebSocket...")
51
125
  await self.connect()
52
- self.loop.create_task(self.consume())
126
+ return self.loop.create_task(self.consume())
53
127
 
54
128
  def stop_sync(self):
55
- self.loop.run_until_complete(self.stop())
129
+ if self.loop.is_running():
130
+ future = asyncio.run_coroutine_threadsafe(self.stop(), self.loop)
131
+ future.result(timeout=10)
132
+ else:
133
+ self.loop.run_until_complete(self.stop())
@@ -1,21 +1,42 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: python-okx
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Python SDK for OKX
5
5
  Home-page: https://okx.com/docs-v5/
6
6
  Author: okxv5api
7
7
  Author-email: api@okg.com
8
8
  Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
9
15
  Classifier: License :: OSI Approved :: MIT License
10
16
  Classifier: Operating System :: OS Independent
17
+ Requires-Python: >=3.7
11
18
  Description-Content-Type: text/markdown
12
- Requires-Dist: importlib-metadata
13
- Requires-Dist: httpx[http2]
14
- Requires-Dist: keyring
15
- Requires-Dist: loguru
16
- Requires-Dist: requests
17
- Requires-Dist: Twisted
18
- Requires-Dist: pyOpenSSL
19
+ Requires-Dist: httpx[http2]>=0.24.0
20
+ Requires-Dist: requests>=2.25.0
21
+ Requires-Dist: websockets>=10.0
22
+ Requires-Dist: certifi>=2021.0.0
23
+ Requires-Dist: loguru>=0.7.0
24
+ Requires-Dist: python-dotenv>=1.0.0
25
+ Requires-Dist: pytest>=7.0.0
26
+ Requires-Dist: pytest-asyncio>=0.21.0
27
+ Requires-Dist: pytest-cov>=4.0.0
28
+ Requires-Dist: ruff>=0.1.0
29
+ Requires-Dist: build>=1.0.0
30
+ Requires-Dist: twine>=4.0.0
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: classifier
34
+ Dynamic: description
35
+ Dynamic: description-content-type
36
+ Dynamic: home-page
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
19
40
 
20
41
  ### Overview
21
42
  This is an unofficial Python wrapper for the [OKX exchange v5 API](https://www.okx.com/okx-api)
@@ -37,22 +58,83 @@ Make sure you update often and check the [Changelog](https://www.okx.com/docs-v5
37
58
  - Websocket handling with reconnection and multiplexed connections
38
59
 
39
60
  ### Quick start
40
- #### Prerequisites
41
61
 
42
- `python version:>=3.9`
62
+ #### Prerequisites
43
63
 
44
- `WebSocketAPI: websockets package advise version 6.0`
64
+ `python version:>=3.7`
45
65
 
46
- #### Step 1: register an account on OKX and apply for an API key
66
+ #### Step 1: Register an account on OKX and apply for an API key
47
67
  - Register for an account: https://www.okx.com/account/register
48
68
  - Apply for an API key: https://www.okx.com/account/users/myApi
49
69
 
50
- #### Step 2: install python-okx
70
+ #### Step 2: Install python-okx
51
71
 
52
- ```python
72
+ ```bash
53
73
  pip install python-okx
54
74
  ```
55
75
 
76
+ ### API Credentials
77
+
78
+ #### Option 1: Hardcoded credentials
79
+
80
+ ```python
81
+ from okx import Account
82
+
83
+ account = Account.AccountAPI(
84
+ api_key="your-api-key-here",
85
+ api_secret_key="your-api-secret-here",
86
+ passphrase="your-passphrase-here",
87
+ flag="1", # 0 = live trading, 1 = demo trading
88
+ debug=False
89
+ )
90
+ ```
91
+
92
+ #### Option 2: Using `.env` file (recommended)
93
+
94
+ Create a `.env` file in your project root:
95
+
96
+ ```bash
97
+ OKX_API_KEY=your-api-key-here
98
+ OKX_API_SECRET=your-api-secret-here
99
+ OKX_PASSPHRASE=your-passphrase-here
100
+ OKX_FLAG=1
101
+ ```
102
+
103
+ Then load it in your code:
104
+
105
+ ```python
106
+ import os
107
+ from dotenv import load_dotenv
108
+ from okx import Account
109
+
110
+ load_dotenv()
111
+
112
+ account = Account.AccountAPI(
113
+ api_key=os.getenv('OKX_API_KEY'),
114
+ api_secret_key=os.getenv('OKX_API_SECRET'),
115
+ passphrase=os.getenv('OKX_PASSPHRASE'),
116
+ flag=os.getenv('OKX_FLAG', '1'),
117
+ debug=False
118
+ )
119
+ ```
120
+
121
+ ### Development Setup
122
+
123
+ For contributors or local development:
124
+
125
+ ```bash
126
+ # Clone the repository
127
+ git clone https://github.com/okxapi/python-okx.git
128
+ cd python-okx
129
+
130
+ # Install dependencies
131
+ pip install -r requirements.txt
132
+ pip install -e .
133
+
134
+ # Run tests
135
+ pytest test/unit/ -v
136
+ ```
137
+
56
138
  #### Step 3: Run examples
57
139
 
58
140
  - Fill in API credentials in the corresponding examples
@@ -1,21 +1,22 @@
1
- okx/Account.py,sha256=8j5WDOt6b3ehlid7XKELa2xq0h-0LmV-7N6DZn5J2Qw,14160
1
+ okx/Account.py,sha256=mK_q3P1ZcPI8iuZuZ5TrMIV4eOLQUP3O3Ts3vasLQMI,16143
2
2
  okx/BlockTrading.py,sha256=A4OcuLuEHucAIhc8doPGr2o6dsCjVbv-a6YiTlzwdEw,4128
3
- okx/Convert.py,sha256=xEB2pwEUpc-k23hA3mYIe3EKB5szIXlEe96JFiRSPpc,1575
3
+ okx/Convert.py,sha256=AlFKMOnn6UPpFvak6iQSpRq6UtrBSBNaYwwZTRRBk6E,1881
4
4
  okx/CopyTrading.py,sha256=Mso5GE9UQTJVrL4V3jIyU1QWBtzr-m32edrGDoRHk84,2751
5
+ okx/DualInvest.py,sha256=CTuvjI6KDgkB4trfjFZLiBNB9FO0r3gampPx3JkioE8,2782
5
6
  okx/FDBroker.py,sha256=nUpcR_XtUcKa1Jk_B1OUZCjiv9ifQnxGRsLNHDxdGno,788
6
- okx/Funding.py,sha256=JN6A7-uUJ83Y-AtqmBr7JUVHOa0lJtABQTS_lQ-6H7g,5173
7
- okx/Grid.py,sha256=r8cZidY2okqrb-zpyTsFJrecPzw2s25ySH3tOBDnDtA,6691
7
+ okx/Funding.py,sha256=lQcx1JUyebhbQGd978K_LuJUTi52Wi1GjrnI40i9Xrc,5040
8
+ okx/Grid.py,sha256=8rPEjojbyZ_kJdzvHMHVkVTp-xrdUC0SCQ2rmY35rPA,6911
8
9
  okx/MarketData.py,sha256=4GO8Vdkh02GQFJeqC0k4ej3w49wkZroEOoVJ407_ju4,4827
9
- okx/PublicData.py,sha256=HEq4cNCk5P1mhLaMewJhVJTvHm0I2bdrL8Z6pH3yYn0,5136
10
+ okx/PublicData.py,sha256=SkiHA3AiXhPHjZ4OK_k7YyAX6A5i3gbEWZDk5swibHE,5938
10
11
  okx/SpreadTrading.py,sha256=Dy7VZ8WK4HyxhMvLY1G5RukRHI_T3d0fnrHZifv9Uyw,3227
11
12
  okx/Status.py,sha256=hGnn-tw302xXCDWly7LssaG3-29HcHZRhrBFUuGrSS0,495
12
13
  okx/SubAccount.py,sha256=BcwEzi_Y63k-rS9IffFKAJ2ywVl7o9J0U9sBzg87Lpw,3264
13
- okx/Trade.py,sha256=Dj-1scJgJzZD2YAoCLys-xw3lVwRw_Bi8nSfLvYfCuk,11423
14
- okx/TradingData.py,sha256=ig9Y3f-CH_nyBvoZCF4M9z2_wQXF-r0On7p9EMMuDMo,2331
15
- okx/__init__.py,sha256=PP0pJpYdSa9UD-5boOMsBSXVIA0syxuXpYcF_dh9HGY,58
16
- okx/consts.py,sha256=xvfy5ZYNgne9GUmzfoNL634vu3xXJCNpM_vJqoyBGsw,14705
14
+ okx/Trade.py,sha256=nw93Zar92U5lIJ5WhPK6v-W8l_J_-sAY4ZvIx8Njp_U,13880
15
+ okx/TradingData.py,sha256=T_55BXEcEXj7Ti5eO1hfRo6fgdg5i2VxXYl4G3acgnE,3498
16
+ okx/__init__.py,sha256=_pOZk8eys0MjXeHo_Sk8gNJ8aujfqAwRu-iN7m7khSY,58
17
+ okx/consts.py,sha256=oPuecL5y3iBt6GfSubJPFIzE5gounrISJUrGK_q7At0,15745
17
18
  okx/exceptions.py,sha256=ERuCnvnudJNBNACh3LNE3km5GJy0BGC5C3OKjgHXFaU,1182
18
- okx/okxclient.py,sha256=wJD8gTyHs92WjLffANbWLCgfX4Mnxh-IKnnJ2nUYHsI,2597
19
+ okx/okxclient.py,sha256=wywtgB73QelIlsQHFkOcRQsichdpm1n8AlYizB9S_rI,3040
19
20
  okx/utils.py,sha256=yMlqB3OF6NDBrz7P6rEmD8PWe2xeqh0Gw8lceUFWy0A,1879
20
21
  okx/Finance/EthStaking.py,sha256=hOl_i4VOBjejeyBboG1-37rhVnlSoOZ44e-Jzger-bQ,1612
21
22
  okx/Finance/FlexibleLoan.py,sha256=L-j1O73fEIIiM_h8NvCT6NocUv6KAx2WEQU6F9GRzIw,2351
@@ -24,11 +25,11 @@ okx/Finance/SolStaking.py,sha256=VcEU4ukbU5bO8qe00DDfFJ0DUIZZS9ZO_-A2evJz5e0,160
24
25
  okx/Finance/StakingDefi.py,sha256=eWyAAmMDht4B4N5VWf3bj3FxSBETMXaay-Luu4GN3ls,2250
25
26
  okx/Finance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
27
  okx/websocket/WebSocketFactory.py,sha256=ba0ZNUggghZ_YlWYB7g8UCv6MewqVr9wn75YOtIU5kE,845
27
- okx/websocket/WsPrivateAsync.py,sha256=zmZuInowW9_YWpQoZoicbbff-UwWvWxOluKclomNcXA,2240
28
- okx/websocket/WsPublicAsync.py,sha256=yDk_2jkgM4M_sq6zSL6i-KfdoSOaSd5KsmA1wqbIx3E,1520
28
+ okx/websocket/WsPrivateAsync.py,sha256=yssG16oniViPQhIUb52kPj_TmNhrulXY7GknkkjAZ7Q,6992
29
+ okx/websocket/WsPublicAsync.py,sha256=rOBVvO1Hm0SeIKGJYanom0Vs4FjH8wZHFNjsaXCJWJA,4269
29
30
  okx/websocket/WsUtils.py,sha256=ynkCtfvkrwBUiCi-_yqp1xTn3hhVd_o5ITIhD1xLdBo,2199
30
31
  okx/websocket/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- python_okx-0.4.0.dist-info/METADATA,sha256=ijdYOjmpA5LpNatTWHij0knW2PChlpd7Mzi2SUdWa-o,3103
32
- python_okx-0.4.0.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
33
- python_okx-0.4.0.dist-info/top_level.txt,sha256=rrbhHZwiw4YlhG1LWWaAODvRxyZr3onxFnqwsHdikDc,4
34
- python_okx-0.4.0.dist-info/RECORD,,
32
+ python_okx-0.4.2.dist-info/METADATA,sha256=YbPxhLxTFDkwS6p140kxhhWfX2VaeeumsS1m5sz_vNc,4912
33
+ python_okx-0.4.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
34
+ python_okx-0.4.2.dist-info/top_level.txt,sha256=rrbhHZwiw4YlhG1LWWaAODvRxyZr3onxFnqwsHdikDc,4
35
+ python_okx-0.4.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (82.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5