Files
web/src/components/chart/LimitBuilder.vue
2025-04-22 16:15:14 -04:00

246 lines
6.6 KiB
Vue

<template>
<rung-builder :name="(builder.breakout?(order.buy?'Breakout':'Breakdown'):'Limit')+(builder.rungs>1?' Ladder':'')"
:description="description"
:order="order" :builder="builder"
v-model="priceEndpoints" :mode="0" :flip="flipped"
:shape="HLine"
:get-model-value="getModelValue" :set-model-value="setModelValue"
:set-values="setPrices" :set-weights="setWeights"
:std-width="stdWidth" :build-tranches="buildTranches">
<table class="rb">
<tbody>
<template v-if="prices.length>1">
<tr>
<td>
<v-text-field type="number" v-model="higherPrice" min="0"
density="compact" hide-details class="mx-1 my-2" variant="outlined"
label="Price"
style="flex: 6em"
/>
</td>
<td class="weight" style="vertical-align: bottom">{{ allocationTexts[higherIndex] }}</td>
</tr>
<tr v-for="i in innerIndexes" class="ml-5">
<td class="pl-5">{{ toPrecision(prices[i],6) }}</td>
<td class="weight">{{ allocationTexts[i] }}</td>
</tr>
</template>
<tr>
<td>
<v-text-field type="number" v-model="lowerPrice" min="0"
density="compact" hide-details class="mx-1 my-2" variant="outlined"
label="Price"
style="flex: 6em"
/>
</td>
<td class="weight">{{ weights.length > 1 ? allocationTexts[lowerIndex] : '' }}</td>
</tr>
</tbody>
</table>
<one-time-hint name="click-chart" activator="#tv-widget" location="center"
:when="priceA===null" text="Click the chart!"
:on-complete="()=>track('click-chart')"
/>
</rung-builder>
</template>
<script setup>
import {applyLinePoint, builderDefaults, useChartOrderStore} from "@/orderbuild.js";
import {sideColor} from "@/misc.js";
import {useOrderStore, useStore} from "@/store/store.js";
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
import RungBuilder from "@/components/chart/RungBuilder.vue";
import {computed, ref} from "vue";
import {allocationText, HLine} from "@/charts/shape.js";
import OneTimeHint from "@/components/OneTimeHint.vue";
import {track} from "@/track.js";
import {toPrecision, toPrecisionOrNull} from "@/misc.js";
const s = useStore()
const os = useOrderStore()
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()
// Fields must be defined in order to be reactive
builderDefaults(props.builder, {
start: null, // todo
end: null, // todo
priceA: null,
priceB: null,
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, tranches)
const ps = prices.value
const ws = weights.value
for(let i=0; i<ps.length; i++) {
let p = ps[i]
const w = ws[i]
const t = newTranche({
fraction: w * MAX_FRACTION,
})
const symbol = co.selectedSymbol
// console.log('symbol', symbol, p)
applyLinePoint(t, symbol, order.buy, p, builder.breakout)
tranches.push(t)
}
if (!flipped.value)
tranches.reverse()
return {tranches, warnings}
}
const priceA = computed({
get() { return priceEndpoints.value[0] },
set(v) {
if (v!==null)
v = Number(v)
update(v, priceEndpoints.value[1])
}
})
const priceB = computed({
get() { return priceEndpoints.value[1] },
set(v) {
if (v!==null)
v = Number(v)
update(priceEndpoints.value[0], v)
}
})
const _priceEndpoints = ref([props.builder.priceA, props.builder.priceB])
const priceEndpoints = computed({
get() { return _priceEndpoints.value},
set(v) {
const [a, b] = v
update(a,b)
}
})
function update(a, b) {
_priceEndpoints.value = [a, b]
props.builder.priceA = a
props.builder.priceB = b
}
const flipped = computed(()=>{
const a = props.builder.priceA
const b = props.builder.priceB
return a !== null && b !== null && a > b
})
const higherPrice = computed({
get() { return toPrecisionOrNull(flipped.value ? priceA.value : priceB.value, 6) },
set(v) {
if (flipped.value)
priceA.value = v
else
priceB.value = v
}
})
const higherIndex = computed(()=>flipped.value ? 0 : weights.value.length-1)
const lowerIndex = computed(()=>!flipped.value ? 0 : weights.value.length-1)
const innerIndexes = computed(()=>{
const n = prices.value.length
const f = flipped.value
const result = []
for (let i=1; i<n-1; i++)
result.push(f?i:n-1-i)
return result
})
const lowerPrice = computed({
get() {return toPrecisionOrNull(!flipped.value ? priceA.value : priceB.value, 6)},
set(v) {
if (!flipped.value)
priceA.value = v
else
priceB.value = v
}
})
const start = computed({
get() {return props.builder.start || 0},
set(v) {props.builder.start=v; },
})
const end = computed({
get() {return props.builder.end || 0},
set(v) {props.builder.end=v; },
})
const prices = ref([])
const weights = ref([])
function setPrices(ps) {prices.value = ps}
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 color = computed(()=>props.builder.color ? props.builder.color : defaultColor)
const stdWidth = computed(()=>co.meanRange)
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
})
function getModelValue(model) {
if(!model)
return null
return model.price
}
function setModelValue(model, value) {
// console.log('setModelValue->', model.price, value)
if (model.price !== value)
model.price = value
}
</script>
<style scoped lang="scss">
td.weight {
width: 11em;
padding-left: 0.5em;
padding-right: 0.5em;
text-align: right;
}
table.rb {
padding: 0;
border-spacing: 0;
tbody {
border: none;
padding: 0;
}
td {
vertical-align: top;
}
}
</style>