"use strict"

class OKEXDemo extends Exchange {
	static meta = {
		aliases: [
			"OKEXDEMO",
			"OKEXSANDBOX",
			"OKEX-SANDBOX",
			"OKEX-DEMO",
			"OKXDEMO",
			"OKXSANDBOX",
			"OKX-SANDBOX",
			"OKX-DEMO",
		],
		endpoint: "https://www.okx.com",
		fields: {
			public: {
				label: "API Key",
			},
			private: {
				label: "Secret Key",
			},
			password: {
				label: "Passphrase",
			},
/*			cache: {
				label: "Use Cache",
				message: "Cache market states (hedge, margin type & leverage) for faster entries – avoid manual state changes!",
				type: "switch",
				optional: true, default: true
			}
*/		},
		name: "OKX Demo",
		permissions: {
			origins: [
				"https://www.okx.com/api/*",
			],
		},
		subscriptions: {
			active: [],
			inactive: [],
		}, d: true,
		website: "https://www.okx.com/join/1834645",
		desc: "OKX is the leading global bitcoin exchange. Secured with bank-level SSL encryption and cold storage. Distributed servers for high-speed bitcoin trading based on real-time data.",
		margin: true, spot: true, posMode: true, marginType: true,
		testSymbol: "BTC-USDT",
		multiAsset: "USD"+marginSuffix,
	}
	constructor(meta = OKEXDemo.meta) {
		super(meta)
	}

	async req(method, resource, params, sign = true) {
		const headers = {"Content-Type": "application/json"}
		if (params && method !== 'POST') {
			resource += '?'+serialize(params)
			params = null
		}
		params = params ? JSON.stringify(params) : ''

		if (sign) {
			const credentials = this.getCredentials("private")
			const ts = this.getTimestamp(true)
			headers["OK-ACCESS-KEY"] = credentials.public
			headers["OK-ACCESS-SIGN"] = CryptoJS.HmacSHA256(ts + method + resource + params, credentials.private).toString(CryptoJS.enc.Base64)
			headers["OK-ACCESS-TIMESTAMP"] = ts
			headers["OK-ACCESS-PASSPHRASE"] = credentials.password
		} else this.hasAccess()
		headers['x-simulated-trading'] = this.meta.d && !0+0 || 0+0

		const result = await request.call(this, method, this.meta.endpoint + resource, params, headers)
		if (!result || result.code !== '0') {
			throw new Error(result && (result.data && result.data[0] && result.data[0].sMsg || result.msg) || "Unknown connection error with API endpoint!")
		}
		return result.data
	}

	async time() {
		return (await request.call(this, 'GET', this.meta.endpoint + "/api/v5/public/time", null, null)).data[0].ts
	}

	async account(currency) {
		let balances = {}
		const isTest = this.isTest()

		const data = (await this.req('GET', "/api/v5/account/balance"))[0]
		if (!data || !data.details || !data.details.forEach) {
			this.info("unable to get account info!")
			return balances
		}
		if (data.totalEq) {
			if (data.adjEq) {
				// 2 = portfolio margin, 1 = multi-currency margin
				balances.isMulti = data.ordFroz === "" ? 2 : 1
			}
			balances[this.meta.multiAsset] = {
				available: Number(data.adjEq) || 0,
				balance: Number(data.totalEq) || 0,
			}
		}
		data.details.forEach(info => {
			if (info.eq || info.ccy == currency) {
				balances[info.ccy] = {
					available: Number(info.cashBal),
					balance: Number(info.eq)
				}
			}
		})
		isTest && (balances[currency] = balances[currency] || {available: 0, balance: 0})
		return balances
	}

	sym(name) {
		return super.sym(name).replace('SWAP', 'PERP')
	}

	async symbolInfoImpl(cache) {
		for (const instType of ['SPOT', 'SWAP', 'FUTURES', 'OPTION']) {
			const ulys = instType === 'OPTION' && (await this.req('GET', "/api/v5/public/underlying", {instType}, false))[0] || ['']
			for (const uly of ulys) {
				const response = await this.req('GET', "/api/v5/public/instruments", {instType, uly}, false)
				response?.forEach(info => {
					info.pricePrecision = decimals(info.tickSz) + (!info.tickSz.endsWith('1') ? Number(info.tickSz) % 1 : 0)
					const lotSz = instType === 'SPOT' || instType === 'SWAP' ? info.lotSz : info.ctVal
					info.quantityPrecision = decimals(lotSz) + (!lotSz.endsWith('1') ? Number(lotSz) % 1 : 0)
					info.lever = Number(info.lever)
					const name = this.sym(info.instId)
					cache[name] = info
					if (info.alias) {
						const alias = ({'this_week': '1W', 'next_week': '2W', 'this_month': '1M', 'next_month': '2M', 'quarter': '3M', 'next_quarter': '6M'})[info.alias]
						if (alias) cache[name.slice(0, -6) + alias] = info
					}
				})
			}
		}
	}

	async symbolTickerImpl(symbol) {
		let ticker = await this.req('GET', "/api/v5/market/ticker", {instId: symbol}, false)
		ticker = ticker && ticker[0]

		return ticker && {
			ask: Number(ticker.askPx),
			bid: Number(ticker.bidPx),
			mid: (Number(ticker.askPx) + Number(ticker.bidPx)) / 2,
			last: Number(ticker.last)
		}
	}

	async getLeverage(market, cross) {
		const def = Math.min(market.lever || 1, 10)
		const sideLev = {long: def, short: def, isNet: false}
		try {
			const result = await this.req('GET', "/api/v5/account/leverage-info", {instId: market.instId, mgnMode: cross ? "cross" : "isolated"})
			result.forEach(pos => {
				if (pos.posSide === "net") {
					sideLev.long = sideLev.short = Number(pos.lever)
					sideLev.isNet = true
				} else {
					sideLev[pos.posSide] = Number(pos.lever)
				}
			})
		} catch (ex) {}
		return sideLev
	}

	contracts(market, type, isMulti, balance, price, cmd = {}) {
		const toBase = market.ctType === "linear" || 
			(type === "MARGIN" && cmd.isAsk) || 
			(type === "SPOT" && cmd.isBid && cmd.t !== "market")

		const toQuote = market.ctType === "inverse" || 
			(type === "MARGIN" && cmd.isBid && cmd.t === "market")

		return (toBase ? balance / price : toQuote && !isMulti ? balance * price : balance) / (market.ctVal || 1)
	}

	async trade(cmd, ohlc = {}) {
		const market = await this.symbolInfo(cmd.s)
		cmd._sym = market.instId
		const ticker = (cmd.pc && !cmd.up && this.getPrice(market.instId)) || (await this.symbolTicker(cmd.up, market.instId))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${market.instId} is not available!`)
		}
		this.checkSlip(cmd, ohlc, market.instId, ticker)
		if (cmd.up) {
			this.updatePrice(market.instId, ticker)
			if (!cmd.b && !cmd.ub) {
				return ticker
			}
		}

		const type = market.instType === 'SPOT' && cmd.isMargin ? 'MARGIN' : market.instType
		const isSpot = type === 'SPOT'
		const isMargin = type === 'MARGIN'
		const isSwap = type === 'SWAP'
		const isFuture = type === 'FUTURES' || type === 'OPTION'
		const isBasic = isSpot || isMargin
		const isContracts = isSwap || isFuture
		let isMulti = this.getBalanceRaw().isMulti || 0
		let ccy = isBasic ? ((cmd.isBid && !isMargin) || (cmd.isAsk && isMargin) ? market.quoteCcy : market.baseCcy) : market.settleCcy
		// $$$$$ verify for isMulti && isBasic => multi asset or ccy???
		let currency = cmd.currency = isMulti && !isBasic ? this.meta.multiAsset : ccy

		let balance = {available: 0, balance: 0}
		if ((cmd.y !== "possize" && cmd.q.wasPercent()) || cmd.ub) {
			balance = cmd.bc && !cmd.ub && this.getBalance(currency)
			const balances = !balance && (await this.account(currency))
			isMulti = isMulti || balances?.isMulti
			if (isMulti && !isBasic) currency = this.meta.multiAsset
			balance = balance || balances[currency]
			this.checkBalance(cmd, balance, currency)
			if (!cmd.bc || cmd.ub) {
				this.updateBalance(balances, currency)
				if (cmd.ub) {
					return Object.assign(ticker, balance)
				}
			}
		}

		const first = ticker[cmd.pr] || this.ohlc(cmd, ohlc, 'pr') || ((cmd.isBid && cmd.t !== "market") || (cmd.isAsk && cmd.t === "market") ? ticker.bid : ticker.ask)
		let price = (cmd.fp || cmd.p._().relative(first).minmax(cmd.minp, cmd.maxp)).resolve(market.pricePrecision)
		if (price <= 0) {
			this.warn("your p parameter might be incorrect! (price of 0 calculated, use minp= to set minimum)")
		}

		const c = {f: 1.0}
		const available = cmd.y === "equity" ? balance.balance : balance.available
		const cross = isMulti || (!isSpot && (cmd.mt ? cmd.mt === "crossed" : cmd.l === 0/* || isMulti*/))
		const sideLev = isSpot ? {} : (await this.getLeverage(market, isMulti != 2 && cross))	// $$$$$ CACHE!
		const doMode = (cmd.pm === "normal" && !sideLev.isNet) || (cmd.pm === "hedge" && sideLev.isNet)
		const isNet = (!doMode && sideLev.isNet) || (doMode && !sideLev.isNet)
		const side = cmd.isBid ? "long" : "short"
		const leverage = cmd._lev = isSpot ? 1 : cmd.l * (this.ohlc(cmd, ohlc, 'lr') || 1) || sideLev[side] || Math.min(market.lever || 1, 10)
		const doLev = cmd.l !== undefined && leverage != sideLev[side]
		const contracts = ohlc[cmd.y] || this.contracts(market, type, isMulti, available * (cmd.lc || leverage) * c.f, price, cmd)
		const isAlgo = cmd.ts || cmd.so || cmd.tp || cmd.sl

		const order = {
			instId: market.instId,
			ordType: ({market: "market", fok: "fok", ioc: "ioc", post: "post_only"})[cmd.t] || "limit",
			posSide: isBasic || isNet ? "net" : side,
			side: cmd.isBid ? 'buy' : 'sell',
			sz: cmd.q._().mul(
					!cmd.q.wasPercent() ? 
						(cmd.lc || leverage) * c.f / 
							(cmd.u === "currency" ? 
								((isSpot && cmd.isAsk) || (isMargin && cmd.isBid) ? 1 : 
								isContracts ? (isMulti ? 1 : price) * market.ctVal : cmd.t === "market" && cmd.isBid ? 1 : price)
							: 1) // $$$$
					: 1)
				.reference(contracts).mul(this.ohlc(cmd, ohlc, 'qr') || 1).add(this.addpos(cmd, ohlc)).minmax(cmd.minq, cmd.maxq).resolve(market.quantityPrecision),
			tdMode: isSpot && !isMulti ? "cash" : cross ? "cross" : "isolated"
		}
		if (cmd.lq && ohlc.left) {
			this.info("using leftover quantity from last order: "+ohlc.left)
			order.sz = new NumberObject(ohlc.left).resolve(market.quantityPrecision)
		}
		if (market.minSz && order.sz < market.minSz) {
			this.warn(`order quantity below instrument minimum of ${market.minSz} – use minq= to set minimum!`)
		} else if (order.sz <= 0) {
			this.warn("your q parameter might be incorrect! (quantity of 0 calculated, use minq= to set minimum)")
		}
		if ((cross && !isMulti) || isMulti) order.ccy = ccy
		if (isMargin && cmd.r) order.reduceOnly = true
		if (cmd.t !== "market" && !isAlgo) order.px = price

		if (cmd.ts) {
			const stop = this.ohlc(cmd, ohlc, cmd.so && 'soref' || 'tsref', ticker, price) || first
			order.ordType = 'move_order_stop'
			if (cmd.so || cmd.cbrate) {
				order.activePx = (cmd.so || cmd.ts)._().relative(stop).resolve(market.pricePrecision)
			}
			if (cmd.cbrate) {
				order.callbackRatio = cmd.cbrate
			} else {
				order.callbackSpread = cmd.ts._().reference(stop).resolve(market.pricePrecision)
			}
		} else if (cmd.so) {
			order.ordType = isNet ? 'conditional' : 'trigger'
			order[isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.so._().relative(this.ohlc(cmd, ohlc, 'soref', ticker, price) || first).resolve(market.pricePrecision)
			order[isNet ? 'slOrdPx' : 'orderPx'] = order.px || -1
			cmd.sr !== "last" && (order[isNet ? 'slTriggerPxType' : 'triggerPxType'] = cmd.sr)
		} else if (cmd.tp || cmd.sl) {
			order.ordType = cmd.tp && cmd.sl ? 'oco' : isNet ? 'conditional' : 'trigger'
			if (cmd.tp) {
				order[cmd.sl || isNet ? 'tpTriggerPx' : 'triggerPx'] = cmd.tp._().relative(this.ohlc(cmd, ohlc, 'tpref', ticker, price) || first).resolve(market.pricePrecision)
				order[cmd.sl || isNet ? 'tpOrdPx' : 'orderPx'] = order.px || -1
				cmd.sr !== "last" && (order[cmd.sl || isNet ? 'tpTriggerPxType' : 'triggerPxType'] = cmd.sr)
			}
			if (cmd.sl) {
				order[cmd.tp || isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.sl._().relative(this.ohlc(cmd, ohlc, 'slref', ticker, price) || first).resolve(market.pricePrecision)
				order[cmd.tp || isNet ? 'slOrdPx' : 'orderPx'] = order.tpTriggerPx && order.tpOrdPx > 0 && (order.slTriggerPx - (order.tpOrdPx - order.tpTriggerPx)) || order.px || -1
				cmd.sr !== "last" && (order[cmd.tp || isNet ? 'slTriggerPxType' : 'triggerPxType'] = cmd.sr)
			}
		}
		const opt = 'tag'
		order[opt] = this.prefixID("e58bb362f05847BC")
		order[isAlgo ? 'algoClOrdId' : 'clOrdId'] = order[opt]+this.uniqueIDA(16, cmd.id)
		if (cmd.mt === "borrow" || cmd.mt === "repay") order.quickMgnType = "auto_"+cmd.mt

		this.info(`placing ${currency} ${type} order${isSpot?'':` @ ${leverage}x ${order.tdMode} (${order.posSide !== "net" ? 'hedged' : 'one-way'})`}${isMulti ? ` (${isMulti > 1 ? 'Portfolio':'Multi-currency'} margin)`:''}: `+stringify(order))
		if (cmd.d) {
			this.msgDisabled(cmd)
			return order
		}

		if (isContracts && doMode) {
			this.info(`setting position mode to ${cmd.pm}...`)
			try {
				await this.req('POST', "/api/v5/account/set-position-mode", {posMode: cmd.pm === "hedge" ? 'long_short_mode' : 'net_mode'})
				//this.clearState(market) // $$$ necessary or can be patched? (check: only hedge changes? what happens to cross & leverages?)
			} catch(ex) {
				const err = ex.msg || ex.error || ex.message
				this.warn(`couldn't change position mode, please change manually! (Error: ${err})`)
			}
		}

		if (!isSpot && doLev) {
			let posSide = order.tdMode === "cross" ? "net" : order.posSide
			this.info(`setting market leverage to ${leverage}x ${order.tdMode} (${posSide !== "net" ? 'hedged' : 'one-way'})...`)
			for (let retry = 0; retry < 2; ++retry) {
				try {
					// $$$$ if isMargin && cross => ccy = currency
					await this.req('POST', "/api/v5/account/set-leverage", {instId: market.instId, lever: leverage, mgnMode: order.tdMode, posSide: posSide})
					//this.updateState(market, posMode)
					break
				} catch(ex) {
					const err = ex.msg || ex.error || ex.message
					if (!retry && err?.includes('posSide')) {
						order.posSide = posSide = posSide !== "net" ? "net" : side
					} else {
						this.warn(`couldn't set leverage, please set manually! (Error: ${err})`); break
					}
				}
			}
		}
		this.checkOrder(order, 46223155, 16)
		for (let retry = 0; retry < 2; ++retry) {
			try {
				const result = await this.req('POST', isAlgo ? "/api/v5/trade/order-algo" : "/api/v5/trade/order", order)
				//cmd.id && result && result[0] && result[0].algoId && this.cacheId(result[0].algoId, cmd.id)
				if (!isAlgo && cmd.t === 'post' && cmd.ifr && result && result[0] && result[0].ordId) {
					const order = await this.req('GET', "/api/v5/trade/order", {instId: market.instId, ordId: result[0].ordId})
					if (order && order[0] && order[0].state === 'canceled') {
						throw new Error(errRejected)
					}
				}
				return result && Object.assign(order, result[0])
			} catch(ex) {
				const err = ex.msg || ex.error || ex.message
				if (!retry && err?.includes('posSide')) {
					order.posSide = order.posSide !== "net" ? "net" : side
				} else if (!retry && err?.includes('tdMode')) {
					order.tdMode = (isSpot || isMargin) && order.tdMode !== "cash" ? "cash" : cross ? "cross" : "isolated"
				} else throw new Error(err)
			}
		}
	}

	async ordersCancel(cmd, ohlc = {}) {
		const market = await this.symbolInfo(cmd.s)
		cmd._sym = market.instId
		const type = market.instType === 'SPOT' && cmd.isMargin ? 'MARGIN' : market.instType

		let orders = []
		for (const ordType of ['', 'conditional', 'oco', 'trigger', 'move_order_stop']) {
			const result = await this.req('GET', ordType ? "/api/v5/trade/orders-algo-pending" : "/api/v5/trade/orders-pending", {instType: type, instId: market.instId, ordType: ordType})
			if (!result || !result.filter) {
				this.msgOrderErr()
				return false
			}
			ordType && result.forEach(order => order.px = order.ordPx)
			orders.push(...result)
		}
		if (!orders.length) {
			this.msgOrdersNone()
			return false
		}

		const ids = (cmd.id || "").split(',')
		const idsa = ids.map(e => alnum(e))
		const id = this.prefixID("e58bb362f05847BC")
		const hasType = cmd.has('t') && (cmd.t === "limit" || cmd.t === "market")
		const match = cmd.cr ? false : true
		const tickers = {}
		await this.filterOrderStart(cmd, ohlc, tickers, orders)
		const totalOrders = orders.length
		orders = orders.filter(order => {
			if ((cmd.isBid && order.side !== 'buy') || (cmd.isAsk && order.side !== 'sell')) {
				return false
			}
			if (cmd.fp && cmd.fp.compare(order.px)) {
				return !match
			}
			if ((cmd.tp && !order.tpTriggerPx) || (cmd.sl && !order.slTriggerPx) || 
				(cmd.so && order.ordType !== "conditional" && order.ordType !== "trigger") || 
				(cmd.ts && order.ordType !== "move_order_stop")) {
				return !match
			}
			if (hasType && order.ordType !== cmd.t) {
				return !match
			}
			if (cmd.r !== undefined && !cmd.r != !order.reduceOnly) {
				return !match
			}
			if ((cmd.t === "open" && order.algoId) || (cmd.t === "close" && !order.algoId)) {
				return !match
			}
			if (cmd.id) {
				const prop = order.algoId ? 'algoClOrdId' : 'clOrdId'
				/*if (order.algoId) {
					if (!this.checkId(order.algoId, ids)) {
						return !match
					}
				} else */if (!order[prop] || !order[prop].substr(id.length).startsWithAny(idsa, true)) {
					return !match
				}
			}
			if (!this.filterOrder(cmd, ohlc, tickers, order)) {
				return !match
			}
			return match
		})

		if (cmd.cm._().reference(orders.length).getMax() < orders.length) {
			const sort = {
				"newest": 	["cTime", true],
				"oldest": 	["cTime", false],
				"highest": 	["px", true],
				"lowest": 	["px", false],
				"biggest": 	["sz", true],
				"smallest": ["sz", false]
			}
			cmd.cmo === 'random' ? shuffle(orders) : sortByIndex(orders, sort[cmd.cmo][0], sort[cmd.cmo][1])
			orders = orders.slice(0, cmd.cm.resolve(0))
		}

		this.msgOrders(cmd, orders, totalOrders)
		if (cmd.ch || cmd.d) {
			orders.length && this.msgDisabled(cmd)
			return orders
		}

		let cancelOrders = []
		// $$$$ aggregate in arrays of 10
		for (const order of orders) {
			const result = await this.req('POST', order.ordType === "move_order_stop" ? "/api/v5/trade/cancel-advance-algos" : order.algoId ? "/api/v5/trade/cancel-algos" : "/api/v5/trade/cancel-order", 
				order.algoId ? [{instId: market.instId, algoId: order.algoId}] : {instId: market.instId, ordId: order.ordId})
			if (result && result[0] && result[0].sCode === "0") {
				if (cmd.sv && order.ordType !== "move_order_stop" && !order.algoId) {
					this.info("checking for updated fill info...")
					const rt = (await this.req('GET', "/api/v5/trade/order", {instId: market.instId, ordId: order.ordId}))?.[0]
					if (rt?.fillSz && rt.fillSz != order.fillSz) {
						this.info(`found new fill info! (was: ${order.fillSz}, new: ${rt.fillSz})`)
						order.fillSz = rt.fillSz
					}
				}
				cancelOrders.push(order)
				//order.algoId && this.removeId(order.algoId)
			}
		}
		return cancelOrders
	}

	async positionsClose(cmd, pos, i, ohlc = {}, market) {
		const ticker = (cmd.pc && !cmd.up && this.getPrice(market.instId)) || (await this.symbolTicker(cmd.up, market.instId))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${market.instId} is not available!`)
		}
		ticker.pos = pos.avgPx
		ticker.liq = pos.liqPx || pos.avgPx
		this.checkSlip(cmd, ohlc, market.instId, ticker)
		cmd.up && this.updatePrice(market.instId, ticker)

		const first = ticker[cmd.sl || cmd.tp || cmd.so ? 'pos' : cmd.pr] || this.ohlc(cmd, ohlc, 'pr') || ((pos.isLong && cmd.t !== "market") || (!pos.isLong && cmd.t === "market") ? ticker.bid : ticker.ask)
		let price = (cmd.fp || cmd.p._().relative(first).minmax(cmd.minp, cmd.maxp)).resolve(market.pricePrecision)
		if (price <= 0) {
			this.warn("your p parameter might be incorrect! (price of 0 calculated, use minp= to set minimum)")
		}

		const isMargin = pos.instType === 'MARGIN'
		const isSwap = pos.instType === 'SWAP'
		const isFuture = pos.instType === 'FUTURES' || pos.instType === 'OPTION'
		const isNet = pos.posSide === 'net'
		const isContracts = isSwap || isFuture
		const leverage = cmd.l || pos.leverage || 10
		const isAlgo = cmd.ts || cmd.so || cmd.tp || cmd.sl

		const order = {
			instId: market.instId,
			ordType: ({market: "market", fok: "fok", ioc: "ioc", post: "post_only"})[cmd.t] || "limit",
			posSide: isNet ? 'net' : pos.isLong ? 'long' : 'short',
			side: pos.isLong ? 'sell' : 'buy',
			sz: cmd.q._().mul(
					!cmd.q.wasPercent() ? 
						(cmd.lc || leverage) / 
							(cmd.u === "currency" ? 
								(isMargin && !pos.isLong ? 1 : 
								isContracts ? (isMulti ? 1 : price) * market.ctVal : cmd.t === "market" && cmd.isBid ? 1 : price)
							: 1) // $$$$
					: isMargin && !pos.isLong ? 1 / price : 1)
				// check conditional (.availPos) vs trigger (.pos) $$$$
				.reference(cmd.so || (cmd.tp && !cmd.sl) || (!cmd.tp && cmd.sl) ? pos.pos : pos.availPos || pos.pos)
				.mul(this.ohlc(cmd, ohlc, 'qr') || 1).add(this.addpos(cmd, ohlc, pos)).minmax(cmd.minq, cmd.maxq).resolve(market.quantityPrecision),
			tdMode: pos.mgnMode,
			// check conditional (.availPos) vs trigger (.pos) $$$$
			reduceOnly: cmd.r === undefined ? (isMargin || (isNet && !cmd.so)) : cmd.r
		}
		if (order.sz <= 0) this.warn("your q parameter might be incorrect! (quantity of 0 calculated, use minq= to set minimum)")
		if (pos.mgnMode === "cross") order.ccy = pos.currency === this.meta.multiAsset ? pos.ccy/*pos.posCcy?*/ || "USDT" : pos.currency // $$$$$$$$ CHECK IN MULTI/PORTF MODE
		if (cmd.t !== "market" && !isAlgo) order.px = price

		if (cmd.ts) {
			const stop = this.ohlc(cmd, ohlc, cmd.so && 'soref' || 'tsref', ticker, price) || first
			order.ordType = 'move_order_stop'
			if (cmd.so || cmd.cbrate) {
				order.activePx = (cmd.so || cmd.ts)._().relative(stop).resolve(market.pricePrecision)
			}
			if (cmd.cbrate) {
				order.callbackRatio = cmd.cbrate
			} else {
				order.callbackSpread = cmd.ts._().reference(stop).resolve(market.pricePrecision)
			}
		} else if (cmd.so) {
			order.ordType = isNet ? 'conditional' : 'trigger'
			order[isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.so._().relative(this.ohlc(cmd, ohlc, 'soref', ticker, price) || first).resolve(market.pricePrecision)
			order[isNet ? 'slOrdPx' : 'orderPx'] = order.px || -1
			cmd.sr !== "last" && (order[isNet ? 'slTriggerPxType' : 'triggerPxType'] = cmd.sr)
		} else if (cmd.tp || cmd.sl) {
			order.ordType = cmd.tp && cmd.sl ? 'oco' : isNet ? 'conditional' : 'trigger'
			if (cmd.tp) {
				order[cmd.sl || isNet ? 'tpTriggerPx' : 'triggerPx'] = cmd.tp._().relative(this.ohlc(cmd, ohlc, 'tpref', ticker, price) || first).resolve(market.pricePrecision)
				order[cmd.sl || isNet ? 'tpOrdPx' : 'orderPx'] = order.px || -1
				cmd.sr !== "last" && (order[cmd.sl || isNet ? 'tpTriggerPxType' : 'triggerPxType'] = cmd.sr)
			}
			if (cmd.sl) {
				order[cmd.tp || isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.sl._().relative(this.ohlc(cmd, ohlc, 'slref', ticker, price) || first).resolve(market.pricePrecision)
				order[cmd.tp || isNet ? 'slOrdPx' : 'orderPx'] = order.tpTriggerPx && order.tpOrdPx > 0 && (order.slTriggerPx - (order.tpOrdPx - order.tpTriggerPx)) || order.px || -1
				cmd.sr !== "last" && (order[cmd.sl || isNet ? 'slTriggerPxType' : 'triggerPxType'] = cmd.sr)
			}
		}
		const opt = 'tag'
		order[opt] = "e58bb362f05847BC"
		order[isAlgo ? 'algoClOrdId' : 'clOrdId'] = order[opt]+this.uniqueIDA(16, cmd.id, i)
		if (cmd.mt === "borrow" || cmd.mt === "repay") order.quickMgnType = "auto_"+cmd.mt

		!cmd.ch && this.info(`placing Close Position order @ ${leverage}x: `+stringify(order))
		if (cmd.ch || cmd.d) {
			this.msgDisabled(cmd)
			return order
		}
		// $$$ /api/v5/trade/close-position

		this.checkOrder(order, 46223155, 16)
		const result = await this.req('POST', isAlgo ? "/api/v5/trade/order-algo" : "/api/v5/trade/order", order)
		//cmd.id && result && result[0] && result[0].algoId && this.cacheId(result[0].algoId, cmd.id)
		if (!isAlgo && cmd.t === 'post' && cmd.ifr && result && result[0] && result[0].ordId) {
			const order = await this.req('GET', "/api/v5/trade/order", {instId: market.instId, ordId: result[0].ordId})
			if (order && order[0] && order[0].state === 'canceled') {
				throw new Error(errRejected)
			}
		}
		return result && Object.assign(order, result[0])
	}

	async positionsCloseAll(cmd, ohlc, positions) {
		if (!positions) {
			const market = cmd.s && cmd.s !== '*' && (await this.symbolInfo(cmd.s))
			positions = await this.req('GET', "/api/v5/account/positions", market && {instId: market.instId})
			if (!positions || !positions.filter) {
				this.msgPosErr()
				return false
			}
			positions = positions.filter(pos => (pos.pos = Number(pos.pos)))
			if (!positions.length) {
				this.msgPosNone()
				return false
			}
		}

		const symbols = await this.symbolInfo()
		const match = cmd.cr ? false : true
		const tickers = {}
		await this.filterPosStart(cmd, ohlc, tickers, positions)
		const totalPos = positions.length
		let isMulti = this.getBalanceRaw().isMulti
		positions = positions.filter(pos => {
			pos._sym = pos.instId
			pos.symbol = this.sym(pos.instId)
			const market = symbols[pos.symbol] || {}
			pos.isLong = pos.posSide === 'net' ? (pos.posCcy ? pos.posCcy !== market.quoteCcy : pos.pos > 0) : pos.posSide === 'long'
			if ((cmd.isBid && !pos.isLong) || (cmd.isAsk && pos.isLong)) {
				return false
			}
			pos.avgPx = Number(pos.avgPx)
			if (cmd.fp && cmd.fp.compare(pos.avgPx)) {
				return !match
			}
			pos.leverage = Number(pos.lever) || 1
			if (cmd.l && cmd.l !== pos.leverage) {
				return !match
			}
			pos.currency = cmd.currency = pos.instType === 'MARGIN' ? (pos.isLong ? market.baseCcy : market.quoteCcy) : isMulti && this.meta.multiAsset || market.settleCcy
			pos.upl = toPrecision(pos.upl, 8)
			pos.pnl = Number(pos.uplRatio) / pos.leverage
			pos.cTime = Number(pos.cTime)
			pos.side = pos.isLong ? 'long' : 'short'
			pos.isolated = pos.mgnMode === "isolated"
			pos.margin = Number(pos.margin || pos.imr)
			pos.pos = Math.abs(pos.pos)
			if (pos.availPos) {
				pos.availPos = Math.abs(pos.availPos)
			} else {
				delete pos.availPos
			}
			pos._sizeCoin = pos.posCcy !== market.quoteCcy || market.ctType === "inverse" ? pos.margin : pos.margin / pos.last
			pos._sizeUSD = pos.posCcy !== market.quoteCcy || market.ctType === "inverse" ? pos.margin * pos.last : pos.margin
			if (!this.filterPos(cmd, ohlc, tickers, pos, balance => this.contracts(market, pos.instType, isMulti, balance * pos.leverage, pos.avgPx), market.quantityPrecision)) {
				return !match
			}
			return match
		})

		if (cmd.cm._().reference(positions.length).getMax() < positions.length) {
			const sort = {
				"newest": 	["cTime", true],
				"oldest": 	["cTime", false],
				"highest": 	["avgPx", true],
				"lowest": 	["avgPx", false],
				"biggest": 	["pos", true],
				"smallest": ["pos", false]
			}
			cmd.cmo === 'random' ? shuffle(positions) : sortByIndex(positions, sort[cmd.cmo][0], sort[cmd.cmo][1])
			positions = positions.slice(0, cmd.cm.resolve(0))
		}
		if (!positions.length || cmd.ch) {
			this.msgPos(cmd, positions, totalPos)
			return positions
		}

		let closeOrders = [], i = 0
		for (const pos of positions) {
			const order = await this.positionsClose(cmd, pos, i++, ohlc, symbols[pos.symbol])
			if (order) {
				closeOrders.push(order)
				Object.assign(closeOrders.last(), Object.without(pos, ["side"]))
			}
		}
		return closeOrders
	}
}

class OKEX extends OKEXDemo {
	static meta = Object.assign({}, super.meta, {
		aliases: [
			"OKEX",
			"OKX",
		],
		name: "OKX",
		subscriptions: {
			active: [
				"e3fdd77de3a682bfcfd8dc3000a25a5b",
				"4db6261b067dabf8ffb0924c18d95d06",
			],
			inactive: [
				"okexx1",
				"okexx2",
			],
		}, d: false,
	})
	constructor(meta = OKEX.meta) {
		super(meta)
	}
}

Broker.add(new OKEX())
Broker.add(new OKEXDemo())
