"use strict"

class BinanceFuturesTestnet extends Exchange {
	static meta = {
		aliases: [
			"BINANCEFUTURESTESTNET",
			"BINANCEFUTURES-TESTNET",
			"BINANCEFTTESTNET",
			"BINANCEFT-TESTNET",
		],
		endpoint: "https://testnet.binancefuture.com/fapi/",
		endpoint2: "https://testnet.binancefuture.com/dapi/",
		fields: {
			public: {
				label: "API Key",
			},
			private: {
				label: "API Secret",
			},
		},
		name: "Binance Futures Testnet",
		patterns: [],
		permissions: {
			origins: [
				"https://testnet.binancefuture.com/*"
			],
			permissions: [],
		},
		subscriptions: {
			active: [],
			inactive: [],
		},
		recvWindow: 30 * 1000,
		website: "https://testnet.binancefuture.com/?ref=AOBJU8Z4",
		desc: "Binance Futures is now letting traders choose up to 125x leverage on their futures trades.",
		defaultAsset: "USDT",
		multiAsset: "USD"+marginSuffix,
		testSymbol: "BTCUSDT",
		margin: true, posMode: true, marginType: true,
	}
	constructor() {
		super(BinanceFuturesTestnet.meta)
	}

	*req(method, resource, delivery = false, params, sign = true) {
		const headers = !params ? {"Content-Type": "application/json"} : {}
		if (sign) {
			const credentials = this.getCredentials("private")
			headers["X-MBX-APIKEY"] = credentials.public
			params = params || {}
			params.timestamp = this.getTimestamp()
			params.recvWindow = this.meta.recvWindow

			let sha = new jsSHA("SHA-256", "TEXT")
			sha.setHMACKey(credentials.private, "TEXT")
			sha.update(serialize(params))
			params.signature = sha.getHMAC("HEX")
		} else this.hasAccess()

		const result = yield request.bind(this, method, (delivery ? this.meta.endpoint2 : this.meta.endpoint) + resource, params, headers)
		if (result && result[0] && result[0].code && result[0].code < 0) {
			throw new Error(result[0].msg || "Unknown connection error with API endpoint!")
		}
		return result
	}

	*time() {
		const result = yield request.bind(this, 'GET', this.meta.endpoint + "v1/time", null, null)
		return result.serverTime
	}

	*account(currency, isMargin) {
		let balances = {}
		const isTest = this.isTest()
		const isFutures = ['USDT', 'BUSD', this.meta.multiAsset].includes(currency) || isMargin

		const futures = isTest || !currency || isFutures ? (yield* this.req('GET', "v2/account", false)) : {assets:[]}
		if (!futures || !futures.assets || !futures.assets.forEach) {
			this.info("unable to get account info! (Futures / USDⓈ-M)")
			return balances
		}
		const delivery = isTest || !currency || !isFutures ? (yield* this.req('GET', "v1/balance", true)) : []
		if (!delivery || !delivery.forEach) {
			this.info("unable to get account info! (Delivery / COIN-M)")
			return balances
		}

		;[...futures.assets, ...delivery].forEach(item => Number(item.balance || item.marginBalance) && (balances[item.asset] = {
			available: Number(item.availableBalance) || Number(item.withdrawAvailable || item.maxWithdrawAmount) || 0,
			balance: Number(item.balance || item.marginBalance) || 0
		}))
		isTest && (balances[this.meta.defaultAsset] = balances[this.meta.defaultAsset] || {available: 0, balance: 0})
		if (futures.totalMarginBalance) {
			balances[this.meta.multiAsset] = {
				available: Number(futures.availableBalance) || Number(futures.maxWithdrawAmount) || 0,
				balance: Number(futures.totalMarginBalance) || 0,
				isMulti: futures.multiAssetsMargin || (Number(futures.totalMarginBalance) || 0) > (balances.USDT && balances.USDT.balance || 0)
			}
		}
		return isMargin ? balances[this.meta.multiAsset] : balances
	}

	*symbolInfo(symbol) {
		const symbolCache = this.getSymbolCache()
		if (symbolCache._refresh) {
			const futures = (yield* this.req('GET', "v1/exchangeInfo", false, undefined, false)).symbols
			futures.forEach(info => info.type = "USDⓈ-M")
			const delivery = (yield* this.req('GET', "v1/exchangeInfo", true, undefined, false)).symbols
			delivery.forEach(info => {
				info.isDelivery = true; info.type = "COIN-M"
				if (info.contractType === "CURRENT_QUARTER" || info.contractType === "NEXT_QUARTER") {
					const dd = new Date(info.deliveryDate)
					symbolCache[info.pair + 'FGHJKMNQUVXZ'[dd.getMonth()] + (dd.getYear()+1900)] = info
				}
			})
			;[...futures, ...delivery].forEach(info => {
				let filters = {}
				info.filters.forEach(filter => filters[filter.filterType] = filter)
				info.pricePrecision = filters.PRICE_FILTER ? decimals(filters.PRICE_FILTER.tickSize) : info.pricePrecision	// minPrice maxPrice
				info.quantityPrecision = filters.LOT_SIZE ? decimals(filters.LOT_SIZE.stepSize) : info.quantityPrecision
				info.minQty = filters.LOT_SIZE && Number(filters.LOT_SIZE.minQty) || 0
				info.maxQty = filters.LOT_SIZE && Number(filters.LOT_SIZE.maxQty) || 0
				info.minNotional = filters.MIN_NOTIONAL ? Number(filters.MIN_NOTIONAL.notional || filters.MIN_NOTIONAL.minNotional) : 0
				info.filters = filters
				symbolCache[info.symbol] = info
			})
			this.updatedSymbolCache()
		}
		if (!symbol) return symbolCache

		if (!symbolCache[symbol]) {
			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[symbol]
	}

	*symbolTickerImpl(symbol, getMark) {
		const market = yield* this.symbolInfo(symbol)
		let ticker = yield* this.req('GET', "v1/ticker/bookTicker", market.isDelivery, {symbol: market.symbol}, false)
		ticker = ticker && (ticker[0] || ticker)
		let premium = null
		getMark && (premium = yield* this.req('GET', "v1/premiumIndex", market.isDelivery, {symbol: market.symbol}, false))
		premium = premium && (premium[0] || premium)

		return ticker && Object.assign({
			ask: Number(ticker.askPrice),
			bid: Number(ticker.bidPrice),
			mid: (Number(ticker.askPrice) + Number(ticker.bidPrice)) / 2
		}, premium && {
			mark: Number(premium.markPrice),
			index: Number(premium.indexPrice)
		})
	}

	*getLeverage(market, side) {
		let lev = null
		try {
			const result = (yield* this.req('GET', `${market.isDelivery?'v1':'v2'}/positionRisk`, market.isDelivery, 
				market.isDelivery ? {pair: market.pair} : {symbol: market.symbol})).filter(pos => pos.symbol == market.symbol)
			let sideLev = {}
			result.forEach(pos => sideLev[({LONG: "BUY", SHORT: "SELL"})[pos.positionSide] || (Number(pos.positionAmt) < 0 ? "SELL" : "BUY")] = pos.leverage)
			lev = Number(sideLev[side] || result.last().leverage)
		} catch (ex) {}
		return lev
	}

	*trade(cmd, ohlc = {}) {
		const market = yield* this.symbolInfo(cmd.s)
		cmd._sym = market.symbol
		let isMulti = !market.isDelivery && (this.getBalanceRaw()[this.meta.multiAsset] || {}).isMulti
		let currency = cmd.currency = (market.isDelivery && market.marginAsset) || (isMulti && this.meta.multiAsset) || market.quoteAsset || this.meta.defaultAsset
		const ticker = (cmd.pc && !cmd.up && this.getPrice(market.symbol)) || (yield* this.symbolTicker(cmd.up, market.symbol, cmd.up || ["mark", "index"].includes(cmd.pr)))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${market.symbol} is not available!`)
		}
		this.checkSlip(cmd, ohlc, market.symbol, ticker)
		if (cmd.up) {
			this.updatePrice(market.symbol, ticker)
			if (!cmd.b && !cmd.ub) {
				return ticker
			}
		}

		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 = isMulti || (!market.isDelivery && balances && balances[this.meta.multiAsset] && balances[this.meta.multiAsset].isMulti)
			isMulti && (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.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)")
		}

		const leverage = cmd._lev = cmd.l * (this.ohlc(cmd, ohlc, 'lr') || 1) || (yield* this.getLeverage(market, cmd.isBid ? "BUY" : "SELL")) || 1
		const available = cmd.y === "equity" ? balance.balance : balance.available
		const contracts = ohlc[cmd.y] || available * (cmd.lc || leverage) * (market.isDelivery ? price / market.contractSize : 1 / price)
		const inHedge = (yield* this.req('GET', "v1/positionSide/dual", market.isDelivery)).dualSidePosition
		const doMode = (cmd.pm === "normal" && inHedge) || (cmd.pm === "hedge" && !inHedge)

		let order = {
			symbol: market.symbol,
			side: cmd.isBid ? "BUY" : "SELL",
			type: cmd.t === "market" ? "MARKET" : "LIMIT",
			timeInForce: ({fok: "FOK", ioc: "IOC", post: "GTX"})[cmd.t] || "GTC",
			quantity: cmd.q._().mul(!cmd.q.wasPercent() ? (cmd.lc || leverage) / (cmd.u === "currency" ? (market.isDelivery ? market.contractSize : price) : 1) : 1)
				.reference(contracts).mul(this.ohlc(cmd, ohlc, 'qr') || 1).minmax(cmd.minq, cmd.maxq).resolve(market.quantityPrecision),
			positionSide: (!doMode && !inHedge) || (doMode && inHedge) ? "BOTH" : cmd.isBid ? (!cmd.r ? "LONG" : "SHORT") : (!cmd.r ? "SHORT" : "LONG"),
			newOrderRespType: "RESULT"
		}
		if (cmd.lq && ohlc.left) {
			this.info("using leftover quantity from last order: "+ohlc.left)
			order.quantity = ohlc.left.toFixed(market.quantityPrecision)
		}
		if (market.minNotional && order.quantity * price < market.minNotional) {
			this.warn(`order value below instrument minimum notional of ${market.minNotional} – use minq= to set minimum!`)
		} else if (market.minQty && order.quantity < market.minQty) {
			this.warn(`order quantity below instrument minimum of ${market.minQty} – use minq= to set minimum!`)
		} else if (market.maxQty && order.quantity > market.maxQty) {
			this.warn(`order quantity above instrument maximum of ${market.maxQty} – use maxq= to set minimum!`)
		} else if (order.quantity <= 0) {
			this.warn("your q parameter might be incorrect! (quantity of 0 calculated, use minq= to set minimum)")
		}
		if (cmd.t !== "market") {
			order.price = price
		} else {
			delete order.timeInForce
		}
		if (cmd.id) order.newClientOrderId = this.uniqueIDA(36, cmd.id)

		if (cmd.tp) {
			order.type = cmd.t === "market" ? "TAKE_PROFIT_MARKET" : "TAKE_PROFIT"
			order.stopPrice = cmd.tp._().relative(this.ohlc(cmd, ohlc, 'tpref', ticker, price) || first).resolve(market.pricePrecision)
			cmd.sr !== "last" && (order.workingType = cmd.sr.toUpperCase() + "_PRICE")
			cmd.pp && (order.priceProtect = "TRUE")
		} else if (cmd.sl) {
			order.type = cmd.t === "market" ? "STOP_MARKET" : "STOP"
			order.stopPrice = cmd.sl._().relative(this.ohlc(cmd, ohlc, 'slref', ticker, price) || first).resolve(market.pricePrecision)
			cmd.sr !== "last" && (order.workingType = cmd.sr.toUpperCase() + "_PRICE")
			cmd.pp && (order.priceProtect = "TRUE")
		} else if (cmd.ts) {
			const param = cmd.so && 'soref' || 'tsref'
			const stop = this.ohlc(cmd, ohlc, param, ticker, price) || first
			order.type = "TRAILING_STOP_MARKET"
			if (cmd.so || cmd.cbrate) {
				order.activationPrice = (cmd.so || cmd.ts)._().relative(stop).resolve(market.pricePrecision)
			}
			order.callbackRate = cmd.cbrate ? minmax(cmd.cbrate || 0.1, 0.1, 5) : cmd.ts._().reference(stop).div(stop / 10).minmax(0.1, 5).toDecimal()
			delete order.timeInForce
			cmd.sr !== "last" && (order.workingType = cmd.sr.toUpperCase() + "_PRICE")
		}
		if (cmd.r && order.positionSide === "BOTH") {
			if ((cmd.sl || cmd.tp) && cmd.t === "market" && cmd.q.wasPercent(true)) {
				delete order.quantity
				order.closePosition = true
			} else {
				order.reduceOnly = true
			}
		}

		this.info(`placing ${currency} order @ ${leverage}x${order.positionSide !== "BOTH"?" (hedged)":''}${isMulti?" (Multi Asset)":''}: `+stringify(order))
		if (cmd.d) {
			this.msgDisabled(cmd)
			return order
		}

		if (doMode) {
			this.info(`setting position mode to ${cmd.pm}...`)
			try {
				yield* this.req('POST', "v1/positionSide/dual", market.isDelivery, {dualSidePosition: cmd.pm === "hedge"})
			} catch(ex) {
				const err = ex.error || ex.msg || ex.message
				this.warn(`couldn't change position mode, please change manually! (Error: ${err})`)
			}
		}
		// $$$$ doType?
		if (cmd.mt) {
			this.info(`setting contract margin type to ${cmd.mt}...`)
			try {
				yield* this.req('POST', "v1/marginType", market.isDelivery, {symbol: market.symbol, marginType: cmd.mt.toUpperCase()})
			} catch(ex) {
				const err = ex.error || ex.msg || ex.message
				(!err || !err.startsWith("No need to change")) && this.warn(`couldn't change margin type, please change manually! (Error: ${err})`)
			}
		}
		// $$$$ doLev?
		if (cmd.l !== undefined) {
			this.info(`setting contract leverage to ${leverage}x...`)
			try {
				yield* this.req('POST', "v1/leverage", market.isDelivery, {symbol: market.symbol, leverage: leverage})
			} catch(ex) {
				const err = ex.error || ex.msg || ex.message
				this.warn(`couldn't set leverage, please set manually! (Error: ${err})`)
			}
		}

		let result = null
		this.checkOrder(order, 107115019, -10)
		if (market.maxQty && order.quantity > market.maxQty) {
			let leftQty = order.quantity, 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.quantity = Math.min(leftQty, market.maxQty)
				if (cmd.id) order.newClientOrderId = this.uniqueIDA(36, cmd.id, i)
				this.info(`posting split order #${i++} of size ${order.quantity}...`)
				result = yield* this.req('POST', "v1/order", market.isDelivery, order)
				leftQty -= order.quantity
			} while(leftQty > 0)
		} else {
			result = yield* this.req('POST', "v1/order", market.isDelivery, order)
		}
		if (result && result.price === "0") delete result.price
		if (result && result.stopPrice === "0") delete result.stopPrice
		return result
	}

	*ordersCancel(cmd, ohlc = {}) {
		const market = yield* this.symbolInfo(cmd.s)
		cmd._sym = market.symbol
		if (!cmd.ch && !cmd.d && !cmd.b && !cmd.fp && !cmd.tp && !cmd.sl && !cmd.ts && !cmd.has('t') && cmd.r === undefined && !cmd.id && cmd.cm.wasPercent(true) && !cmd.gt && !cmd.gte && !cmd.lt && !cmd.lte && !cmd.sv) {
			this.info(`canceling all ${market.symbol} orders in bulk!`)
			const result = yield* this.req('DELETE', "v1/allOpenOrders", market.isDelivery, {symbol: market.symbol})
			return result && result.code === 200
		}

		let orders = yield* this.req('GET*', "v1/openOrders", market.isDelivery, {symbol: market.symbol})
		if (!orders || !orders.filter) {
			this.msgOrderErr()
			return false
		}
		orders = orders.filter(order => order.status !== "FILLED")
		if (!orders.length) {
			this.msgOrdersNone()
			return false
		}

		const ids = (cmd.id || "").split(',').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.price)) {
				return !match
			}
			if ((cmd.tp && !order.type.startsWith("TAKE_PROFIT")) || (cmd.sl && !order.type.startsWith("STOP")) || (cmd.ts && !order.type.startsWith("TRAILING_STOP"))) {
				return !match
			}
			if (hasType && ((cmd.t === "limit" && !order.type.includes("LIMIT")) || (cmd.t === "market" && order.type.includes("LIMIT")))) {
				return !match
			}
			if ((cmd.t === "open" && Number(order.stopPrice)) || (cmd.t === "close" && !Number(order.stopPrice))) {
				return !match
			}
			if (cmd.r !== undefined && !cmd.r != !order.reduceOnly) {
				return !match
			}
			if (cmd.id && (!order.clientOrderId || !order.clientOrderId.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": 	["updateTime", true],
				"oldest": 	["updateTime", false],
				"highest": 	["price", true],
				"lowest": 	["price", false],
				"biggest": 	["origQty", true],
				"smallest": ["origQty", 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 (!orders.length || cmd.ch || cmd.d) {
			orders.length && this.msgDisabled(cmd)
			return orders
		}

		const orderIds = orders.map(order => order.orderId)
		return yield* this.req('DELETE', "v1/batchOrders", market.isDelivery, {symbol: market.symbol, orderIdList: `[${orderIds.join(',')}]`})
	}

	*positionsClose(cmd, pos, i, ohlc = {}, market) {
		const ticker = (cmd.pc && !cmd.up && this.getPrice(market.symbol)) || (yield* this.symbolTicker(cmd.up, market.symbol, cmd.up || ["mark", "index"].includes(cmd.pr)))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${market.symbol} is not available!`)
		}
		ticker.pos = pos.entryPrice
		ticker.liq = pos.liquidationPrice
		this.checkSlip(cmd, ohlc, market.symbol, ticker)
		cmd.up && this.updatePrice(market.symbol, ticker)

		const first = ticker[cmd.sl || cmd.tp || cmd.ts ? 'pos' : 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.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)")
		}

		const leverage = cmd.l || pos.leverage || 1
		let order = {
			symbol: market.symbol,
			side: pos.positionAmt > 0 ? 'SELL' : 'BUY',
			type: cmd.t === "market" ? "MARKET" : "LIMIT",
			timeInForce: ({fok: "FOK", ioc: "IOC", post: "GTX"})[cmd.t] || "GTC",
			quantity: cmd.q._().mul(!cmd.q.wasPercent() ? (cmd.lc || leverage) / (cmd.u === "currency" ? price : 1) : 1)
				.reference(pos.quantity).mul(this.ohlc(cmd, ohlc, 'qr') || 1).minmax(cmd.minq, cmd.maxq).resolve(market.quantityPrecision),
			positionSide: pos.positionSide,
			newOrderRespType: "RESULT"
		}
		if (cmd.t !== "market") order.price = price
		else delete order.timeInForce
		if (cmd.id) order.newClientOrderId = this.uniqueIDA(36, cmd.id, i)
		if (order.positionSide === "BOTH") {
			if ((cmd.tp || cmd.sl) && cmd.t === "market" && cmd.q.wasPercent(true)) {
				delete order.quantity
				order.closePosition = true
			} else {
				order.reduceOnly = cmd.r === undefined ? true : cmd.r
			}
		}
		if (market.maxQty && order.quantity > market.maxQty) {
			this.warn(`order quantity above instrument maximum of ${market.maxQty} – use maxq= to set minimum!`)
		} else if (order.quantity <= 0) {
			this.warn("your q parameter might be incorrect! (quantity of 0 calculated, use minq= to set minimum)")
		}

		if (cmd.tp) {
			order.type = cmd.t === "market" ? "TAKE_PROFIT_MARKET" : "TAKE_PROFIT"
			order.stopPrice = cmd.tp._().relative(this.ohlc(cmd, ohlc, 'tpref', ticker, price) || first).resolve(market.pricePrecision)
			cmd.sr !== "last" && (order.workingType = cmd.sr.toUpperCase() + "_PRICE")
			cmd.pp && (order.priceProtect = "TRUE")
		} else if (cmd.sl) {
			order.type = cmd.t === "market" ? "STOP_MARKET" : "STOP"
			order.stopPrice = cmd.sl._().relative(this.ohlc(cmd, ohlc, 'slref', ticker, price) || first).resolve(market.pricePrecision)
			cmd.sr !== "last" && (order.workingType = cmd.sr.toUpperCase() + "_PRICE")
			cmd.pp && (order.priceProtect = "TRUE")
		} else if (cmd.ts) {
			const hasStop = cmd.so && !cmd.cbrate
			const param = hasStop && 'soref' || 'tsref'
			const stop = this.ohlc(cmd, ohlc, param, ticker, price) || first
			order.type = "TRAILING_STOP_MARKET"
			order.activationPrice = (hasStop ? cmd.so : cmd.ts)._().relative(stop).resolve(market.pricePrecision)
			order.callbackRate = hasStop ? cmd.ts._().reference(stop).div(stop / 10).minmax(0.1, 5).toDecimal() : minmax(cmd.cbrate || 0.1, 0.1, 5)
			delete order.timeInForce
			cmd.sr !== "last" && (order.workingType = cmd.sr.toUpperCase() + "_PRICE")
		}

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

		let result = null
		this.checkOrder(order, 107115019, -10)
		if (market.maxQty && order.quantity > market.maxQty) {
			let leftQty = order.quantity, 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.quantity = Math.min(leftQty, market.maxQty)
				if (cmd.id) order.newClientOrderId = this.uniqueIDA(36, cmd.id, i)
				this.info(`posting split order #${i++} of size ${order.quantity}...`)
				result = yield* this.req('POST', "v1/order", market.isDelivery, order)
				leftQty -= order.quantity
			} while(leftQty > 0)
		} else {
			result = yield* this.req('POST', "v1/order", market.isDelivery, order)
		}
		if (result && result.price === "0") delete result.price
		if (result && result.stopPrice === "0") delete result.stopPrice
		return result
	}

	*positionsCloseAll(cmd, ohlc, positions) {
		const symbols = yield* this.symbolInfo()
		if (!positions) {
			const sym = cmd.s !== '*' && cmd.s
			const market = sym && symbols[sym]
			if (sym && !market) {
				throw new Error(`Unknown market symbol: ${sym} – Use the 'Symbols' button in the exchange settings or alert editor to browse for valid symbols!`)
			}
			const isDelivery = market && market.isDelivery
			const futures = !sym || !isDelivery ? (yield* this.req('GET', "v2/positionRisk", false, sym && {symbol: market.symbol})) : []
			if (!futures || !futures.filter) {
				this.msgPosErr("Futures / USDⓈ-M")
				return false
			}
			const delivery = !sym || isDelivery ? (yield* this.req('GET', "v1/positionRisk", true, sym && {pair: market.pair})) : []
			if (!delivery || !delivery.filter) {
				this.msgPosErr("Delivery / COIN-M")
				return false
			}
			positions = [...futures, ...delivery].filter(pos => Number(pos.positionAmt) && (!sym || pos.symbol == market.symbol))
			if (!positions.length) {
				this.msgPosNone()
				return false
			}
		}

		const isMulti = (this.getBalanceRaw()[this.meta.multiAsset] || {}).isMulti
		const match = cmd.cr ? false : true
		const tickers = {}
		yield* this.filterPosStart(cmd, ohlc, tickers, positions)
		const totalPos = positions.length
		positions = positions.filter(pos => {
			if ((cmd.isBid && pos.positionAmt < 0) || (cmd.isAsk && pos.positionAmt > 0)) {
				return false
			}
			if (cmd.pm === "hedge" && pos.positionSide === "BOTH") {
				return !match
			}
			if (cmd.fp && cmd.fp.compare(pos.entryPrice)) {
				return !match
			}
			pos.leverage = Number(pos.leverage)
			if (cmd.l && pos.leverage !== cmd.l) {
				return !match
			}
			const market = symbols[pos.symbol] || {}
			cmd._sym = market.symbol
			pos.currency = cmd.currency = (market.isDelivery && market.marginAsset) || (isMulti && this.meta.multiAsset) || market.quoteAsset || this.meta.defaultAsset
			pos.side = pos.positionAmt < 0 ? 'short' : 'long'
			pos.quantity = Math.abs(pos.positionAmt)
			pos._sizeCoin = pos.quantity / pos.leverage / (market.isDelivery ? pos.entryPrice / market.contractSize : 1)
			pos._sizeUSD = pos._sizeCoin * pos.markPrice
			pos.pnl = pos.unRealizedProfit / (pos._sizeCoin * pos.leverage * (market.isDelivery ? 1 : pos.entryPrice))
			pos.markPrice = toPrecision(pos.markPrice, market.pricePrecision)
			pos.liquidationPrice = toPrecision(pos.liquidationPrice, market.pricePrecision)
			pos.unRealizedProfit = toPrecision(pos.unRealizedProfit, market.quantityPrecision)
			pos.avgEntryPrice = toPrecision(pos.entryPrice || 0, market.pricePrecision)
			if (!this.filterPos(cmd, ohlc, tickers, pos, balance => balance * pos.leverage * (market.isDelivery ? pos.entryPrice / market.contractSize : 1 / pos.entryPrice), market.quantityPrecision)) {
				return !match
			}
			return match
		})

		if (cmd.cm._().reference(positions.length).getMax() < positions.length) {
			const sort = {
				"newest": 	["updateTime", true],
				"oldest": 	["updateTime", 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, symbols[pos.symbol])
			if (order) {
				closeOrders.push(order)
				Object.assign(closeOrders.last(), Object.without(pos, ["quantity", "side"]))
			}
		}
		return closeOrders
	}

/*
	*transfer(cmd, ohlc) {
	}
*/
}

Broker.add(new BinanceFuturesTestnet())
