251 lines
6.6 KiB
Vue
251 lines
6.6 KiB
Vue
<template>
|
|
<rung-builder name='DCA' :order="order" :builder="builder" v-model="timeEndpoints"
|
|
:shape="VLine"
|
|
:mode="1" :flip="flipped" :orientation="0"
|
|
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
|
:get-points-value="getPointsValue"
|
|
:set-values="setValues" :set-weights="setWeights"
|
|
:std-width="stdWidth" :build-tranches="buildTranches">
|
|
|
|
<!--
|
|
<v-list style="background-color: inherit">
|
|
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
|
</v-list>
|
|
-->
|
|
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td>
|
|
<absolute-time-entry v-model="absTimeA"/>
|
|
</td>
|
|
<td class="weight">{{ weights.length ? allocationTexts[0] : '' }}</td>
|
|
</tr>
|
|
<tr v-if="weights.length>2" v-for="i in weights.length-2" class="ml-5"> <!-- vue uses 1-based loops -->
|
|
<td class="d-flex justify-end pr-3">{{ dateStrings[i] }}</td>
|
|
<td class="weight">{{ allocationTexts[i] }}</td>
|
|
</tr>
|
|
<tr v-if="weights.length>1">
|
|
<td>
|
|
<absolute-time-entry v-model="absTimeB"/>
|
|
</td>
|
|
<td class="weight">{{ allocationTexts[weights.length-1] }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
|
|
</rung-builder>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {builderDefaults, DEFAULT_SLIPPAGE, MIN_EXECUTION_TIME, useChartOrderStore} from "@/orderbuild.js";
|
|
import {allocationText, VLine} from "@/charts/shape.js";
|
|
import {sideColor} from "@/misc.js";
|
|
import {useOrderStore, useStore} from "@/store/store.js";
|
|
import {DISTANT_FUTURE, MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
|
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
|
import {computed, ref, watchEffect} from "vue";
|
|
import {chart, dragging} from "@/charts/chart.js";
|
|
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
|
import {DateTime} from "luxon";
|
|
|
|
const s = useStore()
|
|
const os = useOrderStore()
|
|
const co = useChartOrderStore()
|
|
const props = defineProps(['order', 'builder'])
|
|
const emit = defineEmits(['update:builder'])
|
|
|
|
const minWidth = computed(()=>co.intervalSecs)
|
|
const stdWidth = computed(()=>10 * minWidth.value)
|
|
|
|
function computeDefaultColor() {
|
|
const index = props.order.builders.indexOf(props.builder)
|
|
return sideColor(props.order.buy, index)
|
|
}
|
|
|
|
const defaultColor = computeDefaultColor()
|
|
|
|
builderDefaults(props.builder, {
|
|
timeA: s.clock, // todo relative 0
|
|
timeB: null,
|
|
// relative: true, // todo
|
|
relative: false,
|
|
slippage: DEFAULT_SLIPPAGE,
|
|
rungs: 1,
|
|
skew: 0,
|
|
color: defaultColor,
|
|
})
|
|
|
|
const times = ref([])
|
|
const weights = ref([])
|
|
|
|
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
|
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(w, w*props.order.amount, amountSymbol.value)))
|
|
|
|
const endTimes = computed(()=>{
|
|
if (props.builder.rungs === 1)
|
|
return DISTANT_FUTURE
|
|
const ts = times.value
|
|
const window = Math.max(MIN_EXECUTION_TIME, Math.floor((ts[ts.length-1]-ts[0])/props.builder.rungs))
|
|
return ts.map((t)=>t+window)
|
|
})
|
|
const absoluteTimes = computed(()=>{
|
|
// console.log('absoluteTimes', props.builder.relative, times.value)
|
|
// if (!props.builder.relative)
|
|
return times.value
|
|
// const now = s.clock
|
|
// return times.value.map((t)=>now+t)
|
|
})
|
|
|
|
const dateStrings = computed(()=>absoluteTimes.value.map((t)=>{
|
|
const n = DateTime.fromSeconds(t).setZone(s.timeZone)
|
|
const y = n.toLocaleString({year:'numeric'})
|
|
const m = n.toLocaleString({month:'long'})
|
|
const d = n.toLocaleString({day:'numeric'})
|
|
const h = n.toLocaleString({hour:'numeric', minute:'numeric'})
|
|
return `${y} ${m} ${d} ${h}`
|
|
}))
|
|
|
|
watchEffect(()=>{
|
|
// auto scroll
|
|
if (!dragging && absoluteTimes.value.length) {
|
|
const endTime = absoluteTimes.value[absoluteTimes.value.length-1]
|
|
const range = chart.getVisibleRange()
|
|
const width = range.to - range.from
|
|
const now = s.clock
|
|
const extra = (Math.max(0,endTime - now) + stdWidth.value/2) / width
|
|
// console.log('visrange', range, width, absV)
|
|
if (range.to < endTime) {
|
|
// console.log('scrolling')
|
|
chart.setVisibleRange({from: now - width, to: now}, {
|
|
percentRightMargin: Math.round(100 * extra),
|
|
applyDefaultRightMargin: false
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
const absTimeA = computed({
|
|
get() { return _timeEndpoints.value[0] },
|
|
set(v) {
|
|
if (v!==null)
|
|
v = Number(v)
|
|
updateA(v)
|
|
}
|
|
})
|
|
const absTimeB = computed({
|
|
get() { return _timeEndpoints.value[1] },
|
|
set(v) {
|
|
if (v!==null)
|
|
v = Number(v)
|
|
updateB(v)
|
|
}
|
|
})
|
|
|
|
|
|
const _timeEndpoints = ref([props.builder.timeA, props.builder.timeB])
|
|
const timeEndpoints = computed({
|
|
get() { return _timeEndpoints.value},
|
|
set(v) {
|
|
const [a, b] = v
|
|
update(a,b)
|
|
}
|
|
})
|
|
|
|
|
|
function updateA(a) {
|
|
update(a, _timeEndpoints.value[1], true)
|
|
}
|
|
|
|
|
|
function updateB(b) {
|
|
update(_timeEndpoints.value[0], b, false)
|
|
}
|
|
|
|
|
|
function update(a, b, updatingA) {
|
|
if (updatingA) {
|
|
const minB = a + minWidth.value
|
|
if (b < minB)
|
|
b = minB
|
|
}
|
|
else {
|
|
const maxA = b - minWidth.value
|
|
if (a > maxA)
|
|
a = maxA
|
|
}
|
|
_timeEndpoints.value = [a, b]
|
|
const newBuilder = {...props.builder}
|
|
newBuilder.timeA = a
|
|
newBuilder.timeB = b
|
|
emit('update:builder', newBuilder)
|
|
}
|
|
|
|
const flipped = computed(()=>{
|
|
const a = props.builder.timeA
|
|
const b = props.builder.timeB
|
|
return a !== null && b !== null && a > b
|
|
})
|
|
|
|
function buildTranches() {
|
|
const order = props.order
|
|
const builder = props.builder
|
|
const tranches = []
|
|
|
|
console.log('buildTranches', builder, order, tranches)
|
|
const ts = times.value
|
|
const ets = endTimes.value
|
|
const ws = weights.value
|
|
for(let i=0; i<ts.length; i++) {
|
|
const t = newTranche({
|
|
fraction: ws[i] * MAX_FRACTION,
|
|
startTime: ts[i],
|
|
endTime: ets[i],
|
|
slippage: builder.slippage,
|
|
})
|
|
tranches.push(t)
|
|
}
|
|
return tranches
|
|
}
|
|
|
|
|
|
function getModelValue(model) {
|
|
if(!model) {
|
|
console.log('getModelValue', model)
|
|
return null
|
|
}
|
|
return model.time
|
|
}
|
|
|
|
function getPointsValue(points) {
|
|
return points[0].price
|
|
}
|
|
|
|
function setModelValue(model, value) {
|
|
// console.log('DCA set model value', model, value)
|
|
// const v = value === null ? null : props.builder.relative ? s.clock + Math.round(value) : Math.round(value)
|
|
const v = value === null ? null : Math.round(value)
|
|
if (model.time !== v) {
|
|
// console.log('DCA do set time', v)
|
|
model.time = v
|
|
}
|
|
}
|
|
|
|
function setValues(values) {
|
|
times.value = values.map((t)=>Math.round(t))
|
|
}
|
|
|
|
function setWeights(ws) { weights.value = ws }
|
|
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
td.weight {
|
|
padding-left: 1em;
|
|
padding-right: 1em;
|
|
}
|
|
</style>
|
|
|