ladder orders!
This commit is contained in:
@@ -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>
|
<v-icon v-if="icon" :icon="icon" :color="color"></v-icon> {{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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 <route-price :inverted="routeInverted(os.route)" :route="os.route" class="text-green"/>
|
Current price <route-price :inverted="routeInverted(os.route)" :route="os.route" class="text-green"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user