diagonal order form
This commit is contained in:
@@ -1,23 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<order title="Diagonal" subtitle="Trade trends and channels" :tranches="buildTranches" :valid="validOrder">
|
<order title="Diagonal" subtitle="Trends and channels" :tranches="buildTranches" :valid="validOrder">
|
||||||
<!-- todo times -->
|
<!-- todo times -->
|
||||||
<span><i>Coming soon!</i></span>
|
<div class="title">Line Point A</div>
|
||||||
<!--
|
<v-divider/>
|
||||||
<limit-price :required="true" label="start price" :show-price="false"/>
|
<time-entry v-model="time1" class="mb-0" hide-details="true"/>
|
||||||
<limit-price store-var="limitPrice2" :required="true" label="end price"/>
|
<limit-price v-model="price1" label="Price A" :required="true"/>
|
||||||
<v-table>
|
<div class="title">Line Point B</div>
|
||||||
<thead>
|
<v-divider/>
|
||||||
<tr><td>Fraction</td><td>Amount</td><td>Price</td></tr>
|
<time-entry v-model="time2" hide-details="true"/>
|
||||||
</thead>
|
<limit-price v-model="price2" label="Price B" :required="true"/>
|
||||||
<tbody>
|
<div><i>Backend support for diagonal lines is coming soon...</i></div>
|
||||||
<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>
|
||||||
|
|
||||||
@@ -26,65 +18,22 @@ 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 {computed, ref} from "vue";
|
||||||
import {applyLimit} from "@/orderbuild.js";
|
import {applyLimit, applyLinePoints} from "@/orderbuild.js";
|
||||||
import {validateRequired, validateTranches} from "@/validate.js";
|
import {validateRequired, validateTranches} from "@/validate.js";
|
||||||
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||||
|
import TimeEntry from "@/components/TimeEntry.vue";
|
||||||
|
|
||||||
const os = useOrderStore()
|
const os = useOrderStore()
|
||||||
|
|
||||||
const skew = ref(0)
|
const time1 = ref(new Date())
|
||||||
const rungs = computed(()=>{
|
const price1 = ref(null)
|
||||||
if( !os.limitPrice || !os.limitPrice2 )
|
const time2 = ref(new Date())
|
||||||
return []
|
const price2 = ref(null)
|
||||||
const n = os.tranches;
|
|
||||||
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 = os.tranches
|
|
||||||
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.totalAmount) )
|
|
||||||
|
|
||||||
function buildTranches() {
|
function buildTranches() {
|
||||||
const ts = []
|
const t = newTranche();
|
||||||
const n = os.tranches
|
applyLinePoints(t, time1.value, price1.value, time2.value, price2.value)
|
||||||
for( let i=0; i<n; i++ ) {
|
return [t]
|
||||||
// todo optional deadline
|
|
||||||
const fraction = Math.min(MAX_FRACTION, Math.ceil(MAX_FRACTION * fractions.value[i]) )
|
|
||||||
const tranche = newTranche({fraction})
|
|
||||||
applyLimit(tranche, rungs.value[i])
|
|
||||||
ts.push(tranche)
|
|
||||||
}
|
|
||||||
return ts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validOrder() {
|
function validOrder() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<order title="Ladder" subtitle="Multiple price levels" :tranches="buildTranches" :valid="validOrder">
|
<order title="Ladder" subtitle="Multiple price levels" :tranches="buildTranches" :valid="validOrder">
|
||||||
<limit-price :required="true" label="start price" :show-price="false"/>
|
<limit-price v-model="os.limitPrice" :required="true" label="start price" :show-price="false"/>
|
||||||
<limit-price store-var="limitPrice2" :required="true" label="end price"/>
|
<limit-price v-model="os.limitPrice2" :required="true" label="end price"/>
|
||||||
<v-text-field label="Tranches" type="number" variant="outlined" aria-valuemin="1" min="1" max="255"
|
<v-text-field label="Tranches" type="number" variant="outlined" aria-valuemin="1" min="1" max="255"
|
||||||
v-model="os.tranches" :rules="[validateRequired,validateTranches]">
|
v-model="os.tranches" :rules="[validateRequired,validateTranches]">
|
||||||
<template v-slot:append-inner>tranches</template>
|
<template v-slot:append-inner>tranches</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-text-field v-model="os[storeVar]" :label="getLabel" type="number"
|
<v-text-field v-model="mv" :label="getLabel" type="number"
|
||||||
variant="outlined" aria-valuemin="0" min="0"
|
variant="outlined" aria-valuemin="0" min="0"
|
||||||
clearable :rules="rules" v-auto-select>
|
:clearable="!required && clearable" :rules="rules" v-auto-select>
|
||||||
<template v-slot:append-inner>
|
<template v-slot:append-inner>
|
||||||
<v-btn variant="outlined" @click="os.inverted=!os.inverted">
|
<v-btn variant="outlined" @click="os.inverted=!os.inverted">
|
||||||
{{ os.pairSymbol }}
|
{{ os.pairSymbol }}
|
||||||
@@ -26,10 +26,17 @@ import {vAutoSelect} from "@/misc.js";
|
|||||||
|
|
||||||
const os = useOrderStore()
|
const os = useOrderStore()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
modelValue: {required: true},
|
||||||
required: {default: false},
|
required: {default: false},
|
||||||
label: {default: null},
|
label: {default: null},
|
||||||
storeVar: {default: 'limitPrice'},
|
|
||||||
showPrice: {default: true},
|
showPrice: {default: true},
|
||||||
|
clearable: {default: true},
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const mv = computed({
|
||||||
|
get() {return props.modelValue},
|
||||||
|
set(v) {emit('update:modelValue', v)}
|
||||||
})
|
})
|
||||||
const rules = computed(()=>props.required ? [validateAmount, validateRequired] : [validateAmount])
|
const rules = computed(()=>props.required ? [validateAmount, validateRequired] : [validateAmount])
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<v-list-subheader title="Dexorders"/>
|
<v-list-subheader title="Dexorders"/>
|
||||||
<v-list-item prepend-icon="mdi-clock-outline" title="DCA" subtitle="Spread order across time" @click="nav('twap')"></v-list-item>
|
<v-list-item prepend-icon="mdi-clock-outline" title="DCA" subtitle="Spread order across time" @click="nav('twap')"></v-list-item>
|
||||||
<v-list-item prepend-icon="mdi-menu" title="Ladder" subtitle="Multiple price levels" @click="nav('ladder')"></v-list-item>
|
<v-list-item prepend-icon="mdi-menu" title="Ladder" subtitle="Multiple price levels" @click="nav('ladder')"></v-list-item>
|
||||||
<v-list-item prepend-icon="mdi-vector-line" title="Diagonal" subtitle="Trade trends and channels" @click="nav('diagonal')"></v-list-item>
|
<v-list-item prepend-icon="mdi-vector-line" title="Diagonal" subtitle="Trends and channels" @click="nav('diagonal')"></v-list-item>
|
||||||
<v-list-item prepend-icon="mdi-hammer-wrench" title="Custom" subtitle="Create your own" @click="nav('custom')"></v-list-item>
|
<v-list-item prepend-icon="mdi-hammer-wrench" title="Custom" subtitle="Create your own" @click="nav('custom')"></v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-card-item>
|
</v-card-item>
|
||||||
<v-card-actions class="d-flex justify-space-evenly mb-4">
|
<v-card-actions class="d-flex justify-space-evenly mb-4">
|
||||||
<v-btn variant="outlined" color="red">Cancel</v-btn>
|
<v-btn variant="outlined" color="red" @click="$router.push('/vault')">Cancel</v-btn>
|
||||||
<v-btn variant="flat" color="green" :disabled="!valid()" @click="placeOrder">Place Dexorder</v-btn>
|
<v-btn variant="flat" color="green" :disabled="!valid()" @click="placeOrder">Place Dexorder</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</phone-card>
|
</phone-card>
|
||||||
|
|||||||
65
src/components/TimeEntry.vue
Normal file
65
src/components/TimeEntry.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div class="d-flex">
|
||||||
|
<v-text-field type="number" min="1" max="31" v-model="day" :label="weekday" :hide-details="hideDetails" style="min-width: 3em; width: 3em" class="all"/>
|
||||||
|
<v-select v-model="month" :items="monthItems" :hide-details="hideDetails" style="min-width: 6em; width: 6em" class="all"/>
|
||||||
|
<v-text-field type="number" v-model="year" :hide-details="hideDetails" style="min-width: 5em; width: 5em" class="all"/>
|
||||||
|
<v-text-field type="time" v-model="time" :hide-details="hideDetails" style="min-width: 8em; width: 8em" class="all"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useStore} from "@/store/store";
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
const s = useStore()
|
||||||
|
const props = defineProps(['modelValue', 'hideDetails'])
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const day = computed({
|
||||||
|
get() { return s.utc ? props.modelValue.getUTCDate() : props.modelValue.getDate() },
|
||||||
|
set(v) { update(year.value, month.value, v, time.value)},
|
||||||
|
})
|
||||||
|
const weekdayFormat = computed(()=>new Intl.DateTimeFormat(s.utc ? 'utc':undefined,{weekday:"short"}))
|
||||||
|
const weekday = computed(()=>weekdayFormat.value.format(props.modelValue))
|
||||||
|
const month = computed({
|
||||||
|
get() { return s.utc ? props.modelValue.getUTCMonth() : props.modelValue.getMonth() },
|
||||||
|
set(v) { update(year.value, v, day.value, time.value)},
|
||||||
|
})
|
||||||
|
const year = computed({
|
||||||
|
get() { return s.utc ? props.modelValue.getUTCFullYear() : props.modelValue.getFullYear() },
|
||||||
|
set(v) { update(v, month.value, day.value, time.value)},
|
||||||
|
})
|
||||||
|
const time = computed({
|
||||||
|
get() { return '' + (s.utc ? `${props.modelValue.getUTCHours()}:${props.modelValue.getUTCMinutes()}` : `${props.modelValue.getHours()}:${props.modelValue.getMinutes()}` ) },
|
||||||
|
set(v) { update(year.value, month.value, day.value, v) }
|
||||||
|
})
|
||||||
|
|
||||||
|
function monthsForLocale(localeName = undefined, monthFormat = 'long') {
|
||||||
|
const format = new Intl.DateTimeFormat(localeName, {month: monthFormat}).format;
|
||||||
|
return [...Array(12).keys()]
|
||||||
|
.map((m) => format(new Date(Date.UTC(2000, m+1))));
|
||||||
|
}
|
||||||
|
|
||||||
|
let m=0
|
||||||
|
const monthItems = monthsForLocale(undefined, 'short').map((v)=>{return {title:v, value:m++}})
|
||||||
|
|
||||||
|
function buildDate(y, m, d, t) {
|
||||||
|
const [hours,minutes] = t.split(':')
|
||||||
|
console.log('buildDate', y, m, d, hours, minutes)
|
||||||
|
return s.utc ? new Date(Date.UTC(y, m, d, hours, minutes))
|
||||||
|
: new Date(y, m, d, hours, minutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(y,m,d,t) {
|
||||||
|
const date = buildDate(y, m, d, t);
|
||||||
|
emit('update:modelValue', date)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "src/styles/vars" as *;
|
||||||
|
|
||||||
|
.all {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<order title="DCA / TWAP" subtitle="Multiple tranches over a time range" :tranches="buildTranches" :valid="validOrder">
|
<order title="DCA / TWAP" subtitle="Multiple tranches over a time range" :tranches="buildTranches" :valid="validOrder">
|
||||||
<Tranches/>
|
<Tranches/>
|
||||||
<LimitPrice/>
|
<LimitPrice v-model="os.limitPrice"/>
|
||||||
</order>
|
</order>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -77,3 +77,8 @@ export function dateString(seconds) {
|
|||||||
const date = new Date(seconds*1000)
|
const date = new Date(seconds*1000)
|
||||||
return _dateFormat.format(date)
|
return _dateFormat.format(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function timestamp(date) {
|
||||||
|
return Math.round(date.getTime() / 1000)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,53 @@
|
|||||||
import {routeInverted} from "@/misc.js";
|
import {routeInverted, timestamp} from "@/misc.js";
|
||||||
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||||
import {useOrderStore} from "@/store/store.js";
|
import {useOrderStore} from "@/store/store.js";
|
||||||
import {encodeIEE754} from "@/blockchain/common.js";
|
import {encodeIEE754} from "@/blockchain/common.js";
|
||||||
|
|
||||||
|
|
||||||
export function applyLimit(tranche, price=null) {
|
export function applyLimit(tranche, price=null, isAbove=null) {
|
||||||
const os = useOrderStore()
|
|
||||||
if( price === null ) {
|
if( price === null ) {
|
||||||
|
const os = useOrderStore()
|
||||||
price = os.limitPrice
|
price = os.limitPrice
|
||||||
if (!price)
|
if (!price)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyLine(tranche, price, 0, isAbove)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function applyLinePoints(tranche, date0, price0, date1, price1, isAbove=null) {
|
||||||
|
const os = useOrderStore()
|
||||||
|
if( !date0 || !price0 && price0!==0 || !date1 || !price1 && price1!==0 )
|
||||||
|
return
|
||||||
|
|
||||||
|
const t0 = timestamp(date0);
|
||||||
|
const t1 = timestamp(date1);
|
||||||
|
const slope = (price1-price0)/(t1-t0)
|
||||||
|
const intercept = price1 - slope * t1
|
||||||
|
applyLine(tranche, intercept, slope, isAbove)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function applyLine(tranche, intercept, slope, isAbove=null) {
|
||||||
|
// intercept and slope are still in "human" units of decimal-adjusted prices
|
||||||
|
const os = useOrderStore()
|
||||||
const route = os.route
|
const route = os.route
|
||||||
const inverted = routeInverted(route)
|
const inverted = routeInverted(route)
|
||||||
const isAbove = os.limitIsMinimum ^ inverted
|
const scale = 10 ** (os.tokenA.decimals - os.tokenB.decimals)
|
||||||
const isRatio = false // todo ratios
|
const m = encodeIEE754(inverted ? scale / slope : slope / scale)
|
||||||
const decimals = 10 ** (os.tokenA.decimals - os.tokenB.decimals)
|
const b = encodeIEE754(inverted ? scale / intercept : intercept / scale)
|
||||||
const limit = encodeIEE754(inverted ? decimals / price : price / decimals)
|
if( isAbove === null )
|
||||||
tranche.marketOrder = false;
|
isAbove = os.limitIsMinimum ^ inverted
|
||||||
if( isAbove ) {
|
if( isAbove ) {
|
||||||
tranche.minIntercept = limit;
|
tranche.minIntercept = b;
|
||||||
tranche.minSlope = 0;
|
tranche.minSlope = m;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tranche.maxIntercept = limit;
|
tranche.maxIntercept = b;
|
||||||
tranche.maxSlope = 0;
|
tranche.maxSlope = m;
|
||||||
}
|
}
|
||||||
|
tranche.marketOrder = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function timesliceTranches() {
|
export function timesliceTranches() {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {computed, ref} from "vue";
|
|||||||
|
|
||||||
|
|
||||||
export const useStore = defineStore('app', ()=> {
|
export const useStore = defineStore('app', ()=> {
|
||||||
const nav = ref(false)
|
const nav = ref(false) // controls opening navigation drawer
|
||||||
|
|
||||||
const _chainId = ref(null)
|
const _chainId = ref(null)
|
||||||
const _chainInfo = ref({})
|
const _chainInfo = ref({})
|
||||||
@@ -136,6 +136,7 @@ export const useOrderStore = defineStore('order', ()=> {
|
|||||||
const timeUnitIndex = ref(0)
|
const timeUnitIndex = ref(0)
|
||||||
const routes = ref([])
|
const routes = ref([])
|
||||||
const routesPending = ref(false)
|
const routesPending = ref(false)
|
||||||
|
const utc = ref(false)
|
||||||
|
|
||||||
const validOrder = computed(() => amount.value > 0 && routes.value.length > 0)
|
const validOrder = computed(() => amount.value > 0 && routes.value.length > 0)
|
||||||
const route = computed(() => routes.value.length === 0 ? null : routes.value[0])
|
const route = computed(() => routes.value.length === 0 ? null : routes.value[0])
|
||||||
@@ -164,6 +165,6 @@ export const useOrderStore = defineStore('order', ()=> {
|
|||||||
return {
|
return {
|
||||||
tokenA, tokenB, buy, inverted, amount, amountIsTokenA, amountIsTotal, limitPrice, limitPrice2, tranches,
|
tokenA, tokenB, buy, inverted, amount, amountIsTokenA, amountIsTotal, limitPrice, limitPrice2, tranches,
|
||||||
interval, intervalIsTotal, timeUnitIndex, routes, routesPending, validOrder, route, base, quote, pairSymbol,
|
interval, intervalIsTotal, timeUnitIndex, routes, routesPending, validOrder, route, base, quote, pairSymbol,
|
||||||
limitIsMinimum, amountToken, amountIsInput, setDefaultTokens, totalAmount, trancheAmount,
|
limitIsMinimum, amountToken, amountIsInput, setDefaultTokens, totalAmount, trancheAmount, utc,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<needs-provider>
|
<needs-provider>
|
||||||
<needs-signer>
|
<needs-signer>
|
||||||
<phone-card title="Vault Orders">
|
<phone-card>
|
||||||
|
<v-card-title><v-icon icon="mdi-information-outline" size="small" color="grey-darken-1"/> Vault Orders</v-card-title>
|
||||||
<v-card-item>
|
<v-card-item>
|
||||||
<orders/>
|
<orders/>
|
||||||
</v-card-item>
|
</v-card-item>
|
||||||
|
|||||||
Reference in New Issue
Block a user