Skip to content

Commit

Permalink
v1.6.53: add analytics for SL/TP (#325)
Browse files Browse the repository at this point in the history
* wip

* wip clean up

* bump v

* bump v

* midway through fixing tests

* revise analytics

* review comments

* rename fromSlTp to fromSlTpDialog

* add notation for frunctions

* bumped time (#330)
  • Loading branch information
moo-onthelawn authored May 1, 2024
1 parent 88c6772 commit b5703c7
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import exchange.dydx.abacus.utils.mutable
import exchange.dydx.abacus.utils.safeSet
import kotlin.math.abs

internal object TriggerOrdersConstants {
const val TRIGGER_ORDER_DEFAULT_DURATION_DAYS = 90.0
}

@Suppress("UNCHECKED_CAST")
internal class TriggerOrdersInputCalculator(val parser: ParserProtocol) {
internal fun calculate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ enum class AnalyticsEvent(val rawValue: String) {
// App
NetworkStatus("NetworkStatus"),

// Trade Events
// Trade
TradePlaceOrderClick("TradePlaceOrderClick"),
TradeCancelOrderClick("TradeCancelOrderClick"),
TradePlaceOrder("TradePlaceOrder"),
Expand All @@ -221,6 +221,9 @@ enum class AnalyticsEvent(val rawValue: String) {
TradeCancelOrderConfirmed("TradeCancelOrderConfirmed"),
TradePlaceOrderConfirmed("TradePlaceOrderConfirmed"),

// Trigger Order
TriggerOrderClick("TriggerOrderClick"),

// Transfers
TransferFaucetConfirmed("TransferFaucetConfirmed");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package exchange.dydx.abacus.state.manager

import exchange.dydx.abacus.calculator.TriggerOrdersConstants.TRIGGER_ORDER_DEFAULT_DURATION_DAYS
import exchange.dydx.abacus.output.Compliance
import exchange.dydx.abacus.output.ComplianceAction
import exchange.dydx.abacus.output.ComplianceStatus
Expand Down Expand Up @@ -120,9 +121,6 @@ open class StateManagerAdaptor(
var stateNotification: StateNotificationProtocol?,
var dataNotification: DataNotificationProtocol?,
) {
@Suppress("LocalVariableName", "PropertyName")
private val TRIGGER_ORDER_DEFAULT_DURATION_DAYS = 28.0

var stateMachine: TradingStateMachine = PerpTradingStateMachine(
environment,
uiImplementations.localizer,
Expand Down Expand Up @@ -1878,12 +1876,13 @@ open class StateManagerAdaptor(
fun triggerOrdersPayload(): HumanReadableTriggerOrdersPayload {
val placeOrderPayloads = mutableListOf<HumanReadablePlaceOrderPayload>()
val cancelOrderPayloads = mutableListOf<HumanReadableCancelOrderPayload>()
val triggerOrders = stateMachine.state?.input?.triggerOrders
val triggerOrders = requireNotNull(stateMachine.state?.input?.triggerOrders) { "triggerOrders input was null" }

val marketId = requireNotNull(triggerOrders.marketId) { "triggerOrders.marektId was null" }
val subaccountNumber = connectedSubaccountNumber ?: throw Exception("subaccountNumber is null")
val subaccount = stateMachine.state?.subaccount(subaccountNumber) ?: throw Exception("subaccount is null")

val marketId = triggerOrders?.marketId ?: throw Exception("marketId is null")
val position = subaccount.openPositions?.find { it.id == marketId }
val positionSize = position?.size?.current

fun updateTriggerOrder(triggerOrder: TriggerOrder) {
// Cases
Expand Down Expand Up @@ -1923,6 +1922,8 @@ open class StateManagerAdaptor(
}

return HumanReadableTriggerOrdersPayload(
marketId,
positionSize,
placeOrderPayloads,
cancelOrderPayloads,
)
Expand Down Expand Up @@ -2152,6 +2153,7 @@ open class StateManagerAdaptor(
?: throw Exception("subaccount is null")
val order = subaccount.orders?.firstOrNull { it.id == orderId }
?: throw Exception("order is null")
val type = order.type.rawValue
val clientId = order.clientId ?: throw Exception("clientId is null")
val orderFlags = order.orderFlags ?: throw Exception("orderFlags is null")
val clobPairId = order.clobPairId ?: throw Exception("clobPairId is null")
Expand All @@ -2160,6 +2162,7 @@ open class StateManagerAdaptor(

return HumanReadableCancelOrderPayload(
subaccountNumber,
type,
orderId,
clientId,
orderFlags,
Expand Down Expand Up @@ -2291,13 +2294,19 @@ open class StateManagerAdaptor(
}
}

internal open fun fromSlTpDialogParams(fromSlTpDialog: Boolean): IMap<String, Any> {
return iMapOf(
"fromSlTpDialog" to fromSlTpDialog,
)
}

internal open fun trackingParams(interval: Double): IMap<String, Any> {
return iMapOf(
"roundtripMs" to interval,
)
}

internal fun tracking(eventName: String, params: IMap<String, Any>?) {
internal fun tracking(eventName: String, params: IMap<String, Any?>?) {
val paramsAsString = jsonEncoder.encode(params)
ioImplementations.threading?.async(ThreadingType.main) {
ioImplementations.tracking?.log(eventName, paramsAsString)
Expand Down Expand Up @@ -2326,10 +2335,14 @@ open class StateManagerAdaptor(
if (placeOrderRecord != null) {
val interval = Clock.System.now().toEpochMilliseconds()
.toDouble() - placeOrderRecord.timestampInMilliseconds
val extraParams = ParsingHelper.merge(
trackingParams(interval),
fromSlTpDialogParams(placeOrderRecord.fromSlTpDialog),
)
tracking(
AnalyticsEvent.TradePlaceOrderConfirmed.rawValue,
ParsingHelper.merge(
trackingParams(interval),
extraParams,
orderAnalyticsPayload,
)?.toIMap(),
)
Expand All @@ -2342,10 +2355,14 @@ open class StateManagerAdaptor(
if (cancelOrderRecord != null) {
val interval = Clock.System.now().toEpochMilliseconds()
.toDouble() - cancelOrderRecord.timestampInMilliseconds
val extraParams = ParsingHelper.merge(
trackingParams(interval),
fromSlTpDialogParams(cancelOrderRecord.fromSlTpDialog),
)
tracking(
AnalyticsEvent.TradeCancelOrderConfirmed.rawValue,
ParsingHelper.merge(
trackingParams(interval),
extraParams,
orderAnalyticsPayload,
)?.toIMap(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import exchange.dydx.abacus.protocols.TransactionCallback
import exchange.dydx.abacus.protocols.TransactionType
import exchange.dydx.abacus.protocols.run
import exchange.dydx.abacus.responses.ParsingError
import exchange.dydx.abacus.responses.ParsingErrorType
import exchange.dydx.abacus.responses.ParsingException
import exchange.dydx.abacus.state.app.adaptors.V4TransactionErrors
import exchange.dydx.abacus.state.manager.configs.V4StateManagerConfigs
import exchange.dydx.abacus.state.model.TransferInputField
Expand Down Expand Up @@ -1000,11 +1002,15 @@ class V4StateManagerAdaptor(
callback: TransactionCallback,
payload: HumanReadablePlaceOrderPayload,
analyticsPayload: IMap<String, Any>?,
uiClickTimeMs: Double,
isTriggerOrder: Boolean = false,
): HumanReadablePlaceOrderPayload {
val clientId = payload.clientId
val string = Json.encodeToString(payload)
val uiClickTimeMs = trackOrderClick(analyticsPayload)
val marketId = payload.marketId

val position = stateMachine.state?.subaccount(subaccountNumber)?.openPositions?.find { it.id == marketId }
val positionSize = position?.size?.current

stopWatchingLastOrder()

Expand All @@ -1019,6 +1025,7 @@ class V4StateManagerAdaptor(
subaccountNumber,
clientId,
submitTimeMs,
fromSlTpDialog = isTriggerOrder,
),
)
}
Expand All @@ -1040,6 +1047,8 @@ class V4StateManagerAdaptor(
callback,
if (isTriggerOrder) {
HumanReadableTriggerOrdersPayload(
marketId,
positionSize,
listOf(payload),
emptyList(),
)
Expand All @@ -1056,15 +1065,19 @@ class V4StateManagerAdaptor(

private fun submitCancelOrder(
orderId: String,
marketId: String,
callback: TransactionCallback,
payload: HumanReadableCancelOrderPayload,
analyticsPayload: IMap<String, Any>?,
uiClickTimeMs: Double,
isTriggerOrder: Boolean = false,
) {
val clientId = payload.clientId
val string = Json.encodeToString(payload)

val uiClickTimeMs = trackOrderClick(analyticsPayload, isCancel = true)
val position = stateMachine.state?.subaccount(subaccountNumber)?.openPositions?.find { it.id == marketId }
val positionSize = position?.size?.current

val isShortTermOrder = payload.orderFlags == 0

stopWatchingLastOrder()
Expand All @@ -1080,6 +1093,7 @@ class V4StateManagerAdaptor(
subaccountNumber,
clientId,
submitTimeMs,
fromSlTpDialog = isTriggerOrder,
),
)
}
Expand All @@ -1101,6 +1115,8 @@ class V4StateManagerAdaptor(
callback,
if (isTriggerOrder) {
HumanReadableTriggerOrdersPayload(
marketId,
positionSize,
emptyList(),
listOf(payload),
)
Expand All @@ -1114,12 +1130,12 @@ class V4StateManagerAdaptor(
}

private fun trackOrderClick(
analyticsPayload: IMap<String, Any>?,
isCancel: Boolean = false
analyticsPayload: IMap<String, Any?>?,
analyticsEvent: AnalyticsEvent,
): Double {
val uiClickTimeMs = Clock.System.now().toEpochMilliseconds().toDouble()
tracking(
if (isCancel) AnalyticsEvent.TradeCancelOrderClick.rawValue else AnalyticsEvent.TradePlaceOrderClick.rawValue,
analyticsEvent.rawValue,
analyticsPayload,
)
return uiClickTimeMs
Expand All @@ -1135,8 +1151,7 @@ class V4StateManagerAdaptor(

tracking(
if (isCancel) AnalyticsEvent.TradeCancelOrder.rawValue else AnalyticsEvent.TradePlaceOrder.rawValue,
ParsingHelper.merge(uiTrackingParams(uiDelayTimeMs), analyticsPayload)
?.toIMap(),
ParsingHelper.merge(uiTrackingParams(uiDelayTimeMs), analyticsPayload)?.toIMap(),
)

return submitTimeMs
Expand All @@ -1163,59 +1178,86 @@ class V4StateManagerAdaptor(
override fun commitPlaceOrder(callback: TransactionCallback): HumanReadablePlaceOrderPayload {
val payload = placeOrderPayload()
val midMarketPrice = stateMachine.state?.marketOrderbook(payload.marketId)?.midPrice
val analyticsPayload = analyticsUtils.placeOrderAnalyticsPayload(payload, midMarketPrice)
return submitPlaceOrder(callback, payload, analyticsPayload)
val analyticsPayload = analyticsUtils.placeOrderAnalyticsPayload(payload, midMarketPrice, fromSlTpDialog = false, isClosePosition = false)
val uiClickTimeMs = trackOrderClick(analyticsPayload, AnalyticsEvent.TradePlaceOrderClick)

return submitPlaceOrder(callback, payload, analyticsPayload, uiClickTimeMs)
}

override fun commitClosePosition(callback: TransactionCallback): HumanReadablePlaceOrderPayload {
val payload = closePositionPayload()
val midMarketPrice = stateMachine.state?.marketOrderbook(payload.marketId)?.midPrice
val analyticsPayload = analyticsUtils.placeOrderAnalyticsPayload(payload, midMarketPrice, true)
return submitPlaceOrder(callback, payload, analyticsPayload)
val analyticsPayload = analyticsUtils.placeOrderAnalyticsPayload(payload, midMarketPrice, fromSlTpDialog = false, isClosePosition = true)
val uiClickTimeMs = trackOrderClick(analyticsPayload, AnalyticsEvent.TradePlaceOrderClick)

return submitPlaceOrder(callback, payload, analyticsPayload, uiClickTimeMs)
}

override fun cancelOrder(orderId: String, callback: TransactionCallback) {
val payload = cancelOrderPayload(orderId)
val subaccount = stateMachine.state?.subaccount(subaccountNumber)
val existingOrder = subaccount?.orders?.firstOrNull { it.id == orderId }
val existingOrder = subaccount?.orders?.firstOrNull { it.id == orderId } ?: throw ParsingException(
ParsingErrorType.MissingRequiredData,
"no existing order to be cancelled for $orderId",
)
val marketId = existingOrder.marketId
val analyticsPayload = analyticsUtils.cancelOrderAnalyticsPayload(
payload,
existingOrder,
fromSlTpDialog = false,
)
val uiClickTimeMs = trackOrderClick(analyticsPayload, AnalyticsEvent.TradeCancelOrderClick)

submitCancelOrder(orderId, callback, payload, analyticsPayload)
submitCancelOrder(orderId, marketId, callback, payload, analyticsPayload, uiClickTimeMs)
}

override fun commitTriggerOrders(callback: TransactionCallback): HumanReadableTriggerOrdersPayload {
val payloads = triggerOrdersPayload()
val payload = triggerOrdersPayload()

// this is a diff payload that summarizes the actions to be taken
val analyticsPayload = analyticsUtils.triggerOrdersAnalyticsPayload(payload)
val uiClickTimeMs = trackOrderClick(analyticsPayload, AnalyticsEvent.TriggerOrderClick)

payloads.cancelOrderPayloads.forEach { payload ->
payload.cancelOrderPayloads.forEach { cancelPayload ->
val subaccount = stateMachine.state?.subaccount(subaccountNumber)
val existingOrder = subaccount?.orders?.firstOrNull { it.id == payload.orderId }
val analyticsPayload = analyticsUtils.cancelOrderAnalyticsPayload(
payload,
val existingOrder = subaccount?.orders?.firstOrNull { it.id == cancelPayload.orderId }
?: throw ParsingException(
ParsingErrorType.MissingRequiredData,
"no existing order to be cancelled for $cancelPayload.orderId",
)
val marketId = existingOrder.marketId
val cancelOrderAnalyticsPayload = analyticsUtils.cancelOrderAnalyticsPayload(
cancelPayload,
existingOrder,
fromSlTpDialog = true,
)
submitCancelOrder(
cancelPayload.orderId,
marketId,
callback,
cancelPayload,
cancelOrderAnalyticsPayload,
uiClickTimeMs,
true,
)
submitCancelOrder(payload.orderId, callback, payload, analyticsPayload, true)
}

payloads.placeOrderPayloads.forEach { payload ->
val midMarketPrice = stateMachine.state?.marketOrderbook(payload.marketId)?.midPrice
val analyticsPayload = analyticsUtils.placeOrderAnalyticsPayload(
payload,
payload.placeOrderPayloads.forEach { placePayload ->
val midMarketPrice = stateMachine.state?.marketOrderbook(placePayload.marketId)?.midPrice
val placeOrderAnalyticsPayload = analyticsUtils.placeOrderAnalyticsPayload(
placePayload,
midMarketPrice,
isClosePosition = false,
fromSlTpDialog = true,
isClosePosition = false,
)
submitPlaceOrder(callback, payload, analyticsPayload, true)
submitPlaceOrder(callback, placePayload, placeOrderAnalyticsPayload, uiClickTimeMs, true)
}

if (payloads.cancelOrderPayloads.isEmpty() && payloads.placeOrderPayloads.isEmpty()) {
send(null, callback, payloads)
if (payload.cancelOrderPayloads.isEmpty() && payload.placeOrderPayloads.isEmpty()) {
send(null, callback, payload)
}

return payloads
return payload
}

override fun commitTransfer(callback: TransactionCallback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ data class PlaceOrderRecord(
val subaccountNumber: Int,
val clientId: Int,
val timestampInMilliseconds: Double,
val fromSlTpDialog: Boolean,
)

data class CancelOrderRecord(
val subaccountNumber: Int,
val clientId: Int,
val timestampInMilliseconds: Double,
val fromSlTpDialog: Boolean,
)

@JsExport
Expand Down Expand Up @@ -61,6 +63,7 @@ data class HumanReadablePlaceOrderPayload(
@Serializable
data class HumanReadableCancelOrderPayload(
val subaccountNumber: Int,
val type: String,
val orderId: String,
val clientId: Int,
val orderFlags: Int,
Expand All @@ -72,6 +75,8 @@ data class HumanReadableCancelOrderPayload(
@JsExport
@Serializable
data class HumanReadableTriggerOrdersPayload(
val marketId: String,
val positionSize: Double?,
val placeOrderPayloads: List<HumanReadablePlaceOrderPayload>,
val cancelOrderPayloads: List<HumanReadableCancelOrderPayload>,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ internal open class NetworkSupervisor(
}
}

internal fun tracking(eventName: String, params: IMap<String, Any>?) {
internal fun tracking(eventName: String, params: IMap<String, Any?>?) {
val paramsAsString = helper.jsonEncoder.encode(params)
helper.ioImplementations.threading?.async(ThreadingType.main) {
helper.ioImplementations.tracking?.log(eventName, paramsAsString)
Expand Down
Loading

0 comments on commit b5703c7

Please sign in to comment.