Files
web/src/components/chart/DateBuilder.vue
2025-04-26 14:56:55 -04:00

256 lines
7.1 KiB
Vue

<template class="d-flex align-content-center flex-column" style="height: 100%; width: 100%;">
<rung-builder name='Dates' :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, useChartOrderStore} from "@/orderbuild.js";
import {allocationText, VLine} from "@/charts/shape.js";
import {sideColor} from "@/misc.js";
import {useOrderStore, usePrefStore, useStore} from "@/store/store.js";
import {DEFAULT_SLIPPAGE, DISTANT_FUTURE, MAX_FRACTION, MIN_EXECUTION_TIME, 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 prefs = usePrefStore()
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,
balance: 0,
color: defaultColor,
buy: true,
})
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(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
const endTimes = computed(()=>{
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(prefs.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, true, true)
}
})
function updateA(a) {
update(a, _timeEndpoints.value[1], true, false)
}
function updateB(b) {
update(_timeEndpoints.value[0], b, false, true)
}
function update(a, b, updateA, updateB) {
if (updateA) {
const minB = a + minWidth.value
if (b < minB)
b = minB
}
if (updateB) {
const maxA = b - minWidth.value
if (a > maxA)
a = maxA
}
_timeEndpoints.value = [a, b]
props.builder.timeA = a
props.builder.timeB = b
}
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 = []
const warnings = []
console.log('buildTranches', builder, order, tranches)
const ts = times.value
const ets = endTimes.value
const ws = weights.value
console.log('buildTranches times ends weights', ts, ets, ws)
for(let i=0; i<ts.length; i++) {
const endTime = Math.max(ets[i],ts[i]+60);
console.log('time check', endTime, s.clock)
if (endTime <= s.clock)
warnings.push(`Tranche already expired at ${new Date(endTime*1000)}`)
const t = newTranche({
marketOrder: true,
slippage: builder.slippage,
fraction: ws[i] * MAX_FRACTION,
startTime: ts[i],
endTime, // always give at least 60 seconds of window to execute
})
tranches.push(t)
}
return {tranches, warnings}
}
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>