diagonal order form

This commit is contained in:
Tim Olson
2023-12-16 16:54:05 -04:00
parent 206fb21687
commit 9199d31e77
11 changed files with 143 additions and 93 deletions

View File

@@ -1,23 +1,15 @@
<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 -->
<span><i>Coming soon!</i></span>
<!--
<limit-price :required="true" label="start price" :show-price="false"/>
<limit-price store-var="limitPrice2" :required="true" label="end price"/>
<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>
-->
<div class="title">Line Point A</div>
<v-divider/>
<time-entry v-model="time1" class="mb-0" hide-details="true"/>
<limit-price v-model="price1" label="Price A" :required="true"/>
<div class="title">Line Point B</div>
<v-divider/>
<time-entry v-model="time2" hide-details="true"/>
<limit-price v-model="price2" label="Price B" :required="true"/>
<div><i>Backend support for diagonal lines is coming soon...</i></div>
</order>
</template>
@@ -26,65 +18,22 @@ import {useOrderStore} from "@/store/store";
import LimitPrice from "@/components/LimitPrice.vue";
import Order from "@/components/Order.vue";
import {computed, ref} from "vue";
import {applyLimit} from "@/orderbuild.js";
import {applyLimit, applyLinePoints} from "@/orderbuild.js";
import {validateRequired, validateTranches} from "@/validate.js";
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
import TimeEntry from "@/components/TimeEntry.vue";
const os = useOrderStore()
const skew = ref(0)
const rungs = computed(()=>{
if( !os.limitPrice || !os.limitPrice2 )
return []
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) )
const time1 = ref(new Date())
const price1 = ref(null)
const time2 = ref(new Date())
const price2 = ref(null)
function buildTranches() {
const ts = []
const n = os.tranches
for( let i=0; i<n; i++ ) {
// 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
const t = newTranche();
applyLinePoints(t, time1.value, price1.value, time2.value, price2.value)
return [t]
}
function validOrder() {

View File

@@ -1,7 +1,7 @@
<template>
<order title="Ladder" subtitle="Multiple price levels" :tranches="buildTranches" :valid="validOrder">
<limit-price :required="true" label="start price" :show-price="false"/>
<limit-price store-var="limitPrice2" :required="true" label="end price"/>
<limit-price v-model="os.limitPrice" :required="true" label="start price" :show-price="false"/>
<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-model="os.tranches" :rules="[validateRequired,validateTranches]">
<template v-slot:append-inner>tranches</template>

View File

@@ -1,7 +1,7 @@
<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"
clearable :rules="rules" v-auto-select>
:clearable="!required && clearable" :rules="rules" v-auto-select>
<template v-slot:append-inner>
<v-btn variant="outlined" @click="os.inverted=!os.inverted">
{{ os.pairSymbol }}
@@ -26,10 +26,17 @@ import {vAutoSelect} from "@/misc.js";
const os = useOrderStore()
const props = defineProps({
modelValue: {required: true},
required: {default: false},
label: {default: null},
storeVar: {default: 'limitPrice'},
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])

View File

@@ -7,7 +7,7 @@
<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-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>
</v-navigation-drawer>

View File

@@ -11,7 +11,7 @@
</div>
</v-card-item>
<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-card-actions>
</phone-card>

View 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>

View File

@@ -1,7 +1,7 @@
<template>
<order title="DCA / TWAP" subtitle="Multiple tranches over a time range" :tranches="buildTranches" :valid="validOrder">
<Tranches/>
<LimitPrice/>
<LimitPrice v-model="os.limitPrice"/>
</order>
</template>

View File

@@ -77,3 +77,8 @@ export function dateString(seconds) {
const date = new Date(seconds*1000)
return _dateFormat.format(date)
}
export function timestamp(date) {
return Math.round(date.getTime() / 1000)
}

View File

@@ -1,31 +1,53 @@
import {routeInverted} from "@/misc.js";
import {routeInverted, timestamp} from "@/misc.js";
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
import {useOrderStore} from "@/store/store.js";
import {encodeIEE754} from "@/blockchain/common.js";
export function applyLimit(tranche, price=null) {
const os = useOrderStore()
export function applyLimit(tranche, price=null, isAbove=null) {
if( price === null ) {
const os = useOrderStore()
price = os.limitPrice
if (!price)
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 inverted = routeInverted(route)
const isAbove = os.limitIsMinimum ^ inverted
const isRatio = false // todo ratios
const decimals = 10 ** (os.tokenA.decimals - os.tokenB.decimals)
const limit = encodeIEE754(inverted ? decimals / price : price / decimals)
tranche.marketOrder = false;
const scale = 10 ** (os.tokenA.decimals - os.tokenB.decimals)
const m = encodeIEE754(inverted ? scale / slope : slope / scale)
const b = encodeIEE754(inverted ? scale / intercept : intercept / scale)
if( isAbove === null )
isAbove = os.limitIsMinimum ^ inverted
if( isAbove ) {
tranche.minIntercept = limit;
tranche.minSlope = 0;
tranche.minIntercept = b;
tranche.minSlope = m;
}
else {
tranche.maxIntercept = limit;
tranche.maxSlope = 0;
tranche.maxIntercept = b;
tranche.maxSlope = m;
}
tranche.marketOrder = false;
}
export function timesliceTranches() {

View File

@@ -29,7 +29,7 @@ import {computed, ref} from "vue";
export const useStore = defineStore('app', ()=> {
const nav = ref(false)
const nav = ref(false) // controls opening navigation drawer
const _chainId = ref(null)
const _chainInfo = ref({})
@@ -136,6 +136,7 @@ export const useOrderStore = defineStore('order', ()=> {
const timeUnitIndex = ref(0)
const routes = ref([])
const routesPending = ref(false)
const utc = ref(false)
const validOrder = computed(() => amount.value > 0 && routes.value.length > 0)
const route = computed(() => routes.value.length === 0 ? null : routes.value[0])
@@ -164,6 +165,6 @@ export const useOrderStore = defineStore('order', ()=> {
return {
tokenA, tokenB, buy, inverted, amount, amountIsTokenA, amountIsTotal, limitPrice, limitPrice2, tranches,
interval, intervalIsTotal, timeUnitIndex, routes, routesPending, validOrder, route, base, quote, pairSymbol,
limitIsMinimum, amountToken, amountIsInput, setDefaultTokens, totalAmount, trancheAmount,
limitIsMinimum, amountToken, amountIsInput, setDefaultTokens, totalAmount, trancheAmount, utc,
}
})

View File

@@ -1,7 +1,8 @@
<template>
<needs-provider>
<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>
<orders/>
</v-card-item>