diff --git a/build.gradle.kts b/build.gradle.kts index 8d7dae536..fe5627273 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.9.2" +version = "1.9.3" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/MarginCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/MarginCalculator.kt index 5b3a43106..c12df5874 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/MarginCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/MarginCalculator.kt @@ -26,11 +26,11 @@ import kotlin.math.min internal object MarginCalculator { fun findExistingPosition( - account: InternalAccountState, + account: InternalAccountState?, marketId: String?, subaccountNumber: Int, ): InternalPerpetualPosition? { - val position = account.groupedSubaccounts[subaccountNumber]?.openPositions?.get(marketId) + val position = account?.groupedSubaccounts?.get(subaccountNumber)?.openPositions?.get(marketId) return if ( (position?.size ?: 0.0) != 0.0 ) { @@ -420,6 +420,16 @@ internal object MarginCalculator { } fun getChildSubaccountNumberForIsolatedMarginClosePosition( + account: InternalAccountState?, + subaccountNumber: Int, + tradeInput: InternalTradeInputState? + ): Int { + val marketId = tradeInput?.marketId ?: return subaccountNumber + val position = findExistingPosition(account, marketId, subaccountNumber) + return position?.subaccountNumber ?: subaccountNumber + } + + fun getChildSubaccountNumberForIsolatedMarginClosePositionDeprecated( parser: ParserProtocol, account: Map?, subaccountNumber: Int, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputCalculatorV2.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputCalculatorV2.kt index 5378e7425..6c5b24fb1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputCalculatorV2.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputCalculatorV2.kt @@ -1,11 +1,14 @@ package exchange.dydx.abacus.calculator.v2.tradeinput +import abs import exchange.dydx.abacus.calculator.CalculationPeriod import exchange.dydx.abacus.calculator.TradeCalculation import exchange.dydx.abacus.calculator.v2.AccountTransformerV2 import exchange.dydx.abacus.output.FeeTier import exchange.dydx.abacus.output.input.MarginMode +import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.OrderType +import exchange.dydx.abacus.output.input.TradeInputSize import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.state.internalstate.InternalAccountState import exchange.dydx.abacus.state.internalstate.InternalConfigsState @@ -16,12 +19,16 @@ import exchange.dydx.abacus.state.internalstate.InternalSubaccountState import exchange.dydx.abacus.state.internalstate.InternalTradeInputState import exchange.dydx.abacus.state.internalstate.InternalUserState import exchange.dydx.abacus.state.internalstate.InternalWalletState +import exchange.dydx.abacus.state.internalstate.safeCreate +import exchange.dydx.abacus.state.model.ClosePositionInputField +import exchange.dydx.abacus.utils.Numeric +import exchange.dydx.abacus.utils.Rounder internal class TradeInputCalculatorV2( private val parser: ParserProtocol, private val calculation: TradeCalculation, private val marginModeCalculator: TradeInputMarginModeCalculator = TradeInputMarginModeCalculator(), - private val marketOrderCalculator: TradeInputMarketOrderCalculator = TradeInputMarketOrderCalculator(calculation), + private val marketOrderCalculator: TradeInputMarketOrderCalculator = TradeInputMarketOrderCalculator(), private val nonMarketOrderCalculator: TradeInputNonMarketOrderCalculator = TradeInputNonMarketOrderCalculator(), private val optionsCalculator: TradeInputOptionsCalculator = TradeInputOptionsCalculator(parser), private val summaryCalculator: TradeInputSummaryCalculator = TradeInputSummaryCalculator(), @@ -52,6 +59,9 @@ internal class TradeInputCalculatorV2( ) if (input != null) { + if (calculation == TradeCalculation.closePosition) { + calculateClosePositionSize(trade, markets[trade.marketId], subaccount) + } when (trade.type) { OrderType.Market, OrderType.StopMarket, @@ -103,6 +113,41 @@ internal class TradeInputCalculatorV2( return trade } + private fun calculateClosePositionSize( + trade: InternalTradeInputState, + market: InternalMarketState?, + subaccount: InternalSubaccountState?, + ): InternalTradeInputState { + val inputType = ClosePositionInputField.invoke(trade.size?.input) + val marketId = trade.marketId ?: return trade + val position = subaccount?.openPositions?.get(marketId) ?: return trade + val positionSize = position.calculated[CalculationPeriod.current]?.size ?: return trade + val positionSizeAbs = positionSize.abs() + trade.side = if (positionSize > Numeric.double.ZERO) OrderSide.Sell else OrderSide.Buy + when (inputType) { + ClosePositionInputField.percent -> { + val percent = trade.sizePercent ?: return trade + val size = + if (percent > Numeric.double.ONE) positionSizeAbs else positionSizeAbs * percent + val stepSize = market?.perpetualMarket?.configs?.stepSize ?: return trade + trade.size = + TradeInputSize.safeCreate(trade.size).copy(size = Rounder.round(size, stepSize)) + return trade + } + + ClosePositionInputField.size -> { + trade.sizePercent = null + val size = trade.size?.size ?: return trade + if (size > positionSizeAbs) { + trade.size = TradeInputSize.safeCreate(trade.size).copy(size = positionSizeAbs) + } + } + + else -> {} + } + return trade + } + private fun finalize( trade: InternalTradeInputState, account: InternalAccountState, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputMarketOrderCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputMarketOrderCalculator.kt index 0ea09193b..2d5aeaa9a 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputMarketOrderCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputMarketOrderCalculator.kt @@ -4,7 +4,6 @@ package exchange.dydx.abacus.calculator.v2.tradeinput import abs import exchange.dydx.abacus.calculator.CalculationPeriod -import exchange.dydx.abacus.calculator.TradeCalculation import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.OrderbookUsage import exchange.dydx.abacus.output.input.TradeInputMarketOrder @@ -16,14 +15,11 @@ import exchange.dydx.abacus.state.internalstate.InternalSubaccountState import exchange.dydx.abacus.state.internalstate.InternalTradeInputState import exchange.dydx.abacus.state.internalstate.InternalUserState import exchange.dydx.abacus.state.internalstate.safeCreate -import exchange.dydx.abacus.state.model.ClosePositionInputField import exchange.dydx.abacus.utils.Numeric import exchange.dydx.abacus.utils.Rounder import kollections.toIList -internal class TradeInputMarketOrderCalculator( - private val calculation: TradeCalculation, -) { +internal class TradeInputMarketOrderCalculator() { fun calculate( trade: InternalTradeInputState, market: InternalMarketState?, @@ -31,9 +27,6 @@ internal class TradeInputMarketOrderCalculator( user: InternalUserState?, input: String?, ): InternalTradeInputState { - if (calculation == TradeCalculation.closePosition) { - calculateClosePositionSize(trade, market, subaccount) - } val marketOrder = createMarketOrder( trade = trade, market = market, @@ -87,41 +80,6 @@ internal class TradeInputMarketOrderCalculator( } } - private fun calculateClosePositionSize( - trade: InternalTradeInputState, - market: InternalMarketState?, - subaccount: InternalSubaccountState?, - ): InternalTradeInputState { - val inputType = ClosePositionInputField.invoke(trade.size?.input) - val marketId = trade.marketId ?: return trade - val position = subaccount?.openPositions?.get(marketId) ?: return trade - val positionSize = position.calculated[CalculationPeriod.current]?.size ?: return trade - val positionSizeAbs = positionSize.abs() - trade.side = if (positionSize > Numeric.double.ZERO) OrderSide.Sell else OrderSide.Buy - when (inputType) { - ClosePositionInputField.percent -> { - val percent = trade.sizePercent ?: return trade - val size = - if (percent > Numeric.double.ONE) positionSizeAbs else positionSizeAbs * percent - val stepSize = market?.perpetualMarket?.configs?.stepSize ?: return trade - trade.size = - TradeInputSize.safeCreate(trade.size).copy(size = Rounder.round(size, stepSize)) - return trade - } - - ClosePositionInputField.size -> { - trade.sizePercent = null - val size = trade.size?.size ?: return trade - if (size > positionSizeAbs) { - trade.size = TradeInputSize.safeCreate(trade.size).copy(size = positionSizeAbs) - } - } - - else -> {} - } - return trade - } - private fun createMarketOrder( trade: InternalTradeInputState, market: InternalMarketState?, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputNonMarketOrderCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputNonMarketOrderCalculator.kt index d92fc4e3a..c55a425af 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputNonMarketOrderCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputNonMarketOrderCalculator.kt @@ -20,7 +20,7 @@ internal class TradeInputNonMarketOrderCalculator { val stepSize = market?.perpetualMarket?.configs?.stepSize ?: 0.001 val price = getNonMarketOrderPrice(tradePrices, market, trade.type, isBuying) when (input) { - "size.size" -> { + "size.size", "size.percent" -> { val size = tradeSize.size val usdcSize = if (price != null && size != null) (price * size) else null diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/ClosePositionInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/ClosePositionInput.kt index 021f739cc..04d6664b1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/ClosePositionInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/ClosePositionInput.kt @@ -1,6 +1,7 @@ package exchange.dydx.abacus.output.input import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.internalstate.InternalTradeInputState import exchange.dydx.abacus.utils.Logger import kollections.JsExport import kotlinx.serialization.Serializable @@ -55,6 +56,30 @@ data class ClosePositionInput( val summary: TradeInputSummary? ) { companion object { + internal fun create( + state: InternalTradeInputState? + ): ClosePositionInput? { + if (state == null) { + return null + } + + return ClosePositionInput( + type = state.type, + side = state.side, + marketId = state.marketId, + size = ClosePositionInputSize( + size = state.size?.size, + usdcSize = state.size?.usdcSize, + percent = state.sizePercent, + input = state.size?.input, + ), + price = state.price, + fee = state.fee, + marketOrder = state.marketOrder, + summary = TradeInputSummary.create(state.summary), + ) + } + internal fun create( existing: ClosePositionInput?, parser: ParserProtocol, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt index aae4f1e6a..92fcb8d52 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/input/Input.kt @@ -63,8 +63,12 @@ data class Input( } else { TradeInput.create(existing?.trade, parser, parser.asMap(data?.get("trade"))) } - val closePosition = + + val closePosition = if (staticTyping) { + ClosePositionInput.create(state = internalState?.input?.closePosition) + } else { ClosePositionInput.create(existing?.closePosition, parser, parser.asMap(data?.get("closePosition"))) + } val transfer = TransferInput.create(existing?.transfer, parser, parser.asMap(data?.get("transfer")), environment, internalState?.transfer) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/ClosePositionInputProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/ClosePositionInputProcessor.kt new file mode 100644 index 000000000..35ecf3d25 --- /dev/null +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/ClosePositionInputProcessor.kt @@ -0,0 +1,242 @@ +package exchange.dydx.abacus.processor.input + +import abs +import exchange.dydx.abacus.calculator.CalculationPeriod +import exchange.dydx.abacus.calculator.MarginCalculator +import exchange.dydx.abacus.calculator.TradeCalculation +import exchange.dydx.abacus.calculator.v2.tradeinput.TradeInputCalculatorV2 +import exchange.dydx.abacus.output.input.InputType +import exchange.dydx.abacus.output.input.OrderSide +import exchange.dydx.abacus.output.input.OrderType +import exchange.dydx.abacus.output.input.TradeInputSize +import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.responses.ParsingError +import exchange.dydx.abacus.responses.cannotModify +import exchange.dydx.abacus.state.changes.Changes +import exchange.dydx.abacus.state.changes.StateChanges +import exchange.dydx.abacus.state.internalstate.InternalConfigsState +import exchange.dydx.abacus.state.internalstate.InternalInputState +import exchange.dydx.abacus.state.internalstate.InternalMarketSummaryState +import exchange.dydx.abacus.state.internalstate.InternalPerpetualPosition +import exchange.dydx.abacus.state.internalstate.InternalRewardsParamsState +import exchange.dydx.abacus.state.internalstate.InternalTradeInputState +import exchange.dydx.abacus.state.internalstate.InternalWalletState +import exchange.dydx.abacus.state.manager.StatsigConfig +import exchange.dydx.abacus.state.model.ClosePositionInputField +import exchange.dydx.abacus.utils.Numeric +import kollections.iListOf + +internal interface ClosePositionInputProcessorProtocol + +internal class ClosePositionInputProcessor( + private val parser: ParserProtocol, +) : ClosePositionInputProcessorProtocol { + + fun closePosition( + inputState: InternalInputState, + walletState: InternalWalletState, + marketSummaryState: InternalMarketSummaryState, + configs: InternalConfigsState, + rewardsParams: InternalRewardsParamsState?, + data: String?, + type: ClosePositionInputField, + subaccountNumber: Int, + ): TradeInputResult { + var changes: StateChanges? = null + var error: ParsingError? = null + + if (inputState.currentType != InputType.CLOSE_POSITION) { + inputState.currentType = InputType.CLOSE_POSITION + inputState.closePosition = initiateClosePosition( + marketId = null, + subaccountNumber = subaccountNumber, + walletState = walletState, + marketSummaryState = marketSummaryState, + configs = configs, + rewardsParams = rewardsParams, + ) + } + inputState.currentType = InputType.CLOSE_POSITION + + val childSubaccountNumber = + MarginCalculator.getChildSubaccountNumberForIsolatedMarginClosePosition( + account = walletState.account, + subaccountNumber = subaccountNumber, + tradeInput = inputState.closePosition, + ) + val subaccountNumberChanges = if (subaccountNumber == childSubaccountNumber) { + iListOf(subaccountNumber) + } else { + iListOf(subaccountNumber, childSubaccountNumber) + } + + var sizeChanged = false + val trade = inputState.closePosition + when (type) { + ClosePositionInputField.market -> { + val position = if (data != null) getPosition(data, subaccountNumber, walletState) else null + if (position != null) { + if (data != null) { + if (trade.marketId != data) { + trade.marketId = data + trade.size = null + } + } + trade.type = OrderType.Market + + val positionSize = position.calculated[CalculationPeriod.current]?.size ?: Numeric.double.ZERO + trade.side = if (positionSize > Numeric.double.ZERO) OrderSide.Sell else OrderSide.Buy + + trade.timeInForce = "IOC" + trade.reduceOnly = true + + val currentPositionLeverage = position.calculated[CalculationPeriod.current]?.leverage?.abs() + trade.targetLeverage = if (currentPositionLeverage != null && currentPositionLeverage > 0) currentPositionLeverage else 1.0 + + // default full close + trade.sizePercent = 1.0 + trade.size = TradeInputSize( + size = null, + usdcSize = null, + leverage = null, + input = "size.percent", + ) + + changes = StateChanges( + changes = iListOf(Changes.subaccount, Changes.input), + markets = null, + subaccountNumbers = subaccountNumberChanges, + ) + } else { + error = ParsingError.cannotModify(type.rawValue) + } + } + ClosePositionInputField.size -> { + val newSize = parser.asDouble(data) + sizeChanged = (newSize != trade.size?.size) + trade.size = trade.size?.copy(size = newSize) + changes = StateChanges( + changes = iListOf(Changes.subaccount, Changes.input), + markets = null, + subaccountNumbers = subaccountNumberChanges, + ) + } + ClosePositionInputField.percent -> { + val newPercent = parser.asDouble(data) + sizeChanged = (newPercent != trade.sizePercent) + trade.sizePercent = newPercent + changes = StateChanges( + changes = iListOf(Changes.subaccount, Changes.input), + markets = null, + subaccountNumbers = subaccountNumberChanges, + ) + } + ClosePositionInputField.useLimit -> { + val useLimitClose = + (parser.asBool(data) ?: false) && StatsigConfig.ff_enable_limit_close + + if (useLimitClose) { + trade.type = OrderType.Limit + trade.timeInForce = "GTT" + + trade.marketId?.let { marketId -> + val limitPrice = getMidMarketPrice(marketSummaryState, marketId) + trade.price = trade.price?.copy(limitPrice = limitPrice) + } + } else { + trade.type = OrderType.Market + trade.timeInForce = "IOC" + } + + changes = StateChanges( + changes = iListOf(Changes.subaccount, Changes.input), + markets = null, + subaccountNumbers = subaccountNumberChanges, + ) + } + ClosePositionInputField.limitPrice -> { + val limitPrice = parser.asDouble(data) + trade.price = trade.price?.copy(limitPrice = limitPrice) + changes = StateChanges( + changes = iListOf(Changes.subaccount, Changes.input), + markets = null, + subaccountNumbers = subaccountNumberChanges, + ) + } + } + if (sizeChanged) { + when (type) { + ClosePositionInputField.size, + ClosePositionInputField.percent -> { + trade.size = trade.size?.copy(input = type.rawValue) + } + else -> { } + } + } + + return TradeInputResult( + changes = changes, + error = error, + ) + } + + private fun getPosition( + marketId: String, + subaccountNumber: Int, + wallet: InternalWalletState, + ): InternalPerpetualPosition? { + val position = wallet.account.groupedSubaccounts[subaccountNumber]?.openPositions?.get(marketId) + ?: wallet.account.subaccounts[subaccountNumber]?.openPositions?.get(marketId) + + val size = position?.calculated?.get(CalculationPeriod.current)?.size + return if (size != null && size != Numeric.double.ZERO) { + position + } else { + null + } + } + + private fun initiateClosePosition( + marketId: String?, + subaccountNumber: Int, + walletState: InternalWalletState, + marketSummaryState: InternalMarketSummaryState, + configs: InternalConfigsState, + rewardsParams: InternalRewardsParamsState? + ): InternalTradeInputState { + val closePosition = InternalTradeInputState() + closePosition.type = OrderType.Market + closePosition.side = OrderSide.Buy + closePosition.marketId = marketId ?: "ETH-USD" + // default full close + closePosition.sizePercent = 1.0 + closePosition.size = TradeInputSize( + size = null, + usdcSize = null, + leverage = null, + input = "size.percent", + ) + + val calculator = TradeInputCalculatorV2(parser, TradeCalculation.closePosition) + return calculator.calculate( + trade = closePosition, + wallet = walletState, + marketSummary = marketSummaryState, + rewardsParams = rewardsParams, + configs = configs, + subaccountNumber = subaccountNumber, + input = "size.percent", + ) + } + + private fun getMidMarketPrice( + marketsSummary: InternalMarketSummaryState?, + marketId: String + ): Double? { + val market = marketsSummary?.markets?.get(marketId) ?: return null + val orderbook = market.consolidatedOrderbook ?: return null + val firstAskPrice = orderbook.asks?.firstOrNull()?.price ?: return null + val firstBidPrice = orderbook.bids?.firstOrNull()?.price ?: return null + return (firstAskPrice + firstBidPrice) / 2.0 + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt index 973f7a2f0..a2c59cd47 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt @@ -22,6 +22,7 @@ import exchange.dydx.abacus.state.internalstate.InternalConfigsState import exchange.dydx.abacus.state.internalstate.InternalInputState import exchange.dydx.abacus.state.internalstate.InternalMarketState import exchange.dydx.abacus.state.internalstate.InternalMarketSummaryState +import exchange.dydx.abacus.state.internalstate.InternalRewardsParamsState import exchange.dydx.abacus.state.internalstate.InternalTradeInputOptions import exchange.dydx.abacus.state.internalstate.InternalTradeInputState import exchange.dydx.abacus.state.internalstate.InternalWalletState @@ -35,6 +36,7 @@ internal interface TradeInputProcessorProtocol { marketSummaryState: InternalMarketSummaryState, walletState: InternalWalletState, configs: InternalConfigsState, + rewardsParams: InternalRewardsParamsState?, marketId: String, subaccountNumber: Int, ): StateChanges @@ -44,6 +46,7 @@ internal interface TradeInputProcessorProtocol { walletState: InternalWalletState, marketSummaryState: InternalMarketSummaryState, configs: InternalConfigsState, + rewardsParams: InternalRewardsParamsState?, inputData: String?, inputType: TradeInputField?, subaccountNumber: Int, @@ -64,6 +67,7 @@ internal class TradeInputProcessor( marketSummaryState: InternalMarketSummaryState, walletState: InternalWalletState, configs: InternalConfigsState, + rewardsParams: InternalRewardsParamsState?, marketId: String, subaccountNumber: Int, ): StateChanges { @@ -94,6 +98,7 @@ internal class TradeInputProcessor( walletState = walletState, marketSummaryState = marketSummaryState, configs = configs, + rewardsParams = rewardsParams, ) } @@ -127,6 +132,7 @@ internal class TradeInputProcessor( walletState: InternalWalletState, marketSummaryState: InternalMarketSummaryState, configs: InternalConfigsState, + rewardsParams: InternalRewardsParamsState?, inputData: String?, inputType: TradeInputField?, subaccountNumber: Int, @@ -141,6 +147,7 @@ internal class TradeInputProcessor( walletState = walletState, marketSummaryState = marketSummaryState, configs = configs, + rewardsParams = rewardsParams, ) } if (inputType == null) { @@ -316,6 +323,7 @@ internal class TradeInputProcessor( walletState: InternalWalletState, marketSummaryState: InternalMarketSummaryState, configs: InternalConfigsState, + rewardsParams: InternalRewardsParamsState?, ): InternalTradeInputState { val market = marketSummaryState.markets[marketId] val marginMode = MarginCalculator.findExistingMarginMode( @@ -337,7 +345,7 @@ internal class TradeInputProcessor( ), wallet = walletState, marketSummary = marketSummaryState, - rewardsParams = null, + rewardsParams = rewardsParams, configs = configs, subaccountNumber = subaccountNumber, input = null, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt index d522f7e3f..86e78812f 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt @@ -55,6 +55,7 @@ internal data class InternalState( internal data class InternalInputState( var trade: InternalTradeInputState = InternalTradeInputState(), + var closePosition: InternalTradeInputState = InternalTradeInputState(), var receiptLines: List? = null, var errors: List? = null, var childSubaccountErrors: List? = null, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+ClosePositionInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+ClosePositionInput.kt index bd99514ac..7bab0089b 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+ClosePositionInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+ClosePositionInput.kt @@ -39,126 +39,148 @@ fun TradingStateMachine.closePosition( type: ClosePositionInputField, subaccountNumber: Int ): StateResponse { - var changes: StateChanges? = null - var error: ParsingError? = null - val typeText = type.rawValue - - val input = this.input?.mutable() ?: mutableMapOf() - input["current"] = "closePosition" - val trade = - parser.asMap(input["closePosition"])?.mutable() ?: initiateClosePosition( - null, - subaccountNumber, + if (staticTyping) { + val result = closePositionInputProcessor.closePosition( + inputState = internalState.input, + walletState = internalState.wallet, + marketSummaryState = internalState.marketsSummary, + configs = internalState.configs, + rewardsParams = internalState.rewardsParams, + data = data, + type = type, + subaccountNumber = subaccountNumber, ) - - val childSubaccountNumber = - MarginCalculator.getChildSubaccountNumberForIsolatedMarginClosePosition( - parser, - account, - subaccountNumber, - trade, + result.changes?.let { + updateStateChanges(it) + } + return StateResponse( + state, + result.changes, + if (result.error != null) iListOf(result.error) else null, ) - val subaccountNumberChanges = if (subaccountNumber == childSubaccountNumber) { - iListOf(subaccountNumber) } else { - iListOf(subaccountNumber, childSubaccountNumber) - } + var changes: StateChanges? = null + var error: ParsingError? = null + val typeText = type.rawValue + + val input = this.input?.mutable() ?: mutableMapOf() + input["current"] = "closePosition" + val trade = + parser.asMap(input["closePosition"])?.mutable() ?: initiateClosePosition( + null, + subaccountNumber, + ) - var sizeChanged = false - when (typeText) { - ClosePositionInputField.market.rawValue -> { - val position = if (data != null) getPosition(data, subaccountNumber) else null - if (position != null) { - if (data != null) { - if (parser.asString(trade["marketId"]) != data) { - trade.safeSet("marketId", data) - trade.safeSet("size", null) + val childSubaccountNumber = + MarginCalculator.getChildSubaccountNumberForIsolatedMarginClosePositionDeprecated( + parser, + account, + subaccountNumber, + trade, + ) + val subaccountNumberChanges = if (subaccountNumber == childSubaccountNumber) { + iListOf(subaccountNumber) + } else { + iListOf(subaccountNumber, childSubaccountNumber) + } + + var sizeChanged = false + when (typeText) { + ClosePositionInputField.market.rawValue -> { + val position = if (data != null) getPosition(data, subaccountNumber) else null + if (position != null) { + if (data != null) { + if (parser.asString(trade["marketId"]) != data) { + trade.safeSet("marketId", data) + trade.safeSet("size", null) + } } - } - trade["type"] = "MARKET" + trade["type"] = "MARKET" - val positionSize = - parser.asDouble(parser.value(position, "size.current")) ?: Numeric.double.ZERO - trade["side"] = if (positionSize > Numeric.double.ZERO) "SELL" else "BUY" + val positionSize = + parser.asDouble(parser.value(position, "size.current")) ?: Numeric.double.ZERO + trade["side"] = if (positionSize > Numeric.double.ZERO) "SELL" else "BUY" - trade["timeInForce"] = "IOC" - trade["reduceOnly"] = true + trade["timeInForce"] = "IOC" + trade["reduceOnly"] = true - val currentPositionLeverage = parser.asDouble(parser.value(position, "leverage.current"))?.abs() - trade["targetLeverage"] = if (currentPositionLeverage != null && currentPositionLeverage > 0) currentPositionLeverage else 1.0 + val currentPositionLeverage = parser.asDouble(parser.value(position, "leverage.current"))?.abs() + trade["targetLeverage"] = if (currentPositionLeverage != null && currentPositionLeverage > 0) currentPositionLeverage else 1.0 - // default full close - trade.safeSet("size.percent", 1.0) - trade.safeSet("size.input", "size.percent") + // default full close + trade.safeSet("size.percent", 1.0) + trade.safeSet("size.input", "size.percent") + changes = StateChanges( + iListOf(Changes.subaccount, Changes.input), + null, + subaccountNumberChanges, + ) + } else { + error = ParsingError.cannotModify(typeText) + } + } + ClosePositionInputField.size.rawValue, ClosePositionInputField.percent.rawValue -> { + sizeChanged = (parser.asDouble(data) != parser.asDouble(trade[typeText])) + trade.safeSet(typeText, data) changes = StateChanges( iListOf(Changes.subaccount, Changes.input), null, subaccountNumberChanges, ) - } else { - error = ParsingError.cannotModify(typeText) } - } - ClosePositionInputField.size.rawValue, ClosePositionInputField.percent.rawValue -> { - sizeChanged = (parser.asDouble(data) != parser.asDouble(trade[typeText])) - trade.safeSet(typeText, data) - changes = StateChanges( - iListOf(Changes.subaccount, Changes.input), - null, - subaccountNumberChanges, - ) - } - ClosePositionInputField.useLimit.rawValue -> { - val useLimitClose = (parser.asBool(data) ?: false) && StatsigConfig.ff_enable_limit_close - trade.safeSet(typeText, useLimitClose) - - if (useLimitClose) { - trade["type"] = "LIMIT" - trade["timeInForce"] = "GTT" - parser.asString(trade["marketId"])?.let { - trade.safeSet("price.limitPrice", getMidMarketPrice(it)) + ClosePositionInputField.useLimit.rawValue -> { + val useLimitClose = (parser.asBool(data) ?: false) && StatsigConfig.ff_enable_limit_close + trade.safeSet(typeText, useLimitClose) + + if (useLimitClose) { + trade["type"] = "LIMIT" + trade["timeInForce"] = "GTT" + parser.asString(trade["marketId"])?.let { + trade.safeSet("price.limitPrice", getMidMarketPrice(it)) + } + } else { + trade["type"] = "MARKET" + trade["timeInForce"] = "IOC" } - } else { - trade["type"] = "MARKET" - trade["timeInForce"] = "IOC" - } - changes = StateChanges( - iListOf(Changes.subaccount, Changes.input), - null, - subaccountNumberChanges, - ) - } - ClosePositionInputField.limitPrice.rawValue -> { - trade.safeSet(typeText, parser.asDouble(data)) - changes = StateChanges( - iListOf(Changes.subaccount, Changes.input), - null, - subaccountNumberChanges, - ) - } - else -> {} - } - if (sizeChanged) { - when (typeText) { - ClosePositionInputField.size.rawValue, - ClosePositionInputField.percent.rawValue -> { - trade.safeSet("size.input", typeText) + changes = StateChanges( + iListOf(Changes.subaccount, Changes.input), + null, + subaccountNumberChanges, + ) + } + ClosePositionInputField.limitPrice.rawValue -> { + trade.safeSet(typeText, parser.asDouble(data)) + changes = StateChanges( + iListOf(Changes.subaccount, Changes.input), + null, + subaccountNumberChanges, + ) } else -> {} } - } - input["closePosition"] = trade - this.input = input + if (sizeChanged) { + when (typeText) { + ClosePositionInputField.size.rawValue, + ClosePositionInputField.percent.rawValue -> { + trade.safeSet("size.input", typeText) + } - changes?.let { - updateStateChanges(it) + else -> {} + } + } + input["closePosition"] = trade + this.input = input + + changes?.let { + updateStateChanges(it) + } + return StateResponse(state, changes, if (error != null) iListOf(error) else null) } - return StateResponse(state, changes, if (error != null) iListOf(error) else null) } -fun TradingStateMachine.getPosition( +private fun TradingStateMachine.getPosition( marketId: String, subaccountNumber: Int, ): Map? { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TradeInput.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TradeInput.kt index 7fa839c8b..fba530bdb 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TradeInput.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+TradeInput.kt @@ -95,6 +95,7 @@ internal fun TradingStateMachine.tradeInMarket( marketSummaryState = internalState.marketsSummary, walletState = internalState.wallet, configs = internalState.configs, + rewardsParams = internalState.rewardsParams, marketId = marketId, subaccountNumber = subaccountNumber, ) @@ -228,6 +229,7 @@ fun TradingStateMachine.trade( walletState = internalState.wallet, marketSummaryState = internalState.marketsSummary, configs = internalState.configs, + rewardsParams = internalState.rewardsParams, inputType = type, inputData = data, subaccountNumber = subaccountNumber, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt index d296ca4c5..dc2ea9bd7 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt @@ -37,6 +37,7 @@ import exchange.dydx.abacus.output.input.ReceiptLine import exchange.dydx.abacus.processor.assets.AssetsProcessor import exchange.dydx.abacus.processor.configs.ConfigsProcessor import exchange.dydx.abacus.processor.configs.RewardsParamsProcessor +import exchange.dydx.abacus.processor.input.ClosePositionInputProcessor import exchange.dydx.abacus.processor.input.TradeInputProcessor import exchange.dydx.abacus.processor.launchIncentive.LaunchIncentiveProcessor import exchange.dydx.abacus.processor.markets.MarketsSummaryProcessor @@ -127,6 +128,7 @@ open class TradingStateMachine( internal val rewardsProcessor = RewardsParamsProcessor(parser) internal val launchIncentiveProcessor = LaunchIncentiveProcessor(parser) internal val tradeInputProcessor = TradeInputProcessor(parser) + internal val closePositionInputProcessor = ClosePositionInputProcessor(parser) internal val marketsCalculator = MarketCalculator(parser) internal val accountCalculator = AccountCalculator(parser, useParentSubaccount) @@ -756,15 +758,22 @@ open class TradingStateMachine( private fun calculateTrade(tag: String, calculation: TradeCalculation, subaccountNumber: Int) { if (staticTyping) { val calculator = TradeInputCalculatorV2(parser, calculation) - calculator.calculate( - trade = internalState.input.trade, - wallet = internalState.wallet, - marketSummary = internalState.marketsSummary, - rewardsParams = internalState.rewardsParams, - configs = internalState.configs, - subaccountNumber = subaccountNumber, - input = internalState.input.trade.size?.input, - ) + val inputType = + calculator.calculate( + trade = when (calculation) { + TradeCalculation.closePosition -> internalState.input.closePosition + TradeCalculation.trade -> internalState.input.trade + }, + wallet = internalState.wallet, + marketSummary = internalState.marketsSummary, + rewardsParams = internalState.rewardsParams, + configs = internalState.configs, + subaccountNumber = subaccountNumber, + input = when (calculation) { + TradeCalculation.closePosition -> internalState.input.closePosition.size?.input + TradeCalculation.trade -> internalState.input.trade.size?.input + }, + ) } else { val input = this.input?.mutable() val trade = parser.asNativeMap(input?.get(tag)) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeAccountStateValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeAccountStateValidator.kt index 9b69c56cd..9c89ef2d1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeAccountStateValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeAccountStateValidator.kt @@ -3,6 +3,7 @@ package exchange.dydx.abacus.validator.trade import exchange.dydx.abacus.calculator.CalculationPeriod import exchange.dydx.abacus.output.account.SubaccountOrder import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.OrderStatus import exchange.dydx.abacus.output.input.OrderType @@ -33,7 +34,11 @@ internal class TradeAccountStateValidator( restricted: Boolean, environment: V4Environment? ): List? { - val trade = internalState.input.trade + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } val subaccountNumber = subaccountNumber ?: return null val subaccount = internalState.wallet.account.subaccounts[subaccountNumber] ?: return null val isIsolatedMarginTrade = subaccountNumber >= NUM_PARENT_SUBACCOUNTS diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt index 6a31afdbc..4b32b4d40 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt @@ -2,6 +2,7 @@ package exchange.dydx.abacus.validator.trade import exchange.dydx.abacus.calculator.CalculationPeriod import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.ValidationError import exchange.dydx.abacus.protocols.LocalizerProtocol @@ -28,7 +29,11 @@ internal class TradeBracketOrdersValidator( restricted: Boolean, environment: V4Environment? ): List? { - val trade = internalState.input.trade + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } if (!trade.options.needsBrackets) { return null } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeFieldsValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeFieldsValidator.kt index 3fb30c091..c71f44f1e 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeFieldsValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeFieldsValidator.kt @@ -1,5 +1,6 @@ package exchange.dydx.abacus.validator.trade +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.ValidationError import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol @@ -24,7 +25,11 @@ internal class TradeFieldsValidator( restricted: Boolean, environment: V4Environment? ): List? { - val trade = internalState.input.trade + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } val errors = mutableListOf() if (trade.options.needsSize) { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt index 68db12cf3..0e8916b0c 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt @@ -2,6 +2,7 @@ package exchange.dydx.abacus.validator.trade import abs import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.output.input.ValidationError @@ -48,7 +49,12 @@ internal class TradeInputDataValidator( ): List? { val errors = mutableListOf() - val marketId = internalState.input.trade.marketId ?: return null + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } + val marketId = trade.marketId ?: return null val market = internalState.marketsSummary.markets[marketId] validateSize( diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeMarketOrderInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeMarketOrderInputValidator.kt index 216409ebe..5bc6864dd 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeMarketOrderInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeMarketOrderInputValidator.kt @@ -2,6 +2,7 @@ package exchange.dydx.abacus.validator.trade import abs import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.output.input.ValidationError import exchange.dydx.abacus.protocols.LocalizerProtocol @@ -29,7 +30,11 @@ internal class TradeMarketOrderInputValidator( restricted: Boolean, environment: V4Environment? ): List? { - val trade = internalState.input.trade + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } if (trade.type != OrderType.Market) { return null } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradePositionStateValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradePositionStateValidator.kt index 98152042d..9027872d3 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradePositionStateValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradePositionStateValidator.kt @@ -2,6 +2,7 @@ package exchange.dydx.abacus.validator.trade import exchange.dydx.abacus.calculator.CalculationPeriod import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.ValidationError import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol @@ -28,7 +29,11 @@ internal class TradePositionStateValidator( restricted: Boolean, environment: V4Environment? ): List? { - val trade = internalState.input.trade + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } val subaccountNumber = subaccountNumber ?: return null val subaccount = internalState.wallet.account.subaccounts[subaccountNumber] val position = subaccount?.openPositions?.get(trade.marketId) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeResctrictedValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeResctrictedValidator.kt index c097c0cc8..4f76b9898 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeResctrictedValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeResctrictedValidator.kt @@ -1,6 +1,7 @@ package exchange.dydx.abacus.validator.trade import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.ValidationError import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol @@ -24,7 +25,12 @@ internal class TradeResctrictedValidator( restricted: Boolean, environment: V4Environment? ): List? { - val marketId = internalState.input.trade.marketId ?: return null + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } + val marketId = trade.marketId ?: return null val market = internalState.marketsSummary.markets[marketId] val closeOnlyError = validateClosingOnly( diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt index 92e323bec..8a00ff5a6 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt @@ -2,6 +2,7 @@ package exchange.dydx.abacus.validator.trade import exchange.dydx.abacus.calculator.CalculationPeriod import exchange.dydx.abacus.output.input.ErrorType +import exchange.dydx.abacus.output.input.InputType import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.output.input.ValidationError @@ -38,7 +39,11 @@ internal class TradeTriggerPriceValidator( restricted: Boolean, environment: V4Environment? ): List? { - val trade = internalState.input.trade + val trade = when (internalState.input.currentType) { + InputType.TRADE -> internalState.input.trade + InputType.CLOSE_POSITION -> internalState.input.closePosition + else -> return null + } val needsTriggerPrice = trade.options.needsTriggerPrice if (!needsTriggerPrice) { return null diff --git a/v4_abacus.podspec b/v4_abacus.podspec index 5cce547c2..8ebcae8ce 100644 --- a/v4_abacus.podspec +++ b/v4_abacus.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'v4_abacus' - spec.version = '1.9.2' + spec.version = '1.9.3' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''