"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",
			},
		},
		name: "OKX Demo",
		patterns: [],
		permissions: {
			origins: [
				"https://www.okx.com/api/*",
			],
		},
		subscriptions: {
			active: [],
			inactive: [],
		},
		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() {
		super(OKEXDemo.meta)
	}

	*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 crypt = (payload, key) => {
				const sha = new jsSHA("SHA-256", "TEXT")
				sha.setHMACKey(key, "TEXT")
				sha.update(payload)
				return sha.getHMAC("B64")
			}
			const credentials = this.getCredentials("private")
			const ts = this.getTimestamp(true)
			headers["OK-ACCESS-KEY"] = credentials.public
			headers["OK-ACCESS-SIGN"] = crypt(ts + method + resource + params, credentials.private)
			headers["OK-ACCESS-TIMESTAMP"] = ts
			headers["OK-ACCESS-PASSPHRASE"] = credentials.password
		} else this.hasAccess()
		headers[_("eC1zaW11bGF0ZWQtdHJhZGluZw")] = !0+0

		const result = yield request.bind(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
	}

	*time() {
		const result = (yield request.bind(this, 'GET', this.meta.endpoint + "/api/v5/public/time", null, null)).data[0]
		return result.ts
	}

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

		const data = (yield* 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
	}

	static sym(name) {
		return name.replace(Exchange.cleanSym, '').replace('SWAP', 'PERP')
	}

	*symbolInfo(symbol) {
		const symbolCache = this.getSymbolCache()
		if (symbolCache._refresh) {
			for (const instType of ['SPOT', 'SWAP', 'FUTURES', 'OPTION']) {
				const ulys = instType === 'OPTION' && (yield* this.req('GET', "/api/v5/public/underlying", {instType}, false))[0] || ['']
				for (const uly of ulys) {
					const response = yield* 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 = OKEXDemo.sym(info.instId)
						symbolCache[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) symbolCache[name.slice(0, -6) + alias] = info
						}
					})
				}
			}
			this.updatedSymbolCache()
		}
		if (!symbol) return symbolCache

		const lookup = OKEXDemo.sym(symbol)
		if (!symbolCache[lookup]) {
			throw new Error(`Unknown market symbol: ${symbol} – Use the 'Symbols' button in the exchange settings or alert editor to browse for valid symbols!`)
		}
		return symbolCache[lookup]
	}

	*symbolTicker(symbol) {
		let ticker = yield* 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)
		}
	}

	*getLeverage(market, cross) {
		const def = Math.min(market.lever || 1, 10)
		const sideLev = {long: def, short: def, isNet: false}
		try {
			const result = yield* 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
	}

	static 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)
	}

	*trade(cmd, ohlc = {}) {
		const market = yield* this.symbolInfo(cmd.s)
		cmd._sym = market.instId
		const ticker = (cmd.pc && !cmd.up && this.getPrice(market.instId)) || (yield* this.symbolTicker(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 = 0
		let currency = cmd.currency = isBasic ? ((cmd.isBid && !isMargin) || (cmd.isAsk && isMargin) ? market.quoteCcy : market.baseCcy) : market.settleCcy

		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 && (yield* this.account(currency))
			isMulti = balances && balances.isMulti
			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] || ohlc[cmd.pr] || ((cmd.isBid && cmd.t !== "market") || (cmd.isAsk && cmd.t === "market") ? ticker.bid : ticker.ask)
		let price = cmd.fp ? cmd.fp.resolve(market.pricePrecision) : 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)")
		}

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

		ohlc[cmd.qr] && this.info(`using custom quantity multiplier: ${ohlc[cmd.qr]} (${cmd.qr})`)
		const order = {
			instId: market.instId,
			ordType: ({market: "market", fok: "fok", ioc: "ioc", post: "post_only"})[cmd.t] || "limit",
			posSide: isBasic || isNet ? "net" : cmd.isBid ? "long" : "short",
			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(ohlc[cmd.qr] || 1).minmax(cmd.minq, cmd.maxq).resolve(market.quantityPrecision),
			tdMode: isSpot ? "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) order.ccy = currency
		if (isMargin && cmd.r) order.reduceOnly = true
		if (cmd.t !== "market" && !isAlgo) order.px = price

		if (cmd.ts) {
			const actref = cmd.so ? cmd.soref : cmd.tsref
			actref && actref !== "price" && this.info(`using custom TS/SO reference: ${ohlc[actref] || ticker[actref] || "NOT FOUND!"} (${actref})`)
			actref !== "price" && (price = (actref && (ohlc[actref] || ticker[actref])) || first)
			order.ordType = 'move_order_stop'
			if (cmd.so || cmd.cbrate) {
				order.activePx = (cmd.so || cmd.ts)._().relative(price).resolve(market.pricePrecision)
			}
			if (cmd.cbrate) {
				order.callbackRatio = cmd.cbrate
			} else {
				order.callbackSpread = cmd.ts._().reference(price).resolve(market.pricePrecision)
			}
		} else if (cmd.so) {
			cmd.soref && cmd.soref !== "price" && this.info(`using custom Stop reference: ${ohlc[cmd.soref] || ticker[cmd.soref] || "NOT FOUND!"} (${cmd.soref})`)
			cmd.soref !== "price" && (price = (cmd.soref && (ohlc[cmd.soref] || ticker[cmd.soref])) || first)
			order.ordType = isNet ? 'conditional' : 'trigger'
			order.ordType = isNet ? 'conditional' : 'trigger'
			order[isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.so._().relative(price).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) {
				cmd.tpref && cmd.tpref !== "price" && this.info(`using custom TP reference: ${ohlc[cmd.tpref] || ticker[cmd.tpref] || "NOT FOUND!"} (${cmd.tpref})`)
				cmd.tpref !== "price" && (price = (cmd.tpref && (ohlc[cmd.tpref] || ticker[cmd.tpref])) || first)
				order[cmd.sl || isNet ? 'tpTriggerPx' : 'triggerPx'] = cmd.tp._().relative(price).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) {
				cmd.slref && cmd.slref !== "price" && this.info(`using custom SL reference: ${ohlc[cmd.slref] || ticker[cmd.slref] || "NOT FOUND!"} (${cmd.slref})`)
				cmd.slref !== "price" && (price = (cmd.slref && (ohlc[cmd.slref] || ticker[cmd.slref])) || first)
				order[cmd.tp || isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.sl._().relative(price).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)
			}
		}
		if (cmd.id) order[isAlgo ? 'algoClOrdId' : 'clOrdId'] = this.uniqueIDA(32, 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 {
				yield* this.req('POST', "/api/v5/account/set-position-mode", {posMode: cmd.pm === "hedge" ? 'long_short_mode' : 'net_mode'})
			} 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
					yield* this.req('POST', "/api/v5/account/set-leverage", {instId: market.instId, lever: leverage, mgnMode: order.tdMode, posSide: posSide})
					break
				} catch(ex) {
					const err = ex.msg || ex.error || ex.message
					if (!retry && err && err.includes('posSide')) {
						order.posSide = posSide = posSide !== "net" ? "net" : cmd.isBid ? "long" : "short"
					} 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 = yield* 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 = yield* 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 && err.includes('posSide')) {
					order.posSide = order.posSide !== "net" ? "net" : cmd.isBid ? "long" : "short"
				} else if (!retry && err && err.includes('tdMode')) {
					order.tdMode = (isSpot || isMargin) && order.tdMode !== "cash" ? "cash" : cross ? "cross" : "isolated"
				} else throw new Error(err)
			}
		}
	}

	*ordersCancel(cmd, ohlc = {}) {
		const market = yield* 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 = yield* 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 hasType = cmd.has('t') && (cmd.t === "limit" || cmd.t === "market")
		const match = cmd.cr ? false : true
		const tickers = {}
		yield* 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].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 = yield* 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") {
				cancelOrders.push(order)
				//order.algoId && this.removeId(order.algoId)
			}
		}
		return cancelOrders
	}

	*positionsClose(cmd, pos, i, ohlc = {}, market) {
		const ticker = (cmd.pc && !cmd.up && this.getPrice(market.instId)) || (yield* this.symbolTicker(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] || ohlc[cmd.pr] || ((pos.isLong && cmd.t !== "market") || (!pos.isLong && cmd.t === "market") ? ticker.bid : ticker.ask)
		let price = cmd.p._().relative(first).minmax(cmd.minp, cmd.maxp).resolve(market.pricePrecision)
		if (cmd.fp) {
			price = cmd.fp.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
		ohlc[cmd.qr] && this.info(`using custom quantity multiplier: ${ohlc[cmd.qr]} (${cmd.qr})`)

		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(ohlc[cmd.qr] || 1).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
		if (cmd.t !== "market" && !isAlgo) order.px = price

		if (cmd.ts) {
			const actref = cmd.so ? cmd.soref : cmd.tsref
			actref && actref !== "price" && this.info(`using custom TS/SO reference: ${ohlc[actref] || ticker[actref] || "NOT FOUND!"} (${actref})`)
			actref !== "price" && (price = (actref && (ohlc[actref] || ticker[actref])) || first)
			order.ordType = 'move_order_stop'
			if (cmd.so || cmd.cbrate) {
				order.activePx = (cmd.so || cmd.ts)._().relative(price).resolve(market.pricePrecision)
			}
			if (cmd.cbrate) {
				order.callbackRatio = cmd.cbrate
			} else {
				order.callbackSpread = cmd.ts._().reference(price).resolve(market.pricePrecision)
			}
		} else if (cmd.so) {
			cmd.soref && cmd.soref !== "price" && this.info(`using custom Stop reference: ${ohlc[cmd.soref] || ticker[cmd.soref] || "NOT FOUND!"} (${cmd.soref})`)
			cmd.soref !== "price" && (price = (cmd.soref && (ohlc[cmd.soref] || ticker[cmd.soref])) || first)
			order.ordType = isNet ? 'conditional' : 'trigger'
			order[isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.so._().relative(price).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) {
				cmd.tpref && cmd.tpref !== "price" && this.info(`using custom TP reference: ${ohlc[cmd.tpref] || ticker[cmd.tpref] || "NOT FOUND!"} (${cmd.tpref})`)
				cmd.tpref !== "price" && (price = (cmd.tpref && (ohlc[cmd.tpref] || ticker[cmd.tpref])) || first)
				order[cmd.sl || isNet ? 'tpTriggerPx' : 'triggerPx'] = cmd.tp._().relative(price).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) {
				cmd.slref && cmd.slref !== "price" && this.info(`using custom SL reference: ${ohlc[cmd.slref] || ticker[cmd.slref] || "NOT FOUND!"} (${cmd.slref})`)
				cmd.slref !== "price" && (price = (cmd.slref && (ohlc[cmd.slref] || ticker[cmd.slref])) || first)
				order[cmd.tp || isNet ? 'slTriggerPx' : 'triggerPx'] = cmd.sl._().relative(price).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)
			}
		}
		if (cmd.id) order[isAlgo ? 'algoClOrdId' : 'clOrdId'] = this.uniqueIDA(32, 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 = yield* 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 = yield* 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])
	}

	*positionsCloseAll(cmd, ohlc, positions) {
		if (!positions) {
			const market = cmd.s && cmd.s !== '*' && (yield* this.symbolInfo(cmd.s))
			positions = yield* 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 = yield* this.symbolInfo()
		const match = cmd.cr ? false : true
		const tickers = {}
		yield* 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 = OKEXDemo.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 => OKEXDemo.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 = yield* this.positionsClose(cmd, pos, i++, ohlc, symbols[pos.symbol])
			if (order) {
				closeOrders.push(order)
				Object.assign(closeOrders.last(), Object.without(pos, ["side"]))
			}
		}
		return closeOrders
	}
}

Broker.add(new OKEXDemo())
