| 1 | [ |
| 2 | "autonomous agent", |
| 3 | { |
| 4 | "getters": "{ |
| 5 | |
| 6 | $get_leverages = () => [2, 5, 10, 20, 50, 100]; |
| 7 | |
| 8 | $singularity_threshold = 0.01; |
| 9 | |
| 10 | $trade_merge_period = 1; |
| 11 | |
| 12 | $update_recent_data = ($recent, $p, $final_p, $trigger_initial_address, $tax_token, $traded_amount, $paid_tax, $period_length) => { |
| 13 | $period_start_ts = floor(timestamp / $period_length) * $period_length; |
| 14 | $pmin = min($p, $final_p); |
| 15 | $pmax = max($p, $final_p); |
| 16 | if (+$recent.current.start_ts < $period_start_ts){ |
| 17 | $recent.prev = $recent.current; |
| 18 | $recent.current = {start_ts: $period_start_ts, pmin: $pmin, pmax: $pmax}; |
| 19 | } |
| 20 | else{ |
| 21 | $recent.current.pmin = min($recent.current.pmin, $pmin); |
| 22 | $recent.current.pmax = max($recent.current.pmax, $pmax); |
| 23 | } |
| 24 | if ($recent.last_trade AND $recent.last_trade.address == $trigger_initial_address AND $recent.last_ts >= timestamp - $trade_merge_period){ |
| 25 | $recent.last_trade.pmin = min($recent.last_trade.pmin, $pmin); |
| 26 | $recent.last_trade.pmax = max($recent.last_trade.pmax, $pmax); |
| 27 | $recent.last_trade.amounts[$tax_token] = $recent.last_trade.amounts[$tax_token] + $traded_amount; |
| 28 | $recent.last_trade.paid_taxes[$tax_token] = $recent.last_trade.paid_taxes[$tax_token] + $paid_tax; |
| 29 | } |
| 30 | else{ |
| 31 | $amounts = {x:0, y:0}; |
| 32 | $paid_taxes = {x:0, y:0}; |
| 33 | $amounts[$tax_token] = $traded_amount; |
| 34 | $paid_taxes[$tax_token] = $paid_tax; |
| 35 | $recent.last_trade = { |
| 36 | address: $trigger_initial_address, |
| 37 | pmin: $pmin, |
| 38 | pmax: $pmax, |
| 39 | amounts: $amounts, |
| 40 | paid_taxes: $paid_taxes, |
| 41 | }; |
| 42 | } |
| 43 | $recent.last_ts = timestamp; |
| 44 | }; |
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | $get_final_xy = ($X, $Y, $P, $final_P, $X0, $Y0, $pool_props, $inverted) => { |
| 50 | require($final_P >= $P, "not selling Y"); |
| 51 | |
| 52 | $a = $inverted ? $pool_props.beta : $pool_props.alpha; |
| 53 | $b = 1 - $a; |
| 54 | $final_X = ($X + $X0) * ($P/$final_P)^$b - $X0; |
| 55 | $final_Y = $b/$a * $final_P * ($final_X + $X0) - $Y0; |
| 56 | $deltaX = $X - $final_X; |
| 57 | require($final_X >= 0, "bad final_X " || $final_X); |
| 58 | require($final_Y >= 0, "bad final_Y " || $final_Y); |
| 59 | require($deltaX >= 0, "bad deltaX " || $deltaX); |
| 60 | { |
| 61 | X: $final_X, |
| 62 | Y: $final_Y, |
| 63 | } |
| 64 | }; |
| 65 | |
| 66 | |
| 67 | $get_final_xy_along_x = ($X, $Y, $P, $final_P, $pool_props, $inverted) => { |
| 68 | require($final_P >= $P, "along X: not selling Y"); |
| 69 | $a = $inverted ? $pool_props.beta : $pool_props.alpha; |
| 70 | $b = 1 - $a; |
| 71 | $final_X = $X * ($P/$final_P)^($b*$pool_props.Lambda); |
| 72 | $final_Y = $b/$a * $final_P * $final_X; |
| 73 | { |
| 74 | X: $final_X, |
| 75 | Y: $final_Y, |
| 76 | } |
| 77 | }; |
| 78 | |
| 79 | |
| 80 | $get_final_xy_along_y = ($X, $Y, $P, $final_P, $pool_props, $inverted) => { |
| 81 | require($final_P >= $P, "along Y: not selling Y"); |
| 82 | $a = $inverted ? $pool_props.beta : $pool_props.alpha; |
| 83 | $b = 1 - $a; |
| 84 | $final_Y = $Y * ($final_P/$P)^($a*$pool_props.Lambda); |
| 85 | $final_X = $a/$b * $final_Y / $final_P; |
| 86 | { |
| 87 | X: $final_X, |
| 88 | Y: $final_Y, |
| 89 | } |
| 90 | }; |
| 91 | |
| 92 | |
| 93 | |
| 94 | |
| 95 | $add_net_balance_without_changing_price = ($balances, $profits, $side, $amount, $Lambda) => { |
| 96 | if ($amount == 0) |
| 97 | return; |
| 98 | if ($Lambda == 1){ |
| 99 | $profits[$side] = $profits[$side] + $amount; |
| 100 | return; |
| 101 | } |
| 102 | |
| 103 | $opposite = $side == 'x' ? 'y' : 'x'; |
| 104 | $side_n = $side || 'n'; |
| 105 | $opposite_n = $opposite || 'n'; |
| 106 | |
| 107 | $Xn = $balances[$side_n]; |
| 108 | $Yn = $balances[$opposite_n]; |
| 109 | $X = $balances[$side]; |
| 110 | $Y = $balances[$opposite]; |
| 111 | |
| 112 | $underleveraged = $Xn > ceil($X/$Lambda); |
| 113 | $delta_Xn = $amount; |
| 114 | |
| 115 | |
| 116 | if (!$underleveraged){ |
| 117 | |
| 118 | $full_delta_Y = $Y * $delta_Xn/$Xn; |
| 119 | if ($Y + $full_delta_Y > $Yn * $Lambda){ |
| 120 | |
| 121 | $ratio = $Yn * $Lambda / $Y - 1; |
| 122 | $delta_X = $ratio * $X; |
| 123 | $delta_Y = $ratio * $Y; |
| 124 | } |
| 125 | else{ |
| 126 | $delta_X = $delta_Xn * $Lambda; |
| 127 | $delta_Y = $full_delta_Y; |
| 128 | } |
| 129 | } |
| 130 | else{ |
| 131 | $delta_X = 0; |
| 132 | $delta_Y = 0; |
| 133 | } |
| 134 | |
| 135 | |
| 136 | $balances[$side_n] = $balances[$side_n] + $delta_Xn; |
| 137 | |
| 138 | $balances[$side] = $balances[$side] + $delta_X; |
| 139 | $balances[$opposite] = $balances[$opposite] + $delta_Y; |
| 140 | }; |
| 141 | |
| 142 | |
| 143 | |
| 144 | |
| 145 | |
| 146 | $pow = ($precomputed, $power) => { |
| 147 | require(typeof($precomputed[$power]) == 'number', "no precomputed power " || $power); |
| 148 | $precomputed[$power] |
| 149 | }; |
| 150 | $precompute = $v => { |
| 151 | $pre = {}; |
| 152 | $pre['2'] = $v * $v; |
| 153 | $pre['5'] = $pre['2'] * $pre['2'] * $v; |
| 154 | $pre['10'] = $pre['5'] * $pre['5']; |
| 155 | $pre['20'] = $pre['10'] * $pre['10']; |
| 156 | $pre['50'] = $pre['20'] * $pre['20'] * $pre['10']; |
| 157 | $pre['100'] = $pre['50'] * $pre['50']; |
| 158 | $pre |
| 159 | }; |
| 160 | |
| 161 | $update_leveraged_balances = ($l_balances, $P, $final_P, $inverted) => { |
| 162 | $ratio = $final_P/$P; |
| 163 | $ratio_powers = $precompute($ratio); |
| 164 | |
| 165 | $totals = { |
| 166 | delta_XL: 0, |
| 167 | delta_YL: 0, |
| 168 | XL_denom: 0, |
| 169 | YL_denom: 0, |
| 170 | }; |
| 171 | foreach($get_leverages(), 6, ($L) => { |
| 172 | $allyL = $inverted ? -$L : $L; |
| 173 | $balance = $l_balances[$allyL||'x'].balance; |
| 174 | $obalance = $l_balances[-$allyL||'x'].balance; |
| 175 | if (!$balance AND !$obalance) |
| 176 | return; |
| 177 | $ratio_L1 = $pow($ratio_powers, $L) / $ratio; |
| 178 | $debt_ratio = ($L-1)/$L; |
| 179 | if ($balance) { |
| 180 | $delta_XL_balance = $balance * ($ratio_L1 - 1); |
| 181 | $new_XL_balance = $balance + $delta_XL_balance; |
| 182 | $l_balances[$allyL||'x'].balance = $new_XL_balance; |
| 183 | $delta_YL_balance = -($new_XL_balance * $final_P - $balance * $P) * $debt_ratio; |
| 184 | $totals.delta_XL = $totals.delta_XL + $delta_XL_balance; |
| 185 | $totals.delta_YL = $totals.delta_YL + $delta_YL_balance; |
| 186 | $totals.XL_denom = $totals.XL_denom + $new_XL_balance * ($L-1); |
| 187 | } |
| 188 | if ($obalance) { |
| 189 | $delta_YL_obalance = $obalance * (1/$ratio_L1 - 1); |
| 190 | $new_YL_obalance = $obalance + $delta_YL_obalance; |
| 191 | $l_balances[-$allyL||'x'].balance = $new_YL_obalance; |
| 192 | $delta_XL_obalance = -($new_YL_obalance / $final_P - $obalance / $P) * $debt_ratio; |
| 193 | $totals.delta_YL = $totals.delta_YL + $delta_YL_obalance; |
| 194 | $totals.delta_XL = $totals.delta_XL + $delta_XL_obalance; |
| 195 | $totals.YL_denom = $totals.YL_denom + $new_YL_obalance * ($L-1); |
| 196 | } |
| 197 | }); |
| 198 | $totals |
| 199 | }; |
| 200 | |
| 201 | |
| 202 | |
| 203 | $swap = ($balances, $l_balances, $profits, $recent, $x0, $y0, $y_in, $in_delta_Yn, $final_P, $received_amount_Y, $min_amount_out, $trigger_initial_address, $pool_props) => { |
| 204 | |
| 205 | require(!$in_delta_Yn, "no delta Yn please, this is swap by P"); |
| 206 | |
| 207 | $alpha = $pool_props.alpha; |
| 208 | $beta = $pool_props.beta; |
| 209 | $Lambda = $pool_props.Lambda; |
| 210 | |
| 211 | $xn = $balances.xn; |
| 212 | $yn = $balances.yn; |
| 213 | $x = $balances.x; |
| 214 | $y = $balances.y; |
| 215 | |
| 216 | if ($y_in){ |
| 217 | $inverted = false; |
| 218 | $X = $x; |
| 219 | $Y = $y; |
| 220 | $Xn = $xn; |
| 221 | $Yn = $yn; |
| 222 | $X0 = $x0; |
| 223 | $Y0 = $y0; |
| 224 | $a = $alpha; |
| 225 | $b = $beta; |
| 226 | $in_token = 'y'; |
| 227 | $out_token = 'x'; |
| 228 | } |
| 229 | else{ |
| 230 | $inverted = true; |
| 231 | $X = $y; |
| 232 | $Y = $x; |
| 233 | $Xn = $yn; |
| 234 | $Yn = $xn; |
| 235 | $X0 = $y0; |
| 236 | $Y0 = $x0; |
| 237 | $a = $beta; |
| 238 | $b = $alpha; |
| 239 | $in_token = 'x'; |
| 240 | $out_token = 'y'; |
| 241 | } |
| 242 | $P = $a/$b * ($Y + $Y0) / ($X + $X0); |
| 243 | require($final_P > $P, "price should increase, current " || $P || ", target " || $final_P); |
| 244 | |
| 245 | if ($Lambda > 1){ |
| 246 | $underleveraged = $Xn > ceil($X/$Lambda); |
| 247 | } |
| 248 | |
| 249 | if ($Lambda == 1){ |
| 250 | $final = $get_final_xy($X, $Y, $P, $final_P, $X0, $Y0, $pool_props, $inverted); |
| 251 | $final_X = $final.X; |
| 252 | $final_Y = $final.Y; |
| 253 | $final_Xn = $final_X; |
| 254 | $final_Yn = $final_Y; |
| 255 | } |
| 256 | else if (!$underleveraged){ |
| 257 | $final = $get_final_xy_along_x($X, $Y, $P, $final_P, $pool_props, $inverted); |
| 258 | $final_X = $final.X; |
| 259 | $final_Y = $final.Y; |
| 260 | $final_Xn = $final_X/$Lambda; |
| 261 | $delta_Y = $final_Y - $Y; |
| 262 | $delta_Yn = -$a/($b*$Lambda-1)*$delta_Y; |
| 263 | $final_Yn = $Yn + $delta_Yn; |
| 264 | require($final_Yn > 0, "fully leveraged: negative final_Yn="||$final_Yn); |
| 265 | } |
| 266 | else if ($underleveraged){ |
| 267 | $inflection_P = $P * ( $Lambda/($Lambda-1) * ($b + ($a * $Lambda - 1) * $Xn/$X) )^(1/($a*$Lambda-1)); |
| 268 | require($inflection_P > 0, "negative inflection_P="||$inflection_P); |
| 269 | $inflected = $final_P > $inflection_P; |
| 270 | |
| 271 | $final_P1 = $inflected ? $inflection_P : $final_P; |
| 272 | $final1 = $get_final_xy_along_y($X, $Y, $P, $final_P1, $pool_props, $inverted); |
| 273 | $final_X1 = $final1.X; |
| 274 | $final_Y1 = $final1.Y; |
| 275 | $final_Yn1 = $final_Y1 / $Lambda; |
| 276 | $delta_X1 = $final_X1 - $X; |
| 277 | $delta_Xn1 = -$b/($a*$Lambda-1) * $delta_X1; |
| 278 | $final_Xn1 = $Xn + $delta_Xn1; |
| 279 | require($final_Xn1 > 0, "negative final_Xn1="||$final_Xn1); |
| 280 | if ($inflected){ |
| 281 | |
| 282 | log('inflected at price', $inflection_P); |
| 283 | $final = $get_final_xy_along_x($final_X1, $final_Y1, $final_P1, $final_P, $pool_props, $inverted); |
| 284 | $final_X = $final.X; |
| 285 | $final_Y = $final.Y; |
| 286 | $final_Xn = $final_X/$Lambda; |
| 287 | $delta_Y2 = $final_Y - $final_Y1; |
| 288 | $delta_Yn2 = -$a/($b*$Lambda-1)*$delta_Y2; |
| 289 | $final_Yn = $final_Yn1 + $delta_Yn2; |
| 290 | require($final_Xn > 0, "negative final_Xn="||$final_Xn); |
| 291 | require($final_Xn <= $final_Xn1, "Xn didn't decrease"); |
| 292 | } |
| 293 | else{ |
| 294 | $final_X = $final_X1; |
| 295 | $final_Y = $final_Y1; |
| 296 | $final_Xn = $final_Xn1; |
| 297 | $final_Yn = $final_Yn1; |
| 298 | } |
| 299 | } |
| 300 | else |
| 301 | bounce("???"); |
| 302 | |
| 303 | $balances.x = $y_in ? $final_X : $final_Y; |
| 304 | $balances.y = $y_in ? $final_Y : $final_X; |
| 305 | $balances.xn = $y_in ? $final_Xn : $final_Yn; |
| 306 | $balances.yn = $y_in ? $final_Yn : $final_Xn; |
| 307 | |
| 308 | |
| 309 | |
| 310 | $totals = $update_leveraged_balances($l_balances, $P, $final_P, $inverted); |
| 311 | |
| 312 | $amount_X_exact = -($final_Xn - $Xn + $totals.delta_XL); |
| 313 | $amount_Y_exact = $final_Yn - $Yn + $totals.delta_YL; |
| 314 | $amount_Y = ceil($amount_Y_exact); |
| 315 | if ($received_amount_Y >= 0) |
| 316 | require($received_amount_Y >= $amount_Y, "expected " || $amount_Y || ", received " || $received_amount_Y); |
| 317 | require($amount_X_exact >= 0, "to pay " || $amount_X_exact); |
| 318 | $change = $received_amount_Y - $amount_Y; |
| 319 | |
| 320 | $denom = 1 - $totals.XL_denom/$b/($final_X+$X0) - $totals.YL_denom/$a/($final_Y+$Y0); |
| 321 | |
| 322 | require($denom >= $singularity_threshold, "too close to the singularity point, denom="||$denom||", need more liquidity in order to swap this amount"); |
| 323 | |
| 324 | |
| 325 | if ($recent.last_trade AND $recent.last_trade.address == $trigger_initial_address AND $recent.last_ts >= timestamp - $trade_merge_period){ |
| 326 | $min_P = min($P, $y_in ? $recent.last_trade.pmin : 1/$recent.last_trade.pmax); |
| 327 | $max_P = max($final_P, $y_in ? $recent.last_trade.pmax : 1/$recent.last_trade.pmin); |
| 328 | $recent_traded_amount = $recent.last_trade.amounts[$out_token]; |
| 329 | $recent_paid_tax = $recent.last_trade.paid_taxes[$out_token]; |
| 330 | } |
| 331 | else{ |
| 332 | $min_P = $P; |
| 333 | $max_P = $final_P; |
| 334 | } |
| 335 | $arb_profit_in_Y = ($max_P - $min_P) * ($recent_traded_amount + $amount_X_exact) / 2; |
| 336 | $arb_profit_in_X = $arb_profit_in_Y / $min_P; |
| 337 | $arb_profit_tax = $arb_profit_in_X * $pool_props.arb_profit_tax - $recent_paid_tax; |
| 338 | require($arb_profit_tax >= 0, "negative arb profit tax"); |
| 339 | |
| 340 | $swap_fee = $amount_X_exact * $pool_props.swap_fee; |
| 341 | $fee = $arb_profit_tax + $swap_fee; |
| 342 | |
| 343 | $net_amount_X_exact = $amount_X_exact - $fee; |
| 344 | $net_amount_X = floor($net_amount_X_exact); |
| 345 | $rounding_fee_X = $net_amount_X_exact - $net_amount_X; |
| 346 | $rounding_fee_Y = $amount_Y - $amount_Y_exact; |
| 347 | $total_fee = $fee + $rounding_fee_X; |
| 348 | |
| 349 | $avg_price = $amount_Y / $net_amount_X; |
| 350 | require($avg_price > $P, "avg price below initial price"); |
| 351 | |
| 352 | if ($min_amount_out) |
| 353 | require($net_amount_X >= $min_amount_out, "output amount " || $net_amount_X || " would be less than the expected minimum " || $min_amount_out); |
| 354 | |
| 355 | |
| 356 | $fees = { |
| 357 | X: $total_fee, |
| 358 | Y: $rounding_fee_Y, |
| 359 | }; |
| 360 | |
| 361 | |
| 362 | $add_net_balance_without_changing_price($balances, $profits, $out_token, $fees.X, $Lambda); |
| 363 | $add_net_balance_without_changing_price($balances, $profits, $in_token, $fees.Y, $Lambda); |
| 364 | |
| 365 | |
| 366 | $update_recent_data($recent, $inverted ? 1/$P : $P, $inverted ? 1/$final_P : $final_P, $trigger_initial_address, $out_token, $amount_X_exact, $arb_profit_tax, $pool_props.period_length); |
| 367 | |
| 368 | $event = json_stringify({ |
| 369 | type: 'swap', |
| 370 | direction: $y_in ? 'y2x' : 'x2y', |
| 371 | in: $amount_Y, |
| 372 | out: $net_amount_X, |
| 373 | swap_fee: $swap_fee, |
| 374 | arb_profit_tax: $arb_profit_tax, |
| 375 | total_fee: $total_fee, |
| 376 | }); |
| 377 | |
| 378 | { |
| 379 | net_amount_X: $net_amount_X, |
| 380 | amount_Y: $amount_Y, |
| 381 | swap_fee: $swap_fee, |
| 382 | arb_profit_tax: $arb_profit_tax, |
| 383 | total_fee: $total_fee, |
| 384 | fees: $fees, |
| 385 | change: $change, |
| 386 | initial_price: $P, |
| 387 | final_price: $final_P, |
| 388 | event: $event, |
| 389 | } |
| 390 | }; |
| 391 | |
| 392 | |
| 393 | |
| 394 | |
| 395 | }", |
| 396 | "messages": [ |
| 397 | { |
| 398 | "app": "state", |
| 399 | "state": "{ |
| 400 | $A = $swap(); |
| 401 | bounce("library only"); |
| 402 | }" |
| 403 | } |
| 404 | ] |
| 405 | } |
| 406 | ] |