"use strict"

class Bybit extends Exchange {
	static meta = {
		aliases: [
			"BYBIT",
			//"BYBITV3",
			//"BYBIT3",
		],
		endpoint: "https://api.bybit.com",
//		endpoint: "https://api.bytick.com",
		fields: {
			public: {
				label: "API Key",
			},
			private: {
				label: "API Secret",
			},
		},
		name: "Bybit v3",
		patterns: [],
		permissions: {
			origins: [
				"https://api.bybit.com/*"
//				"https://api.bytick.com/*"
			],
			permissions: [
				"webRequest",
				"webRequestBlocking",
			],
		},
		subscriptions: {
			active: [
				"e3fdd77de3a682bfcfd8dc3000a25a5b",
				"4db6261b067dabf8ffb0924c18d95d06",
			],
			inactive: [
				"bybit1",
				"bybit2",
			],
		},
		ignoreCode: [
			30083,	// position mode not modified
			30084,	// isolated not modified
			34015,	// cannot set leverage which is same to the old leverage
			34036,	// leverage not modified
			110001,	// order does not exist
			110043,	// set leverage not modified
			130127,	// not modified
			140043,	// leverage not modified
		],
		ignoreMsg: [
			"position mode not modified",	// 30083
			"isolated not modified",		// 30084
			"cannot set leverage which is same to the old leverage",	// 34015
			"leverage not modified",		// 34036
			"order does not exist",			// 110001
			"set leverage not modified",	// 110043
			"not modified",					// 130127
			"unknown result",
		],
		recvWindow: 30 * 1000,
		website: "https://www.bybit.com/register?affiliate_id=777",
		desc: "Bybit is the safest, fastest, most transparent, and user friendly Bitcoin and Ethereum trading platform offering cryptocurrency perpetual contracts.",
		margin: true, spot: true, posMode: true, marginType: true, stopsOnFill: true,
		testSymbol: "BTCUSDTS",
		defaultSymbol: "BTC",
		isInverse: sym => sym && sym.endsWith("USD"),
		isLinear: sym => sym && sym.endsWith("USDT"),
		isFuture: sym => sym && !sym.endsWith("USD") && !sym.endsWith("USDT"),
		isSpot: sym => sym && sym.endsWith('S')
	}
	constructor() {
		super(Bybit.meta)
	}

	*req(method, resource, params, sign = true) {
		if (sign) {
			const credentials = this.getCredentials("private")
			params = params || {}
			params.api_key = credentials.public
			params.timestamp = this.getTimestamp()
			params.recv_window = this.meta.recvWindow
			params = serialize(params, null, true, false)

			let sha = new jsSHA("SHA-256", "TEXT")
			sha.setHMACKey(credentials.private, "TEXT")
			sha.update(params)
			params = params.replace(/\+/g, '%2B') + "&sign=" + sha.getHMAC("HEX")
			if (method === 'DELETE') {
				resource += '?' + params
				params = null
			}
		} else this.hasAccess()

		this.removeOrigin(true)
		const response = yield request.bind(this, method, this.meta.endpoint + resource, params, null)

		if (!response) {
			throw new Error("Unknown connection error with API endpoint!")
		}
		if (response.retCode !== 0) {
			if (!this.meta.ignoreCode.includes(Number(response.retCode)) && !this.meta.ignoreMsg.includesNoCase(response.retMsg)) {
				throw new Error(`${response.retMsg || "Unknown connection error with API endpoint!"} (Code ${response.retCode})`)
			}
		}
		return response.result
	}

	*time() {
		const data = yield* this.req('GET', "/v3/public/time", undefined, false)
		return Math.round(data.timeNano / 1000000)
	}

	*account(currency, isMargin) {
		let balances = {}, hasSpotPermission = true
		const isTest = this.isTest()

		if (!isMargin || isTest) {
			try {
				const spot = (yield* this.req('GET', "/spot/v3/private/account")).balances
				spot && spot.forEach && spot.forEach(item => {
					const coin = item.coin.toUpperCase()
					if (Number(item.total) || coin == currency) balances[coin] = {
						available: Number(item.free),
						balance: Number(item.total)
					}
				})
			} catch (ex) {
				// Invalid API-key, IP, or permissions for action. (Code -2015)
				if (!isTest) throw(ex)
				hasSpotPermission = false
				this.warn('seems account has no spot trading permissions!')
			}
		}
		if (isMargin || isTest) {
			try {
				const wallets = (yield* this.req('GET', "/contract/v3/private/account/wallet/balance", !isTest && currency ? {coin: currency} : {})).list
				for (const wallet of wallets) {
					const equity = Number(wallet.equity) || (Number(wallet.walletBalance) + Number(wallet.unrealisedPnl)) || 0
					if (equity) {
						balances[isMargin ? wallet.coin : wallet.coin+marginSuffix] = {
							available: Number(wallet.availableBalance) || (Number(wallet.walletBalance) - Number(wallet.orderMargin)) || 0,
							balance: equity,
							wallet: Number(wallet.walletBalance)
						}
					}
				}
			} catch (ex) {
				// Permission denied, please check your API key permissions. (Code 10005)
				if (!isTest || !hasSpotPermission) throw(ex)
				this.warn('seems account has no contract trading permissions!')
			}
		}
		isTest && currency && (balances[currency] = balances[currency] || {available: 0, balance: 0})
		return balances
	}

	*symbolInfo(symbol) {
		const symbolCache = this.getSymbolCache()
		if (symbolCache._refresh) {
			const response = yield* this.req('GET', "/derivatives/v3/public/instruments-info", undefined, false)
			response.list && response.list.forEach(info => {
				const tickSize = info.priceFilter && info.priceFilter.tickSize
				const qtyStep = info.lotSizeFilter && info.lotSizeFilter.qtyStep
				info.pricePrecision = decimals(tickSize) + (!tickSize.toString().endsWith('1') ? tickSize % 1 : 0)
				info.quantityPrecision = decimals(qtyStep) + (!qtyStep.toString().endsWith('1') ? qtyStep % 1 : 0)
				info.minimumTradeSize = info.lotSizeFilter && Number(info.lotSizeFilter.minTradingQty)
				info.maximumTradeSize = info.lotSizeFilter && Number(info.lotSizeFilter.maxTradingQty)
				info.maximumPostSize = info.lotSizeFilter && Number(info.lotSizeFilter.postOnlyMaxTradingQty)
				info.maxLeverage = info.leverageFilter && Number(info.leverageFilter.maxLeverage)
				if (info.contractType === "LinearPerpetual") {
					info.type = "linear"
					info.isLinear = true
					info.contractType = info.settleCoin+" Perpetual"
				} else if (info.contractType === "InverseFutures") {
					info.type = "inverse"
					info.isInverse = info.isFuture = true
					info.contractType = "Inverse Futures"
				} else if (info.contractType === "InversePerpetual") {
					info.type = "inverse"
					info.isInverse = true
					info.contractType = "Inverse Perpetual"
				} else {
					info.type = "option"
					info.isOption = true
					info.contractType = info.settleCoin+" Options"
				}
				symbolCache[this.sym(info.symbol)] = info
			})
			symbolCache.BTCUSDT = symbolCache.BTCUSDT || {
				name: "BTCUSDT",
				quoteCoin: "USDT",
				type: "USDT Perpetual",
				isLinear: true,
				pricePrecision: 1.1,
				quantityPrecision: 3,
				minimumTradeSize: 0.001,
			}

			const spot = yield* this.req('GET', "/spot/v3/public/symbols", undefined, false)
			spot.list && spot.list.forEach(info => {
				info.pricePrecision = decimals(info.minPricePrecision)
				info.basePrecision = decimals(info.basePrecision)
				info.quotePrecision = decimals(info.quotePrecision)
				info.baseMinSize = Number(info.minTradeQty)
				info.baseMaxSize = Number(info.maxTradeQty)
				info.quoteMinSize = Number(info.minTradeAmt)
				info.quoteMaxSize = Number(info.maxTradeAmt)
				info.contractType = "Spot"
				info.isSpot = true
				info.symbol = info.name
				symbolCache[this.sym(info.name)+'S'] = info
			})

			this.updatedSymbolCache()
		}
		if (!symbol) return symbolCache

		const lookup = this.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) {
		const ticker = this.meta.isSpot(symbol) ? (yield* this.req('GET', "/spot/v3/public/quote/ticker/bookTicker", {symbol: symbol.slice(0, -1)})) : 
			(yield* this.req('GET', "/derivatives/v3/public/tickers", {symbol: symbol}, false)).list[0]

		return ticker && {
			ask: Number(ticker.askPrice || ticker.ask_price),
			bid: Number(ticker.bidPrice || ticker.bid_price),
			mid: (Number(ticker.askPrice || ticker.ask_price) + Number(ticker.bidPrice || ticker.bid_price)) / 2,
			last: Number(ticker.lastPrice || ticker.last_price),
			mark: Number(ticker.markPrice || ticker.mark_price),
			index: Number(ticker.indexPrice || ticker.index_price)
		}
	}

	*getPosMode(market) {
		const posMode = {Buy: 10, Sell: 10, isHedge: false, isCross: false}
		try {
			let result = yield* this.req('GET', "/contract/v3/private/position/list", {symbol: market.symbol})
			result.list.forEach(pos => {
				if (pos.positionIdx === 0 && pos.side === "None") {
					posMode.Buy = posMode.Sell = Number(pos.leverage) || 1
				} else {
					posMode[pos.positionIdx === 2 ? "Sell" : pos.positionIdx === 1 ? "Buy" : pos.side] = pos.leverage
				}
				if (pos.positionIdx !== 0) posMode.isHedge = true
				if (pos.tradeMode === 0) posMode.isCross = true
			})
		} catch (ex) {}
		return posMode
	}

	*trade(cmd, ohlc = {}) {
		const market = yield* this.symbolInfo(cmd.s)
		cmd._sym = market.isSpot ? cmd.s : market.symbol
		const ticker = (cmd.pc && !cmd.up && this.getPrice(cmd._sym)) || (yield* this.symbolTicker(cmd._sym))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${market.symbol} is not available!`)
		}
		this.checkSlip(cmd, ohlc, cmd._sym, ticker)
		if (cmd.up) {
			this.updatePrice(cmd._sym, ticker)
			if (!cmd.b && !cmd.ub) {
				return ticker
			}
		}
		const currency = cmd.currency = market.isSpot ? (cmd.isBid ? market.quoteCoin : market.baseCoin) : (market.isLinear ? market.quoteCoin : market.baseCoin)
		const quantityPrecision = market.isSpot ? (cmd.isBid && cmd.t === "market" ? market.quotePrecision : market.basePrecision) : market.quantityPrecision

		if (!market.isSpot) cmd.currency += marginSuffix
		let balance = {available: 0, balance: 0}
		if ((cmd.y !== "possize" && cmd.q.wasPercent()) || cmd.ub) {
			balance = cmd.bc && !cmd.ub && this.getBalance(currency, !market.isSpot)
			const balances = !balance && (yield* this.account(currency, !market.isSpot))
			balance = balance || balances[currency]
			this.checkBalance(cmd, balance, currency, `${market.isSpot ? "Spot" : "Derivatives"} Wallet`)
			if (!cmd.bc || cmd.ub) {
				this.updateBalance(balances, currency, !market.isSpot)
				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 (market.priceFilter && market.priceFilter.minPrice && price < market.priceFilter.minPrice) {
			this.warn(`price below instrument minimum of ${market.priceFilter.minPrice} – use minp= to set minimum!`)
		} else 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 available = cmd.y === "equity" ? balance.balance : balance.available
		let cf = 1.0
		const posMode = market.isSpot ? {} : (yield* this.getPosMode(market))
		const doMode = (cmd.pm === "normal" && posMode.isHedge) || (cmd.pm === "hedge" && !posMode.isHedge)
		const cross = cmd.mt ? cmd.mt === "crossed" : posMode.isCross || cmd.l === 0
		const doType = cross != posMode.isCross
		let leverage = cmd._lev = market.isSpot ? 1 : (cmd.l === 0 ? 10 : cmd.l) * (ohlc[cmd.lr] || 1) || posMode[cmd.isBid ? "Buy" : "Sell"]
		if (leverage > market.maxLeverage) {
			cmd.l && (cf = market.maxLeverage / leverage)
			leverage = cmd._lev = market.maxLeverage
			this.warn(`leverage limited to ${leverage}x by instrument!`)
		}
		const contracts = ohlc[cmd.y] || (
				market.isSpot ? available / (cmd.isBid && cmd.t !== "market" ? price : 1) : 
				market.isLinear ? available / price : available * price) * (cmd.lc || leverage) * cf

		ohlc[cmd.qr] && this.info(`using custom quantity multiplier: ${ohlc[cmd.qr]} (${cmd.qr})`)
		let order = {
			side: cmd.isBid ? "Buy" : "Sell",
			symbol: market.symbol,
			orderType: cmd.t === "market" ? "Market" : "Limit",
			qty: cmd.q._().mul(cmd.u === "currency" && !cmd.q.wasPercent() ? (market.isLinear ? 1 / price : price) * (cmd.lc || leverage) * cf : 1)
				.reference(contracts).mul(ohlc[cmd.qr] || 1).minmax(cmd.minq, cmd.maxq).resolve(quantityPrecision),
			timeInForce: ({fok: "FillOrKill", ioc: "ImmediateOrCancel", post: "PostOnly"})[cmd.t] || "GoodTillCancel",
			closeOnTrigger: false,
			reduceOnly: false
		}
		if (cmd.lq && ohlc.left) {
			this.info("using leftover quantity from last order: "+ohlc.left)
			order.qty = new NumberObject(ohlc.left).resolve(quantityPrecision)
		}
		const maxQty = cmd.t === "post" && market.maximumPostSize || market.maximumTradeSize
		if (market.minimumTradeSize && order.qty < market.minimumTradeSize) {
			this.warn(`order quantity below instrument minimum of ${market.minimumTradeSize} – use minq= to set minimum!`)
		} else if (maxQty && order.qty > maxQty) {
			this.warn(`order quantity above instrument maximum of ${maxQty} – use maxq= to set maximum!`)
		} else if (order.qty <= 0) {
			this.warn("your q parameter might be incorrect! (quantity of 0 calculated, use minq= to set minimum)")
		}
		if (market.isLinear || market.isFuture) {
			order.positionIdx = (!doMode && !posMode.isHedge) || (doMode && posMode.isHedge) ? 0 : cmd.isBid ? (!cmd.r ? 1 : 2) : (!cmd.r ? 2 : 1)
		}
		if (cmd.t !== "market") order.price = price

		if (market.isSpot) {
			order.side = order.side.toUpperCase()
			order.orderType = order.orderType.toUpperCase()
			order.timeInForce = ({fok: "FOK", ioc: "IOC"})[cmd.t] || "GTC"
			delete order.closeOnTrigger
			delete order.reduceOnly
		} 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.triggerPrice = cmd.so._().relative(price).resolve(market.pricePrecision)
				order.triggerDirection = ticker.last < order.triggerPrice ? 1 : 2
				order.triggerBy = ucfirst(cmd.sr) + "Price"
			}
			if (cmd.tp) {
				cmd.tpref && cmd.tpref !== "price" && this.info(`using custom TP reference: ${ohlc[cmd.tpref] || ticker[cmd.tpref] || "NOT FOUND!"} (${cmd.tpref})`)
				const stop = cmd.tpref === "price" ? price : ((cmd.tpref && (ohlc[cmd.tpref] || ticker[cmd.tpref])) || first)
				order.takeProfit = cmd.tp._().relative(stop).resolve(market.pricePrecision)
				order.tpTriggerBy = ucfirst(cmd.sr) + "Price"
			}
			if (cmd.sl) {
				cmd.slref && cmd.slref !== "price" && this.info(`using custom SL reference: ${ohlc[cmd.slref] || ticker[cmd.slref] || "NOT FOUND!"} (${cmd.slref})`)
				const stop = cmd.slref === "price" ? price : ((cmd.slref && (ohlc[cmd.slref] || ticker[cmd.slref])) || first)
				order.stopLoss = cmd.sl._().relative(stop).resolve(market.pricePrecision)
				order.slTriggerBy = ucfirst(cmd.sr) + "Price"
			}
			if (cmd.r) order.closeOnTrigger = order.reduceOnly = true
		}
		if (cmd.id) order.orderLinkId = this.uniqueID(cmd.id)

		this.info(`placing ${currency} order${market.isSpot ? '' : ` @ ${leverage}x ${cross ? 'cross' : 'isolated'} (${order.positionIdx ? 'hedged' : 'one-way'})`}: `+stringify(order))
		if (cmd.d) {
			this.msgDisabled(cmd)
			return order
		}

		if ((market.isLinear || market.isFuture) && doMode) {
			this.info(`setting position mode to ${cmd.pm}...`)
			try {
				yield* this.req('POST', "/contract/v3/private/position/switch-mode", {symbol: market.symbol, mode:cmd.pm !== "hedge" ? 0 : 3})
			} catch(ex) {
				this.warn(`couldn't change position mode, please change manually! (Error: ${ex.message})`)
			}
		} else if (doMode) {
			this.warn(`this market does not support different position modes!`)
		}

		const posSide = order.positionIdx === 2 ? "Sell" : order.positionIdx === 1 ? "Buy" : order.side
		const doLev = cmd.l !== undefined && leverage != posMode[posSide]
		if (!market.isSpot && (doLev || doType)) {
			this.info(`setting contract leverage to ${leverage}x${cross?' cross':' isolated'}...`)
			posMode[posSide] = leverage
			if ((!doMode && !posMode.isHedge) || (doMode && cmd.pm !== "hedge")) {
				posMode.Buy = posMode.Sell = leverage
			}
			try {
				yield* this.req('POST', doType ? "/contract/v3/private/position/switch-isolated" : "/contract/v3/private/position/set-leverage", 
					{symbol: market.symbol, tradeMode: cross ? 0 : 1, buyLeverage: posMode.Buy.toString(), sellLeverage: posMode.Sell.toString()})
			} catch(ex) {
				this.warn(`couldn't set leverage, please set manually! (Error: ${ex.message})`)
			}
		}

		if (maxQty && order.qty > maxQty) {
			let leftQty = order.qty, result = null, i = 1
			do {
				if (i > 1 && opt.splitDelay) {
					this.info(`waiting for ${opt.splitDelay}sec (split order delay)...`)
					while((yield* sleep(opt.splitDelay)) == null);
				}
				order.qty = Math.min(leftQty, maxQty)
				if (cmd.id) order.orderLinkId = this.uniqueID(cmd.id, i)
				this.info(`posting split order #${i++} of size ${order.qty}...`)
				result = yield* this.req('POST', market.isSpot ? "/spot/v3/private/order" : "/contract/v3/private/order/create", order)
				leftQty -= order.qty
			} while(leftQty > 0)
			return Object.assign(order, result)
		}
		return Object.assign(order, yield* this.req('POST', market.isSpot ? "/spot/v3/private/order" : "/contract/v3/private/order/create", order))
	}

	*ordersCancel(cmd, ohlc) {
		const market = yield* this.symbolInfo(cmd.s)
		cmd._sym = market.isSpot ? cmd.s : market.symbol

		if (!cmd.ch && !cmd.d && !cmd.b && !cmd.fp && !cmd.id && cmd.cm.wasPercent(true) && cmd.r === undefined && !cmd.has('t') && !cmd.tp && !cmd.sl && !cmd.ts && !cmd.so && !cmd.gt && !cmd.gte && !cmd.lt && !cmd.lte) {
			if (market.isSpot) {
				this.info(`canceling all ${cmd.s} orders in bulk!`)
				yield* this.req('POST', "/spot/v3/private/cancel-orders", {symbol: market.symbol})
				return true
			} else {
				this.info(`canceling all ${market.symbol} orders in bulk!`)
				const result = yield* this.req('POST', "/contract/v3/private/order/cancel-all", {symbol: market.symbol})
				if (!result) {
					this.msgOrderErr()
					return false
				} else if (!result.list || !result.list.length) {
					this.msgOrdersNone()
					return false
				}
				return result.list
			}
		}

		let orders = (yield* this.req('GET', market.isSpot ? "/spot/v3/private/open-orders" : "/contract/v3/private/order/unfilled-orders", {symbol: market.symbol, limit: market.isSpot ? 500 : 50})).list || []
		if (!orders.length) {
			this.msgOrdersNone()
			return false
		}

		const ids = (cmd.id || "").split(',')
		const hasType = cmd.has('t') && (cmd.t === "limit" || cmd.t === "market") && ucfirst(cmd.t)
		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.price)) {
				return !match
			}
			if (hasType && order.orderType !== hasType) {
				return !match
			}
			if ((cmd.t === "open" && order.triggerPrice) || ((cmd.t === "close" || cmd.tp || cmd.sl || cmd.ts || cmd.so) && !order.triggerPrice)) {
				return !match
			}
			if ((cmd.so && order.stopOrderType !== "Stop") || (cmd.ts && order.stopOrderType !== "TrailingStop")) {
				return !match
			}
			if (cmd.r !== undefined && !cmd.r != !order.reduceOnly) {
				return !match
			}
			if (cmd.id && (!order.orderLinkId || !order.orderLinkId.startsWithAny(ids, 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": 	["createdTime", true],
				"oldest": 	["createdTime", false],
				"highest": 	["price", true],
				"lowest": 	["price", false],
				"biggest": 	["qty", true],
				"smallest": ["qty", 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 = []
		for (const order of orders) {
			const result = market.isSpot ? (yield* this.req('POST', "/spot/v3/private/cancel-order", {orderId: order.orderId})) : 
				(yield* this.req('POST', "/contract/v3/private/order/cancel", {symbol: market.symbol, orderId: order.orderId}))
			result && result.orderId && cancelOrders.push(order)
		}
		return cancelOrders
	}

	*positionsClose(cmd, pos, i, ohlc = {}, ticker, market) {
		ticker = (cmd.pc && !cmd.up && this.getPrice(market.symbol)) || ticker || (yield* this.symbolTicker(market.symbol))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${market.symbol} is not available!`)
		}
		ticker.pos = pos.entryPrice
		ticker.liq = pos.liqPrice
		this.checkSlip(cmd, ohlc, market.symbol, ticker)
		cmd.up && this.updatePrice(market.symbol, ticker)

		const first = ticker[cmd.sl || cmd.tp || cmd.ts || cmd.so ? 'pos' : cmd.pr] || ohlc[cmd.pr] || ((cmd.isBid && cmd.t !== "market") || (cmd.isAsk && 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 (market.priceFilter && market.priceFilter.minPrice && price < market.priceFilter.minPrice) {
			this.warn(`price below instrument minimum of ${market.priceFilter.minPrice} – use minp= to set minimum!`)
		} else if (price <= 0) {
			this.warn("your p parameter might be incorrect! (price of 0 calculated, use minp= to set minimum)")
		}

		const contracts = pos.quantity
		ohlc[cmd.qr] && this.info(`using custom quantity multiplier: ${ohlc[cmd.qr]} (${cmd.qr})`)
		let order = {
			side: pos.side === 'Buy' ? 'Sell' : 'Buy',
			symbol: market.symbol,
			orderType: cmd.t === "market" ? "Market" : "Limit",
			qty: cmd.q._().mul(cmd.u === "currency" && !cmd.q.wasPercent() ? (market.isLinear ? 1 / price : price) * (cmd.lc || pos.leverage || 1) : 1)
				.reference(contracts).mul(ohlc[cmd.qr] || 1).minmax(cmd.minq, cmd.maxq).resolve(market.quantityPrecision),
			timeInForce: ({fok: "FillOrKill", ioc: "ImmediateOrCancel", post: "PostOnly"})[cmd.t] || "GoodTillCancel",
			closeOnTrigger: cmd.r === undefined ? true : cmd.r,
			reduceOnly: cmd.r === undefined ? true : cmd.r
		}
		if (cmd.t !== "market") order.price = price

		if (((cmd.tp || cmd.sl) && (!cmd.q.wasPercent(true) || cmd.t === "limit" || cmd.id)) || (cmd.so && !cmd.ts)) {
			const ref = (cmd.tp && cmd.tpref) || (cmd.sl && cmd.slref) || (cmd.so && cmd.soref)
			ref && ref !== "price" && this.info(`using custom stop reference: ${ohlc[ref] || ticker[ref] || "NOT FOUND!"} (${ref})`)
			const stop = ref === "price" ? price : ((ref && (ohlc[ref] || ticker[ref])) || first)
			order.triggerPrice = (cmd.tp || cmd.sl || cmd.so)._().relative(stop).resolve(market.pricePrecision)
			order.triggerDirection = ticker.last < order.triggerPrice ? 1 : 2
			order.triggerBy = ucfirst(cmd.sr) + "Price"
		} else if ((cmd.tp || cmd.sl || cmd.ts) && !cmd.ch) {
			let req = {
				symbol: market.symbol,
				tpslMode: cmd.q.wasPercent(true) ? "Full" : "Partial",
				positionIdx: pos.positionIdx,
			}
			if (!cmd.q.wasPercent(true)) {
				req.tpSize = req.slSize = order.qty
			}
			if (cmd.tp) {
				cmd.tpref && cmd.tpref !== "price" && this.info(`using custom TP reference: ${ohlc[cmd.tpref] || ticker[cmd.tpref] || "NOT FOUND!"} (${cmd.tpref})`)
				const stop = cmd.tpref === "price" ? price : ((cmd.tpref && (ohlc[cmd.tpref] || ticker[cmd.tpref])) || first)
				req.takeProfit = cmd.tp._().relative(stop).resolve(market.pricePrecision)
				req.tpTriggerBy = ucfirst(cmd.sr) + "Price"
			}
			if (cmd.sl) {
				cmd.slref && cmd.slref !== "price" && this.info(`using custom SL reference: ${ohlc[cmd.slref] || ticker[cmd.slref] || "NOT FOUND!"} (${cmd.slref})`)
				const stop = cmd.slref === "price" ? price : ((cmd.slref && (ohlc[cmd.slref] || ticker[cmd.slref])) || first)
				req.stopLoss = cmd.sl._().relative(stop).resolve(market.pricePrecision)
				req.slTriggerBy = ucfirst(cmd.sr) + "Price"
			}
			if (cmd.ts) {
				cmd.soref && cmd.soref !== "price" && this.info(`using custom trigger reference: ${ohlc[cmd.soref] || ticker[cmd.soref] || "NOT FOUND!"} (${cmd.soref})`)
				const act = cmd.soref === "price" ? price : ((cmd.soref && (ohlc[cmd.soref] || ticker[cmd.soref])) || first)
				if (cmd.so) {
					req.activePrice = cmd.so._().relative(act).resolve(market.pricePrecision)
				}
				cmd.tsref && cmd.tsref !== "price" && this.info(`using custom TS reference: ${ohlc[cmd.tsref] || ticker[cmd.tsref] || "NOT FOUND!"} (${cmd.tsref})`)
				const stop = cmd.tsref === "price" ? price : ((cmd.tsref && (ohlc[cmd.tsref] || ticker[cmd.tsref])) || act || first)
				req.trailingStop = cmd.ts._().reference(stop).abs().resolve(market.pricePrecision)
			}
			cmd.id && this.warn("does not support custom IDs for Trading Stop position attributes – use so= instead to place a normal stop order!")
			this.info("setting Trading Stop: "+stringify(req))
			if (cmd.d) {
				this.msgDisabled(cmd)
				return req
			}
			return Object.assign(req, yield* this.req('POST', "/contract/v3/private/position/trading-stop", req))
		}
		if (market.isLinear || market.isFuture) order.positionIdx = pos.positionIdx
		if (cmd.id) order.orderLinkId = this.uniqueID(cmd.id, i)

		!cmd.ch && this.info(`placing Close Position order @ ${pos.leverage}x: `+stringify(order))
		if (cmd.ch || cmd.d) {
			this.msgDisabled(cmd)
			return order
		}

		const maxQty = (cmd.t === "post" && market.maximumPostSize) || market.maximumTradeSize
		if (maxQty && order.qty > maxQty) {
			let leftQty = order.qty, result = null, i = 1
			do {
				if (i > 1 && opt.splitDelay) {
					this.info(`waiting for ${opt.splitDelay}sec (split order delay)...`)
					while((yield* sleep(opt.splitDelay)) == null);
				}
				order.qty = Math.min(leftQty, maxQty)
				if (cmd.id) order.orderLinkId = this.uniqueID(cmd.id, i)
				this.info(`posting split order #${i++} of size ${order.qty}...`)
				result = yield* this.req('POST', "/contract/v3/private/order/create", order)
				leftQty -= order.qty
			} while(leftQty > 0)
			return Object.assign(order, result)
		}
		return Object.assign(order, yield* this.req('POST', "/contract/v3/private/order/create", order))
	}

	*positionsCloseAll(cmd, ohlc, positions) {
		const isAll = !cmd.s || cmd.s === '*'
		const symbols = isAll ? yield* this.symbolInfo() : {[cmd.s]: yield* this.symbolInfo(cmd.s)}
		if (!positions) {
			if (isAll) {
				positions = []
				const wallets = (yield* this.req('GET', "/contract/v3/private/account/wallet/balance")).list
				const linearCoins = wallets.map(wallet => Number(wallet.positionMargin) && wallet.coin)
				//const linearCoins = [... new Set(Object.values(symbols).map(info => info.settleCoin))]
				for (const coin of linearCoins) {
					if (!coin) continue
					const result = (yield* this.req('GET', "/contract/v3/private/position/list", {settleCoin: coin})).list
					if (!result || !result.filter) continue
					positions = positions.concat(result)
				}
			} else {
				const market = symbols[cmd.s]
				positions = (yield* this.req('GET', "/contract/v3/private/position/list", {symbol: market.symbol})).list
				if (!positions || !positions.filter) {
					this.msgPosErr()
					return false
				}
			}
			positions = positions.filter(pos => pos.side && pos.side !== 'None' && Number(pos.size))
			if (!positions.length) {
				this.msgPosNone()
				return false
			}
		}

		const tickers = {}
		for (const pos of positions) {
			if (!tickers[pos.symbol]) {
				tickers[pos.symbol] = ((cmd.up || ((cmd.minpl || cmd.maxpl || !cmd.s) && (pos.unrealisedPnl === undefined/* || this.meta.isFuture(pos.symbol)*/))) 
					&& (yield* this.symbolTicker(pos.symbol))) || (cmd.pc && this.getPrice(pos.symbol))
			}
		}

		const match = cmd.cr ? false : true
		yield* this.filterPosStart(cmd, ohlc, tickers, positions)
		const totalPos = positions.length
		positions = positions.filter(pos => {
			if ((cmd.isBid && pos.side !== 'Buy') || (cmd.isAsk && pos.side !== 'Sell')) {
				return false
			}
			if (cmd.fp && cmd.fp.compare(pos.entryPrice)) {
				return !match
			}
			if (cmd.l && pos.leverage !== cmd.l) {
				return !match
			}
			const market = symbols[pos.symbol] || {}
			cmd._sym = market.symbol
			pos.currency = cmd.currency = (market.isLinear ? market.quoteCoin : market.baseCoin) + marginSuffix
			pos.quantity = Math.abs(pos.size)
			const ticker = tickers[pos.symbol]
			if ((pos.unrealisedPnl === undefined/* || market.isFuture*/) && ticker) {
				pos.unrealisedPnl = (pos.side === 'Buy' ? ticker.mark - pos.entryPrice : pos.entryPrice - ticker.mark) * 
					(market.isFuture ? pos.positionValue / pos.entryPrice : pos.quantity)
			}
			if (ticker) pos.markPrice = pos.markPrice || ticker.mark
			pos.isolated = pos.tradeMode !== 0
			pos.unrealisedPnl = toPrecision(pos.unrealisedPnl || 0, 8)
			pos.margin = Number(pos.positionBalance) || Number(pos.positionIM)
			pos.pnl = pos.unrealisedPnl / pos.margin / pos.leverage
			pos._sizeCoin = market.isLinear ? pos.quantity / pos.leverage : pos.margin
			pos._sizeUSD = market.isLinear ? pos.margin : pos.quantity / pos.leverage
			pos.avgEntryPrice = toPrecision(pos.entryPrice || 0, market.pricePrecision)
			if (!this.filterPos(cmd, ohlc, tickers, pos, balance => (market.isLinear ? balance / pos.entryPrice : balance * pos.entryPrice) * pos.leverage, market.quantityPrecision)) {
				return !match
			}
			return match
		})

		if (cmd.cm._().reference(positions.length).getMax() < positions.length) {
			const sort = {
				"newest": 	["createdTime", true],
				"oldest": 	["createdTime", false],
				"highest": 	["entryPrice", true],
				"lowest": 	["entryPrice", false],
				"biggest": 	["quantity", true],
				"smallest": ["quantity", 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, tickers[pos.symbol], symbols[pos.symbol])
			if (order) {
				closeOrders.push(order)
				Object.assign(closeOrders.last(), Object.without(pos, ["quantity", "size", "side", "takeProfit", "stopLoss", "trailingStop"]))
			}
		}
		return closeOrders
	}
}

Broker.add(new Bybit())
