From 5fb499267198bf8442ea7956799a685b6b078e5b Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 7 Feb 2024 03:21:48 -0400 Subject: [PATCH] ladders! --- src/charts/chart.js | 12 +- src/charts/shape.js | 93 +++++--------- src/components/chart/LimitBuilder.vue | 175 +++++++++++++++----------- src/misc.js | 5 + src/orderbuild.js | 10 +- 5 files changed, 154 insertions(+), 141 deletions(-) diff --git a/src/charts/chart.js b/src/charts/chart.js index 4efa817..3553752 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -34,6 +34,7 @@ function initChart() { const co = useChartOrderStore() invokeCallbacks(co.drawingCallbacks, 'onRedraw') }) + useChartOrderStore().chartReady = true } @@ -114,6 +115,9 @@ export function drawShape(shapeType, ...callbacks) { export function createShape(shapeType, points, options, ...callbacks) { + const co = useChartOrderStore() + co.drawingCallbacks = null + co.drawing = false options.shape = shapeType.code // programatically adds a shape to the chart let shapeId @@ -126,6 +130,8 @@ export function createShape(shapeType, points, options, ...callbacks) { if( callbacks.length ) shapeCallbacks[shapeId] = callbacks console.log('created shape', shapeId) + const props = chart.getShapeById(shapeId).getProperties() + invokeCallbacks(callbacks, 'onCreate', shapeId, points, props) return shapeId } @@ -194,14 +200,14 @@ function handleDrawingEvent(id, event) { } else if (event === 'points_changed') { if (id in shapeCallbacks) { const points = shape.getPoints() - console.log('points', points) + // console.log('points', points) invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points) } } else if (event === 'properties_changed') { console.log('id in shapes', id in shapeCallbacks, id, shapeCallbacks) if (id in shapeCallbacks) { const props = shape.getProperties() - console.log('props', props) + // console.log('props', props) invokeCallbacks(shapeCallbacks[id], 'onProps', id, props) } } else if (event === 'move') { @@ -228,7 +234,7 @@ function handleDrawingEvent(id, event) { console.log('unknown drawing event', event) } -export function deleteShape(id) { +export function deleteShapeId(id) { if( id in shapeCallbacks ) delete shapeCallbacks[id] console.log('removing entity', id) diff --git a/src/charts/shape.js b/src/charts/shape.js index 88436c8..4ef4c7d 100644 --- a/src/charts/shape.js +++ b/src/charts/shape.js @@ -1,7 +1,7 @@ // noinspection JSPotentiallyInvalidUsageOfThis import {invokeCallback} from "@/common.js"; -import {chart, createShape, deleteShape, drawShape} from "@/charts/chart.js"; +import {chart, createShape, deleteShapeId, drawShape} from "@/charts/chart.js"; // @@ -48,7 +48,7 @@ function updatePoints(shape, points) { function updateProps(shape, props) { shape.lock++ try { - chart.getShapeById(shape.id).setProps(props) + chart.getShapeById(shape.id).setProperties(props) } finally { shape.lock-- @@ -56,18 +56,6 @@ function updateProps(shape, props) { } -function delShape(shape) { - shape.lock++ - try { - deleteShape(shape.id) - } finally { - shape.lock-- - } - shape.lock = true - shape.lock = false -} - - class Shape { constructor(type, model={}, onModel=null, onDelete=null) { // the Shape object manages synchronizing internal data with a corresponding TradingView shape @@ -97,6 +85,7 @@ class Shape { } create() { + if (this.id !== null) return // programatically create the shape using the current this.points const points = this.pointsFromModel() if( points && points.length ) { @@ -106,7 +95,8 @@ class Shape { } doCreate(points) { - createShape(this.type, points, new ShapeTVCallbacks(this)) + const props = this.propsFromModel() + createShape(this.type, points, {overrides:props}, new ShapeTVCallbacks(this)) } createOrDraw() { @@ -122,25 +112,18 @@ class Shape { setModel(model) { for( const [k,v] of Object.entries(model)) this.model[k] = v - this.setPointsIfDirty(this.pointsFromModel()); + this.setPoints(this.pointsFromModel()); this.setPropsIfDirty(this.propsFromModel()) } setPoints(points) { invokeCallback(this, 'onPoints', points) - if (points !== null && points.length) { - if (this.id) { - updatePoints(this, points) - } else + if (points !== null && points.length) + if (this.id === null) this.doCreate(points) - } - } - - - setPointsIfDirty(points) { - if (dirtyPoints(this.points, points)) - this.setPoints(points) + else + updatePoints(this, points) } @@ -159,9 +142,16 @@ class Shape { delete() { - if (this.id!==null) { - delShape(this) + console.log('shape.delete', this.id) + if (this.id === null) return + this.lock++ + try { + deleteShapeId(this.id) this.id = null + } catch (e) { + throw e + } finally { + this.lock-- } } @@ -198,23 +188,6 @@ class Shape { } -function dirtyPoints(pointsA, pointsB) { - if (pointsA === null) - return pointsB !== null - if (pointsB === null) - return true - if (pointsA.length !== pointsB.length) - return true - for (let i = 0; i < pointsA.length; i++) { - const a = pointsA[i]; - const b = pointsB[i]; - if (a.time !== b.time || a.price !== b.price) - return true - } - return false -} - - // B is modifying A function dirtyProps(propsA, propsB) { if (propsB===null) @@ -235,29 +208,28 @@ class ShapeTVCallbacks { } onCreate(shapeId, points, props) { + if( this.shape.id ) + throw Error(`Created a shape ${shapeId}where one already existed ${this.shape.id}`) + this.shape.id = shapeId + console.log('set shape id', this.shape) if( this.shape.lock ) return - if( this.id ) - throw Error(`Created a shape ${shapeId}where one already existed ${this.id}`) - this.shape.id=shapeId invokeCallback(this.shape, 'onCreate', points, props) } onPoints(shapeId, points) { + this.shape.points = points if( this.shape.lock ) return - if( dirtyPoints(this.shape.points, points) ) { - this.shape.points = points - this.shape.pointsToModel() - this.shape.onModel(this.shape.model) - } + this.shape.onPoints(points) + this.shape.pointsToModel() + this.shape.onModel(this.shape.model) } onProps(shapeId, props) { + this.shape.props = props if( this.shape.lock ) return - if( dirtyProps(this.shape.props, props) ) { - this.shape.props = props - this.shape.propsToModel() - this.shape.onModel(this.shape.model) - } + this.shape.onProps(props) + this.shape.propsToModel() + this.shape.onModel(this.shape.model) } onDraw() { @@ -301,6 +273,7 @@ class ShapeTVCallbacks { } onDelete(_shapeId, props) { + this.shape.id = null if( this.shape.lock ) return invokeCallback(this.shape, 'onDelete',props) } @@ -314,7 +287,7 @@ export class HLine extends Shape { pointsFromModel() { if (this.model.price === null) return null - if (this.points.length > 0) + if (this.points !== null && this.points.length > 0) return [{time:this.points[0].time, price:this.model.price}] return [{time:0, price:this.model.price}] } diff --git a/src/components/chart/LimitBuilder.vue b/src/components/chart/LimitBuilder.vue index a00288c..19cc143 100644 --- a/src/components/chart/LimitBuilder.vue +++ b/src/components/chart/LimitBuilder.vue @@ -36,52 +36,12 @@ import {chart} from "@/charts/chart.js"; import {useChartOrderStore} from "@/orderbuild.js"; import Color from "color"; import {HLine, ShapeType} from "@/charts/shape.js"; +import {builderDefaults} from "@/misc.js"; const co = useChartOrderStore() const props = defineProps(['builder']) -// we keep two special control lines as the edge of the ranges and then deletable lines in-between - -const lineAPrice = computed({ - get() { return props.builder.priceA }, - set(v) { props.builder.priceA = v===null ? v : Number(v) - adjustShapes() -}}) - -const lineA = new HLine( - {price:null,color:null}, - (line)=>{props.builder.priceA = line.price; props.builder.color = line.color}, - deleteBuilder -) - -const lineBPrice = computed({ - get() { return props.builder.priceB }, - set(v) { props.builder.priceB = v===null ? v : Number(v) - adjustShapes() -}}) -const lineB = new HLine( - {price:null,color:null}, - (line)=>{props.builder.priceB = line.price;props.builder.color = line.color}, - deleteBuilder -) - -let interiorLines = [] - -function createInteriorLine(price) { - const line = new HLine({price:price}) // todo - interiorLines.push(line) - line.create() -} - - -function builderDefaults( builder, defaults ) { - for ( const k in defaults ) - if (builder[k] === undefined) - builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k] -} - - // Fields must be defined in order to be reactive builderDefaults(props.builder, { start: null, // todo @@ -92,6 +52,65 @@ builderDefaults(props.builder, { color: null, }) +// we keep two special control lines as the edge of the ranges and then deletable lines in-between + +const lineAPrice = computed({ + get() { return props.builder.priceA }, + set(v) { + props.builder.priceA = v===null ? v : Number(v) + adjustShapes() + } +}) + +const lineA = new HLine( + {price:null,color:null}, + (line)=>{props.builder.priceA = line.price; props.builder.color = line.color; adjustShapes()}, + deleteBuilder +) +lineA.onMove = (points)=>console.log('move', points) + +const lineBPrice = computed({ + get() { return props.builder.priceB }, + set(v) { + props.builder.priceB = v===null ? v : Number(v) + adjustShapes() + } +}) + +const lineB = new HLine( + {price:null,color:null}, + (line)=>{props.builder.priceB = line.price; props.builder.color = line.color; adjustShapes()}, + deleteBuilder +) + + +let interiorLines = [] + +function createInteriorLine(price) { + const line = new HLine({price: price, color: props.builder.color}, + function (line) {props.builder.color = line.color}, + deleteBuilder) + line.onPoints = function (points) { + const delta = points[0].price - this.model.price + console.log('moving delta', delta) + if (delta != 0) { + props.builder.priceA += delta + props.builder.priceB += delta + adjustShapes() + } + } + interiorLines.push(line) + line.create() +} + + +function removeInteriorLine() { + if (interiorLines.length) { + const line = interiorLines.pop() + line.delete() + } +} + const color = computed(() => { // todo saturate the color when the corresponding shape is selected https://github.com/Qix-/color @@ -138,6 +157,18 @@ const rungs = computed({ set(r) { r = Number(r) props.builder.rungs = r + if ( r > 0 && props.builder.priceB === null ) { + // convert single line to a range + const range = 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 + } adjustShapes() } }) @@ -147,24 +178,27 @@ 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 Math.max(0,limits.length-2) ) - interiorLines.pop() - // now adjust the interior lines - for( let i=0; i numInterior ) + removeInteriorLine() + + // adjust the interior line prices and/or add lines + for( let i=0; i new Promise(r => setTimeout(r, ms)) +export function builderDefaults(builder, defaults) { + for (const k in defaults) + if (builder[k] === undefined) + builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k] +} \ No newline at end of file diff --git a/src/orderbuild.js b/src/orderbuild.js index 91bd967..c999e4e 100644 --- a/src/orderbuild.js +++ b/src/orderbuild.js @@ -16,6 +16,8 @@ function TrancheBuilder( component, options = {}) { export const useChartOrderStore = defineStore('chart_orders', () => { + const chartReady = ref(false) + const builderIdList = ref([]) // this is where we keep the UI ordering const builderList = computed(()=>{ console.log('builder list', builderIdList.value.map((id)=>builderDict.value[id])) @@ -32,18 +34,12 @@ export const useChartOrderStore = defineStore('chart_orders', () => { builderDict.value[b.id] = b } - function touchBuilder(builder) { - // noinspection SillyAssignmentJS - builderIdList.value = builderIdList.value - builderDict.value[builder.id] = builder - } - function removeBuilder(builder) { builderIdList.value = builderIdList.value.filter((v)=>v!==builder.id) delete builderDict.value[builder.id] } - return { builderList, builderDict, drawing, drawingCallbacks, addBuilder, removeBuilder, touchBuilder } + return { chartReady, builderList, builderDict, drawing, drawingCallbacks, addBuilder, removeBuilder, } })