LimitBuilder as RungBuilder; HLine fixes; cant place orders yet

This commit is contained in:
Tim
2024-04-16 18:50:14 -04:00
parent 65be28fb0a
commit 1a752d3080
4 changed files with 117 additions and 372 deletions

View File

@@ -1,95 +1,60 @@
<template>
<!-- todo extract builder-panel --> <!-- :builder="builder" color-tag="limit" -->
<row-bar :color="builder.color">
<color-band :color="builder.color"/>
<div style="min-width: 3em; font-size: larger" :style="colorStyle"
class="align-self-start ml-2 pt-3">{{ rungs === 1 ? 'Limit' : 'Ladder' }}</div>
<div>
<v-text-field type="number" v-model="rungs"
density="compact" hide-details class="mx-1 my-2" variant="outlined"
label="Rungs"
:color="color" :base-color="color" min="1"
:disabled="!lineAPrice"
style="width: 4.5em;"
/>
</div>
<table>
<tbody>
<template v-if="rungs>1">
<tr>
<td><v-text-field type="number" v-model="lineBPrice" min="0"
density="compact" hide-details class="mx-1 my-2" variant="outlined"
label="Price"
:color="color" :base-color="color"
style="flex: 6em"
/></td>
<td class="weight">{{allocationText(weights[weights.length-1])}}</td>
</tr>
<tr v-for="num in rungs-2" class="ml-5">
<td class="pl-5">{{prices[prices.length-1-num]}}</td>
<td class="weight">{{allocationText(weights[prices.length-1-num])}}</td>
</tr>
</template>
<rung-builder :name="prices.length>1?'Ladder':'Limit'" :order="order" :builder="builder"
v-model:value-a="priceA" v-model:value-b="priceB" :mode="0" :shape="HLine"
:get-model-value="getModelValue" :set-model-value="setModelValue"
:set-values="setPrices" :set-weights="setWeights"
:std-width="stdWidth" :build-tranches="buildTranches">
<table>
<tbody>
<template v-if="prices.length>1">
<tr>
<td><v-text-field type="number" v-model="lineAPrice" min="0"
<td>
<v-text-field type="number" v-model="priceB" min="0"
density="compact" hide-details class="mx-1 my-2" variant="outlined"
label="Price"
:color="color" :base-color="color"
style="flex: 6em"
/>
</td>
<td class="weight">{{ allocationText(weights[weights.length - 1]) }}</td>
</tr>
<tr v-for="i in prices.length-2" class="ml-5">
<td class="pl-5">{{ prices[prices.length-i-1] }}</td>
<td class="weight">{{ allocationText(weights[prices.length-i-1]) }}</td>
</tr>
</template>
<tr>
<td>
<v-text-field type="number" v-model="priceA" min="0"
density="compact" hide-details class="mx-1 my-2" variant="outlined"
label="Price"
:color="color" :base-color="color"
style="flex: 6em"
/></td>
<td class="weight">{{weights.length?allocationText(weights[0]):''}}</td>
</tr>
</tbody>
</table>
<div v-if="co.drawing" class="d-flex align-center pl-3"><v-icon icon="mdi-chat-alert-outline" color="grey" class="mr-1"/>Click the chart!</div>
<div v-if="rungs>1" class="mx-2 d-flex align-center">
<v-slider v-if="rungs>1" direction="vertical" min="-100" max="100" v-model="skew100" class="no-slider-bg ml-2 mr-4" hide-details/>
<v-text-field type="number" v-model="skew100" min="-100" max="100"
density="compact" hide-details variant="outlined" label="Skew" step="5"
:color="color" :base-color="color">
<template v-slot:prepend>
<v-btn icon="mdi-scale-balance" variant="plain" @click="builder.skew=0" :color="color"/>
</template>
</v-text-field>
</div>
<div class="align-self-center">
<v-menu>
<template v-slot:activator="{ props }">
<v-btn variant="plain" v-bind="props" icon="mdi-dots-vertical"/>
</template>
<v-list>
<v-list-subheader :title="'Limit '+ (lineAPrice?lineAPrice.toPrecision(5):'')"/>
<v-list-item title="Delete" key="withdraw" value="withdraw" prepend-icon="mdi-delete" color="red"
@click="deleteBuilder"/>
</v-list>
</v-menu>
</div>
</row-bar>
<!-- <v-col>-->
<!-- <v-btn v-if="!co.drawing" prepend-icon="mdi-ray-vertex" @click="draw" variant="tonal">Draw</v-btn>-->
<!-- <v-btn v-if="co.drawing" prepend-icon="mdi-ray-vertex" @click="cancelDrawing" variant="tonal">Cancel Drawing</v-btn>-->
<!-- </v-col>-->
/>
</td>
<td class="weight">{{ weights.length ? allocationText(weights[0]) : '' }}</td>
</tr>
</tbody>
</table>
</rung-builder>
</template>
<script setup>
import {computed, onBeforeUnmount, onMounted, onUnmounted, onUpdated, watch, watchEffect} from "vue";
import {cancelDrawing, chart} from "@/charts/chart.js";
import {applyLine2, builderDefaults, builderFuncs, useChartOrderStore} from "@/orderbuild.js";
import Color from "color";
import {HLine} from "@/charts/shape.js";
import {lightenColor2, sideColor} from "@/misc.js";
import {applyLine2, builderDefaults, useChartOrderStore} from "@/orderbuild.js";
import {sideColor} from "@/misc.js";
import {useTheme} from "vuetify";
import {useOrderStore} from "@/store/store.js";
import RowBar from "@/components/chart/RowBar.vue";
import ColorBand from "@/components/chart/ColorBand.vue";
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 {chart} from "@/charts/chart.js";
import {HLine} from "@/charts/shape.js";
const s = useStore()
const os = useOrderStore()
const co = useChartOrderStore()
const theme = useTheme().current
const props = defineProps(['order', 'builder'])
const emit = defineEmits(['update:builder'])
function computeDefaultColor() {
const index = props.order.builders.indexOf(props.builder)
@@ -109,18 +74,6 @@ builderDefaults(props.builder, {
color: defaultColor,
})
// validity checks
watchEffect(()=>{
const order = props.order
const builder = props.builder
props.builder.valid =
order && builder &&
builder.rungs >= 1 && builder.priceA &&
(builder.rungs < 2 || builder.priceB)
})
function buildTranches() {
const order = props.order
const builder = props.builder
@@ -133,6 +86,7 @@ function buildTranches() {
const p = ps[i]
const w = ws[i]
const t = newTranche({
// todo start/end
fraction: w * MAX_FRACTION,
})
const symbol = co.selectedSymbol
@@ -144,94 +98,20 @@ function buildTranches() {
}
// todo move this code and the validity check into a supercomponent
let lastId = props.builder.id
builderFuncs[props.builder.id] = buildTranches
onUpdated(()=>{
if (lastId !== props.builder.id ) {
delete builderFuncs[lastId]
builderFuncs[props.builder.id] = buildTranches
lastId = props.builder.id
}
})
onUnmounted(() => delete builderFuncs[lastId])
const skew100 = computed( {
get() {return props.builder.skew*100},
set(v) {props.builder.skew = v/100; }
} )
// we keep two special control lines as the edge of the ranges and then deletable lines in-between
const lineAPrice = computed({
const priceA = computed({
get() { return props.builder.priceA },
set(v) {
props.builder.priceA = v===null ? v : Number(v)
}
})
const lineA = new HLine(
{price:null,color:defaultColor},
function (line) {props.builder.priceA = line.price; props.builder.color = line.color; },
deleteBuilder
)
const lineBPrice = computed({
const priceB = computed({
get() { return props.builder.priceB },
set(v) {
props.builder.priceB = v===null ? v : Number(v)
}
})
const lineB = new HLine(
{price:null,color:props.builder.color.value},
(line)=>{props.builder.priceB = line.price; props.builder.color = line.color; },
deleteBuilder
)
const adjustInteriorLine = (line) => {
props.builder.color = line.color
}
let interiorLines = []
function createInteriorLine(price, lineProps) {
const line = new HLine({price: price, color: props.builder.color}, adjustInteriorLine, deleteBuilder, lineProps)
line.onPoints = function (points) {
const delta = points[0].price - this.model.price
if (delta !== 0) {
props.builder.priceA += delta
props.builder.priceB += delta
}
}
interiorLines.push(line)
line.create()
}
function removeInteriorLine() {
if (interiorLines.length) {
const line = interiorLines.pop()
line.delete()
}
}
const color = computed({
get() {return props.builder.color},
set(v) {
const maxLightness = 60
const c = new Color(v).hsl()
props.builder.color = c.saturation <= maxLightness ? v : c.lightness(maxLightness).string()
}
})
const faintColor = computed(() => lightenColor2(color.value))
const colorStyle = computed(() => {
return {'color': color.value}
})
const start = computed({
get() {return props.builder.start || 0},
set(v) {props.builder.start=v; },
@@ -242,6 +122,18 @@ const end = computed({
set(v) {props.builder.end=v; },
})
const prices = ref([])
const weights = ref([])
function setPrices(ps) {
console.log('setPrices', ps)
prices.value = ps
}
function setWeights(ws) { weights.value = ws }
const color = computed(()=>props.builder.color ? props.builder.color : defaultColor)
function computeRange() {
let range = 0
const series = chart.getSeries()
@@ -260,91 +152,13 @@ function computeRange() {
return range
}
const rungs = computed({
get() {
return props.builder.rungs
},
set(r) {
if (!r) {
props.builder.rungs = 1
return
}
r = Number(r)
props.builder.rungs = r
if ( r > 0 && props.builder.priceB === null ) {
// convert single line to a range
const range = 2 * computeRange()
const mid = props.builder.priceA
props.builder.priceA = mid - range/2
props.builder.priceB = mid + range/2
}
else if ( r === 1 && props.builder.priceB !== null ) {
// convert from a range to a single line
props.builder.priceA = (props.builder.priceA + props.builder.priceB) / 2
props.builder.priceB = null
}
}
})
const prices = computed(()=>{
let a = props.builder.priceA
let b = props.builder.priceB
const r = props.builder.rungs
// console.log('prices for', a, b, r)
if ( a===null || !r ) return [] // no data
if (r===1) return [a] // single line
const spacing = (b-a)/(r-1)
// console.log('spacing', a, b)
const result = []
for( let i=0; i<r; i++ )
result.push(a+i*spacing)
// console.log('prices', result)
return result
})
const weights = computed(() => {
const n = props.builder.rungs
const s = -props.builder.skew
if (n === 1) return [1]
const result = []
if (s === 0) {
// equal weighted
for (let i = 0; i < n; i++)
result.push(1 / n)
} else 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 {
for (let i = 0; i < n; i++)
result.push((1 - s * (2 * i / (n - 1) - 1)) / n)
}
// console.log('weights', result)
return result
})
const colors = computed( ()=> {
const color = props.builder.color !== null ? props.builder.color
: props.buy ? theme.value.colors.success : theme.value.colors.error
const c = new Color(color).rgb()
const ws = weights.value;
const max = Math.max(...ws)
const ns = ws.map((w)=>w/max) // set largest weight to 100%
const adj = ns.map((w)=>c.alpha(Math.pow(w,0.67))) // https://en.wikipedia.org/wiki/Stevens's_power_law
return adj.map((a)=>a.string())
})
const stdWidth = 2*computeRange() // todo make reactive
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
// todo move into misc and use in shape as well
function allocationText(weight) {
const alloc = props.builder.allocation
if (alloc===null) return ''
@@ -354,89 +168,26 @@ function allocationText(weight) {
return `${(w*100).toFixed(1)}% = ${a.toLocaleString('fullwide')} ${amountSymbol.value}`
}
function adjustShapes() {
// this is where all the lines are created or adjusted
// console.log('adjustShapes()')
const limits = prices.value
if (limits.length)
cancelDrawing()
const colorStrings = colors.value
// line properties
const lps = weights.value.map((w)=>{return {text:allocationText(w), textcolor:color.value, showLabel: true}}) // todo make this label innate to HLine with allocation and amount set in the model
if( limits.length === 0 ) {
lineA.delete()
lineB.delete()
for( const line of interiorLines )
line.delete()
interiorLines = []
}
else if (limits.length === 1) {
//
// SINGLE LINE
//
if (!lineA.beingDragged())
lineA.setModel({price:limits[0], color: colorStrings[0]}, lps[0])
lineB.delete()
if (interiorLines.length) {
for( const line of interiorLines )
line.delete()
interiorLines = []
}
}
else {
//
// PRICE RANGE
//
if (!lineA.beingDragged())
lineA.setModel({price:limits[0], color: colorStrings[0]}, lps[0])
if (!lineB.beingDragged()) {
const last = colorStrings.length - 1
lineB.setModel({price: limits[last], color: colorStrings[last]}, lps[last])
}
const numInterior = Math.max(0,limits.length-2);
// trim excess interior lines
while( interiorLines.length > numInterior )
removeInteriorLine()
// adjust the interior line prices and/or add lines
for( let i=0; i<limits.length-2; i++ ) {
const limit = limits[1+i]
if (i === interiorLines.length)
createInteriorLine(limit, lps[1+i])
else if (!interiorLines[i].beingDragged())
interiorLines[i].setModel({price:limit, color: colorStrings[1+i]}, lps[1+i])
}
}
return ''
function getModelValue(model) {
if(!model)
return null
return model.price
}
watchEffect(adjustShapes)
// const autoAdjust = computed(adjustShapes)
function deleteShapes() {
lineA.delete()
lineB.delete()
for (const line of interiorLines)
line.delete()
interiorLines = []
function setModelValue(model, value) {
if (model.price !== value)
model.price = value
}
function deleteBuilder() {
props.order.builders = props.order.builders.filter((b)=>b!==props.builder)
deleteShapes()
function setValues(values) {
prices.value = values
}
if (!props.builder.priceA)
lineA.createOrDraw(); // initiate drawing mode
onMounted(adjustShapes)
onBeforeUnmount(deleteShapes)
function valueFromPoints(points) {
const result = points[0].time;
console.log('valueFromPoints', points, result);
return result
}
</script>