444 lines
13 KiB
Vue
444 lines
13 KiB
Vue
<template>
|
|
<rung-builder :name="name" :description="description"
|
|
:order="order" :builder="builder" v-model="endpoints"
|
|
:shape="DLine" :mode="0"
|
|
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
|
:set-values="setLines" :set-weights="setWeights"
|
|
:set-shapes="setShapes"
|
|
:std-width="stdWidth" :build-tranches="buildTranches">
|
|
<table v-if="!co.drawing">
|
|
<tbody>
|
|
<tr>
|
|
<td> </td>
|
|
<td colspan="3">
|
|
<div class="d-flex align-center">
|
|
<v-switch class="d-inline-flex" v-model="extendLeft" inline
|
|
:true-value="false" :false-value="true"
|
|
true-icon="mdi-circle-outline" false-icon="mdi-arrow-left"
|
|
/>
|
|
<span class="mx-3">Extend</span>
|
|
<v-switch class="d-inline-flex" v-model="extendRight" inline
|
|
false-icon="mdi-circle-outline" true-icon="mdi-arrow-right"
|
|
/>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label" rowspan="2">Line A</td>
|
|
<td>
|
|
<absolute-time-entry v-model="time1A"/>
|
|
</td>
|
|
<td>
|
|
<v-text-field type="number" v-model="price1A" min="0"
|
|
density="compact" hide-details variant="outlined"
|
|
class="mx-1 my-2 price"
|
|
:color="color"
|
|
label="Price"
|
|
/>
|
|
</td>
|
|
<td rowspan="2" class="weight"
|
|
:style="'vertical-align: '+(price2A!==null?'bottom':'center')">
|
|
{{ allocationTexts[weights.length-1] }}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<absolute-time-entry v-model="time1B"/>
|
|
</td>
|
|
<td>
|
|
<v-text-field type="number" v-model="price1B" min="0"
|
|
density="compact" hide-details variant="outlined"
|
|
class="mx-1 my-2 price"
|
|
:color="color"
|
|
label="Price"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
<tr v-for="i in innerIndexes" class="ml-5">
|
|
<td class="text-right"> </td>
|
|
<td colspan="2" class="text-center">— Interior Line —</td>
|
|
<td class="weight">{{ allocationTexts[i] }}</td>
|
|
</tr>
|
|
<tr v-if="weights.length>1">
|
|
<td rowspan="2" class="label">Line B</td>
|
|
<td>
|
|
<absolute-time-entry v-model="time2A"/>
|
|
</td>
|
|
<td>
|
|
<v-text-field type="number" v-model="price2A" min="0"
|
|
density="compact" hide-details variant="outlined"
|
|
class="mx-1 my-2 price"
|
|
:color="color"
|
|
label="Price"
|
|
/>
|
|
</td>
|
|
<td rowspan="2" class="weight" style="vertical-align: top">{{ allocationTexts[0] }}</td>
|
|
</tr>
|
|
<tr v-if="weights.length>1">
|
|
<td>
|
|
<absolute-time-entry v-model="time2B"/>
|
|
</td>
|
|
<td>
|
|
<v-text-field type="number" v-model="price2B" min="0"
|
|
density="compact" hide-details variant="outlined"
|
|
class="mx-1 my-2 price"
|
|
:color="color"
|
|
label="Price"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<one-time-hint name="click-chart" activator="#tv-widget" location="center" :when="builder.lineA===null && !co.drew" text="Click the chart!"/>
|
|
</rung-builder>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {applyLinePoints, builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
|
import {sideColor} from "@/misc.js";
|
|
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
|
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
|
import {computed, ref} from "vue";
|
|
import {allocationText, DLine} from "@/charts/shape.js";
|
|
import {vectorEquals, vectorInterpolate} from "@/vector.js";
|
|
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
|
import {useStore} from "@/store/store.js";
|
|
import OneTimeHint from "@/components/OneTimeHint.vue";
|
|
|
|
const s = useStore()
|
|
const co = useChartOrderStore()
|
|
const props = defineProps(['order', 'builder'])
|
|
const emit = defineEmits(['update:builder'])
|
|
|
|
function computeDefaultColor() {
|
|
const index = props.order.builders.indexOf(props.builder)
|
|
return sideColor(props.order.buy, index)
|
|
}
|
|
|
|
const defaultColor = computeDefaultColor()
|
|
const color = computed(()=>props.builder.color ? props.builder.color : defaultColor)
|
|
|
|
// Fields must be defined in order to be reactive
|
|
builderDefaults(props.builder, {
|
|
lineA: null, // [{time, price}, {time, price}]
|
|
lineB: null,
|
|
extendLeft: false,
|
|
extendRight: true,
|
|
rungs: 1,
|
|
balance: 0,
|
|
breakout: false,
|
|
color: defaultColor,
|
|
buy: true,
|
|
})
|
|
|
|
|
|
function buildTranches() {
|
|
const order = props.order
|
|
const builder = props.builder
|
|
const tranches = []
|
|
const warnings = []
|
|
|
|
console.log('buildTranches', builder, order, _endpoints.value)
|
|
const la = _endpoints.value[0] // use the flatline format which is a vector of length 4, useful for vectorInterpolate()
|
|
const lb = _endpoints.value[1]
|
|
const ws = weights.value
|
|
const symbol = co.selectedSymbol
|
|
for (let i = 0; i < ws.length; i++) {
|
|
const w = ws[i]
|
|
const t = newTranche({fraction: w * MAX_FRACTION})
|
|
const line = ws.length === 1 ? la : vectorInterpolate(la, lb, i/(ws.length-1))
|
|
const el = extendLeft.value
|
|
const er = extendRight.value
|
|
const reversed = line[0] > line[2]
|
|
if (reversed ? !er : !el)
|
|
t.startTime = reversed ? line[2] : line[0]
|
|
if (reversed ? !el : !er)
|
|
t.endTime = reversed ? line[0] : line[2]
|
|
if (t.endTime <= s.clock)
|
|
warnings.push(`Tranche already expired at ${new Date(t.endTime*1000)}`)
|
|
|
|
// console.log('tranche start/end',
|
|
// t.startTime === DISTANT_PAST ? 'PAST' : t.startTime,
|
|
// t.endTime === DISTANT_FUTURE ? 'FUTURE' : t.endTime)
|
|
applyLinePoints(t, symbol, order.buy, ...line, builder.breakout)
|
|
tranches.push(t)
|
|
}
|
|
// if( flipped.value )
|
|
// tranches.reverse()
|
|
return {tranches, warnings}
|
|
}
|
|
|
|
|
|
function flattenLine(l) {
|
|
return l === null ? null : [l[0].time, l[0].price, l[1].time, l[1].price]
|
|
}
|
|
|
|
|
|
function buildLine(f) {
|
|
// console.log('buildLine', f)
|
|
return f === null ? null : [{time: Number(f[0]), price: Number(f[1])}, {time: Number(f[2]), price: Number(f[3])}]
|
|
}
|
|
|
|
const _endpoints = ref([flattenLine(props.builder.lineA), flattenLine(props.builder.lineB)])
|
|
const endpoints = computed({
|
|
get() {
|
|
return _endpoints.value
|
|
},
|
|
set(v) {
|
|
let [a, b] = v
|
|
a = buildLine(a)
|
|
b = buildLine(b)
|
|
update(a, b)
|
|
}
|
|
})
|
|
|
|
const alignTimes = ref(true)
|
|
|
|
const time1A = computed({
|
|
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][0] },
|
|
set(v) {
|
|
const flatline0 = _endpoints.value[0];
|
|
update(
|
|
[{time:v, price: flatline0[1]}, {time:flatline0[2], price: flatline0[3]}],
|
|
_endpoints.value[1]
|
|
)
|
|
}
|
|
})
|
|
|
|
const price1A = computed({
|
|
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][1] },
|
|
set(v) {
|
|
const flatline0 = _endpoints.value[0];
|
|
update(
|
|
[{time:flatline0[0], price: Number(v)}, {time:flatline0[2], price: flatline0[3]}],
|
|
_endpoints.value[1]
|
|
)
|
|
}
|
|
})
|
|
|
|
const time1B = computed({
|
|
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][2] },
|
|
set(v) {
|
|
const flatline0 = _endpoints.value[0];
|
|
update(
|
|
[{time:flatline0[0], price: flatline0[1]}, {time:v, price: flatline0[3]}],
|
|
_endpoints.value[1]
|
|
)
|
|
}
|
|
})
|
|
|
|
const price1B = computed({
|
|
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][3] },
|
|
set(v) {
|
|
const flatline0 = _endpoints.value[0];
|
|
update(
|
|
[{time:flatline0[0], price: flatline0[1]}, {time:flatline0[2], price: Number(v)}],
|
|
_endpoints.value[1]
|
|
)
|
|
}
|
|
})
|
|
|
|
const time2A = computed({
|
|
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][0] },
|
|
set(v) {
|
|
const flatline = _endpoints.value[1];
|
|
update(
|
|
_endpoints.value[0],
|
|
[{time:v, price: flatline[1]}, {time:flatline[2], price: flatline[3]}],
|
|
)
|
|
}
|
|
})
|
|
|
|
const price2A = computed({
|
|
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][1] },
|
|
set(v) {
|
|
const flatline = _endpoints.value[1];
|
|
update(
|
|
_endpoints.value[0],
|
|
[{time:flatline[0], price: Number(v)}, {time:flatline[2], price: flatline[3]}],
|
|
)
|
|
}
|
|
})
|
|
|
|
const time2B = computed({
|
|
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][2] },
|
|
set(v) {
|
|
const flatline = _endpoints.value[1];
|
|
update(
|
|
_endpoints.value[0],
|
|
[{time:flatline[0], price: flatline[1]}, {time:v, price: flatline[3]}],
|
|
)
|
|
}
|
|
})
|
|
|
|
const price2B = computed({
|
|
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][3] },
|
|
set(v) {
|
|
const flatline = _endpoints.value[1];
|
|
update(
|
|
_endpoints.value[0],
|
|
[{time:flatline[0], price: flatline[1]}, {time:flatline[2], price: Number(v)}],
|
|
)
|
|
}
|
|
})
|
|
|
|
function update(a, b) { // a and b are lines of two points
|
|
if (!vectorEquals(props.builder.lineA, a) || !vectorEquals(props.builder.lineB, b)) {
|
|
_endpoints.value = [flattenLine(a), flattenLine(b)]
|
|
const newBuilder = {...props.builder}
|
|
newBuilder.lineA = a
|
|
newBuilder.lineB = b
|
|
emit('update:builder', newBuilder)
|
|
}
|
|
}
|
|
|
|
|
|
let shapeA = null
|
|
let shapeB = null
|
|
|
|
function setShapes(a,b) {
|
|
shapeA = a
|
|
shapeB = b
|
|
}
|
|
|
|
|
|
const _extendLeft = ref(true)
|
|
const extendLeft = computed({
|
|
get() {return _extendLeft.value},
|
|
set(v) {
|
|
if (v !== _extendLeft.value) {
|
|
_extendLeft.value = v;
|
|
const b = {...props.builder}
|
|
b.extendLeft = v
|
|
// shapeA.setModel({extendLeft: v})
|
|
// shapeB.setModel({extendLeft: v})
|
|
emit('update:builder', b)
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
const _extendRight = ref(true)
|
|
const extendRight = computed({
|
|
get() {return _extendRight.value},
|
|
set(v) {
|
|
if (v !== _extendRight.value) {
|
|
_extendRight.value = v;
|
|
const b = {...props.builder}
|
|
b.extendRight = v
|
|
// shapeA.setModel({extendRight: v})
|
|
// shapeB.setModel({extendRight: v})
|
|
emit('update:builder', b)
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
const innerIndexes = computed(() => {
|
|
const n = flatLines.value.length
|
|
const result = []
|
|
for (let i = 1; i < n - 1; i++)
|
|
result.push(n - 1 - i)
|
|
return result
|
|
})
|
|
|
|
|
|
// these are set by the RungBuilder
|
|
const flatLines = ref([])
|
|
const weights = ref([])
|
|
|
|
function setLines(ls) {
|
|
flatLines.value = ls
|
|
}
|
|
|
|
function setWeights(ws) {
|
|
weights.value = ws
|
|
}
|
|
|
|
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 stdWidth = computed(()=>[0, co.meanRange, 0, co.meanRange])
|
|
|
|
|
|
function getModelValue(model) {
|
|
// model is the DLine shape's model object
|
|
if (!model.pointA || !model.pointB)
|
|
return null
|
|
if (model.extendLeft !== _extendLeft.value)
|
|
extendLeft.value = model.extendLeft
|
|
if (model.extendRight !== _extendRight.value)
|
|
extendRight.value = model.extendRight
|
|
const result = flattenLine([model.pointA, model.pointB]);
|
|
// console.log('getModelValue', {...model}, result)
|
|
return result // this is the vector the RungBuilder will interpolate
|
|
}
|
|
|
|
|
|
function setModelValue(model, value) {
|
|
// const oldModel = {...model};
|
|
// console.log('setModelValue', oldModel, value)
|
|
const oldLine = !model.pointA || !model.pointB ? null : [model.pointA, model.pointB]
|
|
const line = buildLine(value)
|
|
if (dirtyLine(oldLine, line)) {
|
|
if (line===null) {
|
|
model.pointA = null
|
|
model.pointB = null
|
|
}
|
|
else {
|
|
model.pointA = line[0]
|
|
model.pointB = line[1]
|
|
}
|
|
}
|
|
if (model.extendLeft !== _extendLeft.value)
|
|
model.extendLeft = _extendLeft.value
|
|
if (model.extendRight !== _extendRight.value)
|
|
model.extendRight = _extendRight.value
|
|
// console.log('setModelValue end', oldModel, value, model)
|
|
}
|
|
|
|
|
|
function dirtyLine(a, b) {
|
|
const result = a === b ? false :
|
|
a === null && b !== null || a !== null && b === null ||
|
|
a[0].time !== b[0].time || a[0].price !== b[0].price || a[1].time !== b[1].time || a[1].price !== b[1].price
|
|
// console.log('dirtyLine', result, a, b)
|
|
return result
|
|
}
|
|
|
|
const name = computed(()=>props.builder.breakout?(props.order.buy?'Breakout':'Breakdown'):'Limit')
|
|
|
|
const description = computed(()=>{
|
|
const buy = props.order.buy
|
|
const above = buy === props.builder.breakout
|
|
const plural = props.builder.rungs > 1 ? 's' : ''
|
|
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'+(plural?'s':'')
|
|
})
|
|
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
td.weight {
|
|
min-width: 5em;
|
|
max-width: 20em;
|
|
padding-left: 0.5em;
|
|
padding-right: 0.5em;
|
|
text-align: right;
|
|
}
|
|
td.label {
|
|
$w: 4em;
|
|
width: $w;
|
|
min-width: $w;
|
|
max-width: $w;
|
|
text-align: right;
|
|
padding-right: .5em;
|
|
}
|
|
.price {
|
|
$w: 6em;
|
|
//width: $w;
|
|
min-width: $w;
|
|
max-width: 10em;
|
|
}
|
|
</style>
|