ladder orders!

This commit is contained in:
Tim Olson
2023-11-27 17:00:54 -04:00
parent d2db5dc4f7
commit 1dff2da3fe
8 changed files with 145 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
<template> <template>
<v-btn variant="outlined"> <v-btn variant="outlined">
<v-icon v-if="icon" :icon="icon" :color="color"></v-icon>&nbsp; <v-icon v-if="icon" :icon="icon" :color="color"></v-icon>&nbsp{{text}}
<slot/> <slot/>
</v-btn> </v-btn>
</template> </template>
@@ -10,7 +10,7 @@ import {useStore} from "@/store/store";
import {useAttrs} from "vue"; import {useAttrs} from "vue";
const s = useStore() const s = useStore()
const props = defineProps(['icon', 'color']) const props = defineProps(['icon', 'color', 'text'])
const attrs = useAttrs() const attrs = useAttrs()
</script> </script>

View File

@@ -1,22 +1,96 @@
<template> <template>
<order :tranches="buildTranches"> <order title="Ladder" subtitle="Multiple price levels" :tranches="buildTranches" :valid="validOrder">
<limit-price :required="true" label=""/> <limit-price :required="true" label="start price" :show-price="false"/>
<limit-price :show-price="false" store-var="limitPrice2" :required="true" label=""/> <limit-price store-var="limitPrice2" :required="true" label="end price"/>
<v-text-field label='Parts' type="number" step="1" aria-valuemin="0" min="1" variant="outlined"
v-model="num" />
<v-text-field label='Skew' type="number" step="10" aria-valuemin="0" min="-100" max="100" variant="outlined"
v-model="skew" clearable @click:clear="skew=0" suffix="%"/>
<!-- todo deadline -->
<v-table>
<thead>
<tr><td>Fraction</td><td>Amount</td><td>Price</td></tr>
</thead>
<tbody>
<tr v-for="(r,i) in rungsFmt">
<td>{{(100*fractions[i]).toFixed(1)}}%</td>
<td>{{(amounts[i]).toPrecision(5)}} {{os.amountToken.symbol}}</td>
<td>{{r}}</td>
</tr>
</tbody>
</v-table>
</order> </order>
</template> </template>
<script setup> <script setup>
import {useStore} from "@/store/store"; import {useOrderStore} from "@/store/store";
import LimitPrice from "@/components/LimitPrice.vue"; import LimitPrice from "@/components/LimitPrice.vue";
import Order from "@/components/Order.vue"; import Order from "@/components/Order.vue";
import {computed, ref} from "vue";
import {newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js";
import {limitConstraint, maxFraction} from "@/orderbuild.js";
import {validateMax} from "@/validate.js";
const s = useStore() const os = useOrderStore()
const num = ref(3)
const skew = ref(0)
const rungs = computed(()=>{
const n = num.value;
const a = parseFloat(os.limitPrice);
const b = parseFloat(os.limitPrice2);
if( n < 1 || !a || !b ) return []
if( n === 1 ) return [(a+b)/2]
// num >= 2
const result = []
const delta = (b-a)/(n-1)
for( let i=0; i<n; i++ )
result.push(a+i*delta)
return result
})
const rungsFmt = computed(()=>{
return rungs.value.map((r)=>r.toPrecision(5)) // todo precisions
})
const fractions = computed(()=>{
const n = num.value
const s = skew.value / 100
const result = []
if( s === 1 ) {
result.push(1)
for( let i=1; i<n; i++ )
result.push(0)
}
else if( s === -1 ) {
for( let i=1; i<n; i++ )
result.push(0)
result.push(1)
}
else {
const mean = 1/n
for( let i=0; i<n; i++ )
result.push( mean * ( 1 - s * (2*i/(n-1)-1) ) )
}
return result
})
const amounts = computed( ()=>fractions.value.map((f)=>f*os.amount) )
function buildTranches() { function buildTranches() {
const ts = [] const ts = []
const n = num.value
const mf = Number(maxFraction)
for( let i=0; i<n; i++ ) {
// todo optional deadline
const cs = [limitConstraint(rungs.value[i])]
const fraction = Math.min(mf, Math.ceil(mf * fractions.value[i]) )
ts.push([fraction, cs])
}
return ts return ts
} }
function validOrder() {
return os.validOrder
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -7,7 +7,7 @@
{{ os.pairSymbol }} {{ os.pairSymbol }}
</v-btn> </v-btn>
</template> </template>
<template #details style="flex-direction: column-reverse"> <template v-if="showPrice" #details style="flex-direction: column-reverse">
<div> <div>
Current price&nbsp;<route-price :inverted="routeInverted(os.route)" :route="os.route" class="text-green"/> Current price&nbsp;<route-price :inverted="routeInverted(os.route)" :route="os.route" class="text-green"/>
</div> </div>

View File

@@ -1,15 +1,51 @@
<template> <template>
<v-btn prepend-icon="mdi-plus" text="New Order"/> <div class="d-inline-flex">
<btn icon="mdi-plus" color="green" @click="show=true">New Order</btn>
<v-dialog v-model="show" width="auto">
<v-card>
<v-card-item class="d-flex">
<v-card variant="elevated"
prepend-icon="mdi-clock-outline" title="DCA"
subtitle="Spread order across time"
text="The DCA order gives you a Dollar Cost Average (DCA) or a Time Weighted Average
Price (TWAP) by splitting the order amount into multiple parts executed at regular time intervals.">
<v-card-actions><btn icon="mdi-clock-outline" text="DCA" @click="$router.push('/twap')"/></v-card-actions>
</v-card>
<v-card variant="elevated"
prepend-icon="mdi-menu" title="Ladder"
subtitle="Multiple price levels"
text="The Ladder order helps catch wicks and other reversals by splitting the order amount across
multiple price levels. The amounts may be weighted towards one end of the ladder, to either have
a larger filled amount on a shallow reversal, or a larger amount at a better price that might not
be reached.">
<v-card-actions><btn icon="mdi-menu" text="Ladder" @click="$router.push('/ladder')"/></v-card-actions>
</v-card>
</v-card-item>
<v-card-item class="mb-3"><v-btn variant="outlined" prepend-icon="mdi-cancel" color="red" @click="show=false">Cancel</v-btn></v-card-item>
</v-card>
</v-dialog>
</div>
</template> </template>
<script setup> <script setup>
import {useStore} from "@/store/store"; import {useStore} from "@/store/store";
import {ref} from "vue";
import Btn from "@/components/Btn.vue";
const s = useStore() const s = useStore()
const show = ref(false)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@use "src/styles/vars" as *; @use "src/styles/vars" as *;
.v-card .v-card {
width: 20em;
margin: 2em;
}
</style> </style>

View File

@@ -9,8 +9,7 @@
</v-app-bar-title> </v-app-bar-title>
<v-btn icon="mdi-safe-square" color="grey-darken-2" text="Vault" @click="$router.push('/vault')"></v-btn> <v-btn icon="mdi-safe-square" color="grey-darken-2" text="Vault" @click="$router.push('/vault')"></v-btn>
<v-btn icon="mdi-swap-horizontal-circle-outline" text="New Order" @click="$router.push('/twap')"></v-btn> <v-btn icon="mdi-information-outline" text="Order Status" @click="$router.push('/orders')"></v-btn>
<v-btn icon="mdi-menu" text="Order Status" @click="$router.push('/orders')"></v-btn>
</v-app-bar> </v-app-bar>
</template> </template>

View File

@@ -2,16 +2,23 @@ import {routeInverted} from "@/misc.js";
import {newLimitConstraint, newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js"; import {newLimitConstraint, newTimeConstraint, TimeMode} from "@/blockchain/orderlib.js";
import {useOrderStore, useStore} from "@/store/store.js"; import {useOrderStore, useStore} from "@/store/store.js";
export function limitConstraint() {
const s = useStore() export const maxFraction = 65535n // by contract definition of uint16 fraction
if (!s.limitPrice)
return null
const route = s.route export function limitConstraint(price=null) {
const os = useOrderStore()
if( price === null ) {
price = os.limitPrice
if (!price)
return null
}
const route = os.route
const inverted = routeInverted(route) const inverted = routeInverted(route)
const isAbove = s.limitIsMinimum ^ inverted const isAbove = os.limitIsMinimum ^ inverted
const isRatio = false // todo ratios const isRatio = false // todo ratios
const decimals = 10 ** (s.tokenA.decimals - s.tokenB.decimals) const decimals = 10 ** (os.tokenA.decimals - os.tokenB.decimals)
const limit = inverted ? decimals / s.limitPrice : s.limitPrice / decimals const limit = inverted ? decimals / price : price / decimals
return newLimitConstraint(isAbove, isRatio, limit) return newLimitConstraint(isAbove, isRatio, limit)
} }
@@ -32,9 +39,8 @@ export function timesliceTranches() {
} else { } else {
window = Math.round(duration / n) window = Math.round(duration / n)
} }
const oneHundredPercent = 65535n // by contract definition of uint16 fraction const ceil = maxFraction % BigInt(n) ? 1n : 0n
const ceil = oneHundredPercent % BigInt(n) ? 1n : 0n const amtPerTranche = maxFraction / BigInt(n) + ceil
const amtPerTranche = oneHundredPercent / BigInt(n) + ceil
duration -= 15 // subtract 15 seconds so the last tranche completes before the deadline duration -= 15 // subtract 15 seconds so the last tranche completes before the deadline
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
const start = Math.floor(i * (duration / Math.max((n - 1), 1))) const start = Math.floor(i * (duration / Math.max((n - 1), 1)))

View File

@@ -28,7 +28,12 @@ const routes = [
// route level code-splitting // route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route // this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "ordersview" */ '@/views/TwapView.vue'), component: () => import(/* webpackChunkName: "twap" */ '@/components/TimedOrder.vue'),
},
{
path: '/ladder',
name: 'Ladder',
component: () => import(/* webpackChunkName: "ladder" */ '@/components/LadderOrder.vue'),
}, },
{ {
path: '/vault', path: '/vault',

View File

@@ -3,8 +3,8 @@
<needs-signer> <needs-signer>
<phone-card title="Orders"> <phone-card title="Orders">
<v-card-item> <v-card-item>
<btn icon="mdi-plus" color="green" class="mb-6" @click="$router.push('/twap')">New Dexorder</btn>
<orders/> <orders/>
<new-order class="ma-3"/>
</v-card-item> </v-card-item>
</phone-card> </phone-card>
</needs-signer> </needs-signer>
@@ -15,8 +15,8 @@
import Orders from "@/components/Orders.vue"; import Orders from "@/components/Orders.vue";
import NeedsSigner from "@/components/NeedsSigner.vue"; import NeedsSigner from "@/components/NeedsSigner.vue";
import NeedsProvider from "@/components/NeedsProvider.vue"; import NeedsProvider from "@/components/NeedsProvider.vue";
import Btn from "@/components/Btn.vue";
import PhoneCard from "@/components/PhoneCard.vue"; import PhoneCard from "@/components/PhoneCard.vue";
import NewOrder from "@/components/NewOrder.vue";
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">