massive Shape rework: keep both tvPoints/Props and ourPoints/Props; delegate model updates to subclasses; DCA/VLine working but Ladder/HLine not done.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import {useChartOrderStore} from "@/orderbuild.js";
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {invokeCallbacks, prototype} from "@/common.js";
|
import {invokeCallbacks, prototype} from "@/common.js";
|
||||||
import {DataFeed, initFeeDropdown, lookupSymbol} from "@/charts/datafeed.js";
|
import {DataFeed, initFeeDropdown, lookupSymbol} from "@/charts/datafeed.js";
|
||||||
|
import {intervalToSeconds} from "@/misc.js";
|
||||||
|
|
||||||
export let widget = null
|
export let widget = null
|
||||||
export let chart = null
|
export let chart = null
|
||||||
@@ -24,6 +25,11 @@ function changeSymbol(symbol) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function changeInterval(interval, _timeframe) {
|
||||||
|
useChartOrderStore().intervalSecs = intervalToSeconds(interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* TradingView event keystrings
|
/* TradingView event keystrings
|
||||||
const subscribeEvents = [
|
const subscribeEvents = [
|
||||||
'toggle_sidebar', 'indicators_dialog', 'toggle_header', 'edit_object_dialog', 'chart_load_requested',
|
'toggle_sidebar', 'indicators_dialog', 'toggle_header', 'edit_object_dialog', 'chart_load_requested',
|
||||||
@@ -69,8 +75,10 @@ function initChart() {
|
|||||||
console.log('init chart')
|
console.log('init chart')
|
||||||
chart = widget.activeChart()
|
chart = widget.activeChart()
|
||||||
chart.crossHairMoved().subscribe(null, (point)=>setTimeout(()=>handleCrosshairMovement(point),0) )
|
chart.crossHairMoved().subscribe(null, (point)=>setTimeout(()=>handleCrosshairMovement(point),0) )
|
||||||
chart.onSymbolChanged().subscribe(null, changeSymbol);
|
chart.onSymbolChanged().subscribe(null, changeSymbol)
|
||||||
|
chart.onIntervalChanged().subscribe(null, changeInterval)
|
||||||
changeSymbol(chart.symbolExt())
|
changeSymbol(chart.symbolExt())
|
||||||
|
changeInterval(widget.symbolInterval().interval)
|
||||||
useChartOrderStore().chartReady = true
|
useChartOrderStore().chartReady = true
|
||||||
console.log('chart ready')
|
console.log('chart ready')
|
||||||
}
|
}
|
||||||
@@ -242,7 +250,6 @@ function drawingEventWorker() {
|
|||||||
const queue = drawingEventQueue
|
const queue = drawingEventQueue
|
||||||
drawingEventQueue = []
|
drawingEventQueue = []
|
||||||
propsEvents = {}
|
propsEvents = {}
|
||||||
// console.log('events locked', queue)
|
|
||||||
for (const [id, event] of queue) {
|
for (const [id, event] of queue) {
|
||||||
if (event === 'properties_changed')
|
if (event === 'properties_changed')
|
||||||
propsEvents[id] = event
|
propsEvents[id] = event
|
||||||
@@ -259,8 +266,18 @@ function drawingEventWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function doHandleDrawingEvent(id, event) {
|
function doHandleDrawingEvent(id, event) {
|
||||||
// console.log('drawing event', id, event)
|
console.log('drawing event', id, event)
|
||||||
const shape = event === 'remove' ? null : chart.getShapeById(id);
|
let shape
|
||||||
|
if (event==='remove')
|
||||||
|
shape = null
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
shape = chart.getShapeById(id)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if (event === 'create') {
|
if (event === 'create') {
|
||||||
const co = useChartOrderStore();
|
const co = useChartOrderStore();
|
||||||
const callbacks = co.drawingCallbacks
|
const callbacks = co.drawingCallbacks
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import {invokeCallback, mixin} from "@/common.js";
|
import {invokeCallback, mixin} from "@/common.js";
|
||||||
import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape, widget} from "@/charts/chart.js";
|
import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape, widget} from "@/charts/chart.js";
|
||||||
import {unique} from "@/misc.js";
|
import {unique} from "@/misc.js";
|
||||||
|
import {useChartOrderStore} from "@/orderbuild.js";
|
||||||
|
import model from "color";
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -37,34 +39,96 @@ export const ShapeType = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Shape {
|
export class Shape {
|
||||||
constructor(type, model={}, onModel=null, onDelete=null, props=null) {
|
|
||||||
|
constructor(type, onModel=null, onDelete=null, props=null) {
|
||||||
// the Shape object manages synchronizing internal data with a corresponding TradingView shape
|
// the Shape object manages synchronizing internal data with a corresponding TradingView shape
|
||||||
|
|
||||||
|
// each shape in the class hierarchy overrides setModel() to cause effects in TradingView
|
||||||
|
// TV callbacks are returned to the on*() handlers, primarily onPoints() and onProps(). Handlers
|
||||||
|
// of TV callbacks should call updateModel() with just the kv's they want to change. Any changes
|
||||||
|
// are then passed onto the onModel() callback.
|
||||||
|
|
||||||
|
// the model object has various attributes defined by each shape subclass. each subclass's constructor
|
||||||
|
// must call setModel(model) after first calling super.constructor() and optionally declaring any model
|
||||||
|
// defaults on this.model.
|
||||||
|
|
||||||
|
// Shape: {
|
||||||
|
// lineColor, textColor, color, // lineColor and textColor default to color
|
||||||
|
// }
|
||||||
|
|
||||||
|
this.debug = false
|
||||||
this.id = null // TradingView shapeId, or null if no TV shape created yet (drawing mode)
|
this.id = null // TradingView shapeId, or null if no TV shape created yet (drawing mode)
|
||||||
this.type = type // ShapeType
|
this.type = type // ShapeType
|
||||||
this.model = model // subclass-specific
|
this.model = {} // set to nothing at first
|
||||||
this.points = null
|
this.ourPoints = null
|
||||||
this.points = this.pointsFromModel()
|
this.tvPoints = null
|
||||||
// console.log('construct points', this.points)
|
this.ourProps = {}
|
||||||
this.props = props === null ? this.propsFromModel() : mixin(props, this.propsFromModel())
|
if (props !== null)
|
||||||
|
this.ourProps = mixin(props, this.ourProps)
|
||||||
|
this.tvProps = null
|
||||||
if (onModel !== null)
|
if (onModel !== null)
|
||||||
this.onModel = onModel
|
this.onModel = onModel
|
||||||
if (onDelete !== null )
|
if (onDelete !== null )
|
||||||
this.onDelete = onDelete
|
this.onDelete = onDelete
|
||||||
this.create()
|
|
||||||
|
// Model values handled by this base class
|
||||||
|
this.model.color = null
|
||||||
|
this.model.lineColor = null
|
||||||
|
this.model.textColor = null
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// primary interface methods
|
// primary interface methods
|
||||||
//
|
//
|
||||||
|
|
||||||
|
setModel(model) {
|
||||||
|
if (model.textColor || model.lineColor) {
|
||||||
|
if (model.textColor)
|
||||||
|
this.model.textColor = model.textColor
|
||||||
|
if (model.lineColor)
|
||||||
|
this.model.lineColor = model.lineColor
|
||||||
|
this.setProps(this.colorProps())
|
||||||
|
}
|
||||||
|
// todo text
|
||||||
|
}
|
||||||
|
|
||||||
|
onModel(model, changedKeys) {} // model was changed by a TradingView user action
|
||||||
|
|
||||||
|
updateModel(changes) {
|
||||||
|
if (this.debug) console.log('updateModel', this.id, changes)
|
||||||
|
const changedKeys = []
|
||||||
|
for (const k of Object.keys(changes)) {
|
||||||
|
if( changes[k] !== undefined && changes[k] !== this.model[k] ) {
|
||||||
|
changedKeys.push(k)
|
||||||
|
this.model[k] = changes[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changedKeys.length)
|
||||||
|
this.onModel(this.model, changedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
colorProps() {
|
||||||
|
if (!this.model.color&&!this.model.lineColor&&!this.model.textColor) return null
|
||||||
|
const o = {}
|
||||||
|
const p = this.type.drawingProp
|
||||||
|
const lc = this.model.lineColor ? this.model.lineColor : this.model.color;
|
||||||
|
const tc = this.model.textColor ? this.model.textColor : this.model.color;
|
||||||
|
if (lc)
|
||||||
|
o[p+".linecolor"] = lc
|
||||||
|
if (tc)
|
||||||
|
o[p+".textcolor"] = tc
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
// have the user draw this shape
|
// have the user draw this shape
|
||||||
console.log(`draw ${this.type.name}`, this.model)
|
if (this.debug) console.log(`draw ${this.type.name}`, this.model)
|
||||||
if (this.id)
|
if (this.id)
|
||||||
throw Error(`Shape already exists ${this.id}`)
|
throw Error(`Shape already exists ${this.id}`)
|
||||||
const or = this.drawingOverrides();
|
const or = this.drawingOverrides();
|
||||||
// console.log('drawing overrides', or)
|
// if (this.debug) console.log('drawing overrides', or)
|
||||||
widget.applyOverrides(or)
|
widget.applyOverrides(or)
|
||||||
drawShape(this.type, new ShapeTVCallbacks(this))
|
drawShape(this.type, new ShapeTVCallbacks(this))
|
||||||
}
|
}
|
||||||
@@ -72,30 +136,28 @@ class Shape {
|
|||||||
// return an object with property defaults, e.g. { "linetoolhorzline.linecolor": "#7f11e0" }
|
// return an object with property defaults, e.g. { "linetoolhorzline.linecolor": "#7f11e0" }
|
||||||
// https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library#drawingoverrides
|
// https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library#drawingoverrides
|
||||||
drawingOverrides() {
|
drawingOverrides() {
|
||||||
if (this.model.color===null) return null
|
return this.colorProps()
|
||||||
const o = {}
|
|
||||||
const p = this.type.drawingProp
|
|
||||||
o[p+".linecolor"] = this.model.color
|
|
||||||
o[p+".textcolor"] = this.model.color
|
|
||||||
return o
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
if (this.id !== null) return
|
if (this.id !== null) return
|
||||||
// programatically create the shape using the current this.points
|
// programatically create the shape using the current this.points
|
||||||
if( this.points && this.points.length ) {
|
if( this.ourPoints && this.ourPoints.length ) {
|
||||||
this.doCreate()
|
this.doCreate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doCreate() {
|
doCreate() {
|
||||||
createShape(this.type, this.points, {overrides:this.props}, new ShapeTVCallbacks(this))
|
// createShape(this.type, this.points, {overrides:this.props}, new ShapeTVCallbacks(this))
|
||||||
|
this.tvPoints = [...this.ourPoints]
|
||||||
|
this.id = createShape(this.type, this.ourPoints, {overrides:this.ourProps}, new ShapeTVCallbacks(this))
|
||||||
|
if (this.debug) console.log('created', this.type.name, this.ourPoints, this.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
createOrDraw() {
|
createOrDraw() {
|
||||||
if(this.id) return
|
if(this.id) return
|
||||||
if(this.points && this.points.length)
|
if(this.ourPoints && this.ourPoints.length)
|
||||||
this.doCreate(this.points)
|
this.doCreate(this.ourPoints)
|
||||||
else
|
else
|
||||||
this.draw()
|
this.draw()
|
||||||
}
|
}
|
||||||
@@ -106,36 +168,25 @@ class Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setModel(model, props=null) {
|
|
||||||
for( const [k,v] of Object.entries(model))
|
|
||||||
this.model[k] = v
|
|
||||||
this.setPointsIfDirty(this.pointsFromModel());
|
|
||||||
let p
|
|
||||||
const mp = this.propsFromModel();
|
|
||||||
if (props===null) {
|
|
||||||
p = mp
|
|
||||||
if (p===null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else if (mp !== null)
|
|
||||||
p = mixin(props, mp)
|
|
||||||
this.setPropsIfDirty(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setPoints(points) {
|
setPoints(points) {
|
||||||
this.points = points
|
// setting points to null will delete the shape from the chart. setting points to a valid value will cause the
|
||||||
|
// shape to be drawn.
|
||||||
|
if (this.debug) console.log('setPoints', points)
|
||||||
|
this.ourPoints = points
|
||||||
if (points === null || !points.length)
|
if (points === null || !points.length)
|
||||||
this.delete()
|
this.delete()
|
||||||
else {
|
else {
|
||||||
if (this.id === null)
|
if (this.id === null)
|
||||||
this.doCreate()
|
this.doCreate()
|
||||||
else {
|
else if (dirtyPoints(this.tvPoints, points)) {
|
||||||
const s = this.tvShape();
|
const s = this.tvShape();
|
||||||
if (!dragging) {
|
if (this.debug) console.log('adjusting tv points', s, this.tvPoints, points)
|
||||||
|
if (!this.beingDragged()) {
|
||||||
|
if (this.debug) console.log('not dragging. use setPoints.')
|
||||||
s.setPoints(points)
|
s.setPoints(points)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (this.debug) console.log('dragging. use QUIET setPoints.')
|
||||||
// quiet setPoints doesn't disturb tool editing mode
|
// quiet setPoints doesn't disturb tool editing mode
|
||||||
const i = s._pointsConverter.apiPointsToDataSource(points)
|
const i = s._pointsConverter.apiPointsToDataSource(points)
|
||||||
// s._model.startChangingLinetool(this._source)
|
// s._model.startChangingLinetool(this._source)
|
||||||
@@ -147,67 +198,39 @@ class Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPoints(points) {} // the control points of an existing shape were changed
|
||||||
setPointsIfDirty(points) {
|
|
||||||
if (dirtyPoints(this.points, points))
|
|
||||||
this.setPoints(points)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setProps(props) {
|
setProps(props) {
|
||||||
if(this.id)
|
if (!props || Object.keys(props).length===0) return
|
||||||
this.tvShape().setProperties(props)
|
if (this.debug) console.log('setProps', props)
|
||||||
|
this.ourProps = mixin(props, this.ourProps)
|
||||||
|
if(this.id) {
|
||||||
|
const p = dirtyItems(this.tvProps, props)
|
||||||
|
this.tvProps = this.tvProps === null ? p : mixin(p, this.tvProps)
|
||||||
|
this.tvShape().setProperties(p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onProps(props) { // the display properties of an existing shape were changed
|
||||||
setPropsIfDirty(props) {
|
this.updateModel({lineColor:props.linecolor, textColor:props.textcolor})
|
||||||
// console.log('dirtyProps', this.props, props, dirtyProps(this.props, props))
|
|
||||||
if( dirtyProps(this.props, props) )
|
|
||||||
this.setProps(props)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
beingDragged() {
|
beingDragged() {
|
||||||
return draggingShapeIds.indexOf(this.id) !== -1
|
return draggingShapeIds.indexOf(this.id) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
// console.log('shape.delete', this.id)
|
if (this.debug) console.log('shape.delete', this.id)
|
||||||
|
this.ourPoints = null
|
||||||
if (this.id === null) return
|
if (this.id === null) return
|
||||||
this.lock++
|
deleteShapeId(this.id)
|
||||||
try {
|
this.id = null
|
||||||
deleteShapeId(this.id)
|
|
||||||
this.id = null
|
|
||||||
} finally {
|
|
||||||
this.lock--
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Model synchronization
|
// Overridable shape callbacks initiated by TradingView
|
||||||
//
|
|
||||||
|
|
||||||
onDirtyModel(toModel) {
|
|
||||||
const old = {...this.model}
|
|
||||||
toModel.call(this)
|
|
||||||
const dirty = dirtyKeys(old, this.model)
|
|
||||||
// console.log('onDirtyModel', old, this.model, dirty)
|
|
||||||
if (dirty.length)
|
|
||||||
this.onModel(this.model)
|
|
||||||
}
|
|
||||||
|
|
||||||
pointsFromModel() {return null}
|
|
||||||
pointsToModel() {} // set the model using this.points
|
|
||||||
propsFromModel() {return null}
|
|
||||||
propsToModel() {} // set the model using this.props
|
|
||||||
|
|
||||||
onModel(model) {} // called whenever points or props updates the model dictionary
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Overridable shape callbacks
|
|
||||||
//
|
//
|
||||||
|
|
||||||
onDraw() {} // start drawing a new shape for the builder
|
onDraw() {} // start drawing a new shape for the builder
|
||||||
@@ -215,8 +238,6 @@ class Shape {
|
|||||||
onUndraw() {} // drawing was canceled by clicking on a different tool
|
onUndraw() {} // drawing was canceled by clicking on a different tool
|
||||||
onAddPoint() {} // the user clicked a point while drawing (that point is added to the points list)
|
onAddPoint() {} // the user clicked a point while drawing (that point is added to the points list)
|
||||||
onCreate(points, props) {} // the user has finished creating all the control points. drawing mode is exited and the initial shape is created.
|
onCreate(points, props) {} // the user has finished creating all the control points. drawing mode is exited and the initial shape is created.
|
||||||
onPoints(points) {} // the control points of an existing shape were changed
|
|
||||||
onProps(props) {} // the display properties of an existing shape were changed
|
|
||||||
onMove(points) {} // the shape was moved by dragging a drawing element not the control point
|
onMove(points) {} // the shape was moved by dragging a drawing element not the control point
|
||||||
onDrag(points) {}
|
onDrag(points) {}
|
||||||
onHide(props) {}
|
onHide(props) {}
|
||||||
@@ -226,7 +247,6 @@ class Shape {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function dirtyPoints(pointsA, pointsB) {
|
function dirtyPoints(pointsA, pointsB) {
|
||||||
if (pointsB===null)
|
if (pointsB===null)
|
||||||
return pointsA !== null
|
return pointsA !== null
|
||||||
@@ -243,21 +263,6 @@ function dirtyPoints(pointsA, pointsB) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// B is modifying A
|
|
||||||
function dirtyProps(propsA, propsB) {
|
|
||||||
if (propsB===null)
|
|
||||||
return propsA !== null
|
|
||||||
const entries = Object.entries(propsB);
|
|
||||||
if (propsA===null)
|
|
||||||
return entries.length > 0
|
|
||||||
for( const [k,v] of entries)
|
|
||||||
if ( !(k in propsA) || propsA[k] !== v )
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// B is modifying A
|
// B is modifying A
|
||||||
function dirtyKeys(propsA, propsB) {
|
function dirtyKeys(propsA, propsB) {
|
||||||
if (propsB===null)
|
if (propsB===null)
|
||||||
@@ -265,7 +270,15 @@ function dirtyKeys(propsA, propsB) {
|
|||||||
if (propsA===null)
|
if (propsA===null)
|
||||||
return [...Object.keys(propsB)]
|
return [...Object.keys(propsB)]
|
||||||
const keys = unique([...Object.keys(propsA), ...Object.keys(propsB)])
|
const keys = unique([...Object.keys(propsA), ...Object.keys(propsB)])
|
||||||
return keys.filter((k)=> !(k in propsA) || propsA[k] !== propsB[k])
|
return keys.filter((k)=> !(k in propsA) || propsA[k] !== undefined && propsA[k] !== propsB[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function dirtyItems(a, b) {
|
||||||
|
const result = {}
|
||||||
|
for (const k of dirtyKeys(this.tvProps, props))
|
||||||
|
result[k] = props[k]
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -278,113 +291,173 @@ class ShapeTVCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onCreate(shapeId, _tvShape, points, props) {
|
onCreate(shapeId, _tvShape, points, props) {
|
||||||
if( this.shape.id )
|
|
||||||
throw Error(`Created a shape ${shapeId} where one already existed ${this.shape.id}`)
|
|
||||||
this.shape.id = shapeId
|
|
||||||
if( this.shape.lock ) return
|
|
||||||
this.creating = true
|
this.creating = true
|
||||||
invokeCallback(this.shape, 'onCreate', points, props)
|
invokeCallback(this.shape, 'onCreate', points, props)
|
||||||
}
|
}
|
||||||
|
|
||||||
onPoints(shapeId, _tvShape, points) {
|
onPoints(shapeId, _tvShape, points) {
|
||||||
this.shape.points = points
|
if (this.shape.debug) console.log('tvcb onPoints', points)
|
||||||
|
this.shape.tvPoints = points
|
||||||
this.shape.onPoints(points)
|
this.shape.onPoints(points)
|
||||||
this.shape.onDirtyModel(this.shape.pointsToModel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onProps(shapeId, _tvShape, props) {
|
onProps(shapeId, _tvShape, props) {
|
||||||
// console.log('onProps', props)
|
// if (this.shape.debug) console.log('tvOnProps', props)
|
||||||
if (this.creating) {
|
if (this.creating) { // todo still useful?
|
||||||
this.creating = false
|
this.creating = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.shape.props = props
|
this.shape.tvProps = props
|
||||||
this.shape.onProps(props)
|
this.shape.onProps(props)
|
||||||
this.shape.onDirtyModel(this.shape.propsToModel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDraw() {
|
onDraw() {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onDraw')
|
invokeCallback(this.shape, 'onDraw')
|
||||||
}
|
}
|
||||||
|
|
||||||
onRedraw() {
|
onRedraw() {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onRedraw')
|
invokeCallback(this.shape, 'onRedraw')
|
||||||
}
|
}
|
||||||
|
|
||||||
onUndraw() {
|
onUndraw() {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onUndraw')
|
invokeCallback(this.shape, 'onUndraw')
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddPoint() {
|
onAddPoint() {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onAddPoint')
|
invokeCallback(this.shape, 'onAddPoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMove(_shapeId, _tvShape, points) {
|
onMove(_shapeId, _tvShape, points) {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onMove',points)
|
invokeCallback(this.shape, 'onMove',points)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrag(_shapeId, _tvShape, points) {
|
onDrag(_shapeId, _tvShape, points) {
|
||||||
if( this.shape.lock ) return
|
if (this.shape.debug) console.log('onDrag')
|
||||||
invokeCallback(this.shape, 'onDrag', points)
|
invokeCallback(this.shape, 'onDrag', points)
|
||||||
}
|
}
|
||||||
|
|
||||||
onHide(_shapeId, _tvShape, props) {
|
onHide(_shapeId, _tvShape, props) {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onHide',props)
|
invokeCallback(this.shape, 'onHide',props)
|
||||||
}
|
}
|
||||||
|
|
||||||
onShow(_shapeId, _tvShape, props) {
|
onShow(_shapeId, _tvShape, props) {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onShow',props)
|
invokeCallback(this.shape, 'onShow',props)
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(_shapeId, _tvShape) {
|
onClick(_shapeId, _tvShape) {
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onClick')
|
invokeCallback(this.shape, 'onClick')
|
||||||
}
|
}
|
||||||
|
|
||||||
onDelete(shapeId) {
|
onDelete(shapeId) {
|
||||||
this.shape.id = null
|
this.shape.id = null
|
||||||
if( this.shape.lock ) return
|
|
||||||
invokeCallback(this.shape, 'onDelete', shapeId)
|
invokeCallback(this.shape, 'onDelete', shapeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class HLine extends Shape {
|
export class Line extends Shape {
|
||||||
|
onDrag(points) {
|
||||||
|
const s = this.tvShape();
|
||||||
|
if (this.debug) {
|
||||||
|
console.log('shape', s)
|
||||||
|
console.log('currentMovingPoint', s._source.currentMovingPoint())
|
||||||
|
console.log('startMovingPoint', s._source.startMovingPoint())
|
||||||
|
console.log('isBeingEdited', s._source.isBeingEdited())
|
||||||
|
console.log('state', s._source.state())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class HLine extends Line {
|
||||||
constructor(model, onModel=null, onDelete=null, props=null) {
|
constructor(model, onModel=null, onDelete=null, props=null) {
|
||||||
super(ShapeType.HLine, model, onModel, onDelete, props)
|
super(ShapeType.HLine, model, onModel, onDelete, props)
|
||||||
}
|
}
|
||||||
|
|
||||||
pointsFromModel() {
|
setModel(model) {
|
||||||
if (this.model.price === null) return null
|
if (model.price !== undefined && model.price !== this.model.price) {
|
||||||
const time = this.points !== null && this.points.length > 0 ? this.points[0].time : 0
|
this.model.price = model.price
|
||||||
return [{time:time, price:this.model.price}]
|
this.setPoints([{time:0,price:this.model.price}])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pointsToModel() {
|
onPoints(points) {
|
||||||
this.model.price = this.points[0].price
|
super.onPoints(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
propsFromModel() {
|
pointsFromModel(model) {
|
||||||
return this.model.color ? {linecolor: this.model.color} : null
|
if (model.price === null || model.price===undefined) return null
|
||||||
|
// take any time available, or 0
|
||||||
|
const time =
|
||||||
|
this.ourPoints !== null && this.ourPoints.length > 0 ? this.ourPoints[0].time :
|
||||||
|
this.tvPoints !== null && this.tvPoints.length > 0 ? this.tvPoints[0].time : 0
|
||||||
|
return [{time:time, price:model.price}]
|
||||||
}
|
}
|
||||||
|
|
||||||
propsToModel() {this.model.color=this.props.linecolor}
|
pointsToModel(points) {
|
||||||
|
return {price: this.ourPoints[0].price}
|
||||||
onDrag(points) {
|
|
||||||
const s = this.tvShape();
|
|
||||||
console.log('shape', s)
|
|
||||||
console.log('currentMovingPoint', s._source.currentMovingPoint())
|
|
||||||
console.log('startMovingPoint', s._source.startMovingPoint())
|
|
||||||
console.log('isBeingEdited', s._source.isBeingEdited())
|
|
||||||
console.log('state', s._source.state())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function timeAdjustmentTooSmall(orig, newValue) {
|
||||||
|
// TradingView adjusts our lines to be at the start of the intervals, so
|
||||||
|
// we ignore deltas smaller than one interval prior
|
||||||
|
return newValue === undefined ||
|
||||||
|
orig !== null && orig !== undefined && newValue !== null &&
|
||||||
|
newValue < orig && orig - newValue < useChartOrderStore().intervalSecs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ohlcStart(time) {
|
||||||
|
const period = useChartOrderStore().intervalSecs
|
||||||
|
return Math.floor(time/period) * period
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class VLine extends Line {
|
||||||
|
constructor(model, onModel=null, onDelete=null, props=null) {
|
||||||
|
super(ShapeType.VLine, onModel, onDelete, props)
|
||||||
|
|
||||||
|
// Model
|
||||||
|
this.model.time = null
|
||||||
|
|
||||||
|
this.setModel(model) // call setModel at the end
|
||||||
|
}
|
||||||
|
|
||||||
|
onPoints(points) {
|
||||||
|
if (this.debug) console.log('vline onPoints', this.ourPoints, points)
|
||||||
|
super.onPoints(points);
|
||||||
|
const orig = this.ourPoints && this.ourPoints.length ? this.ourPoints[0].time : null
|
||||||
|
if (!timeAdjustmentTooSmall(orig, points[0].time)) {
|
||||||
|
if (this.debug) console.log('updateModel', points[0].time)
|
||||||
|
this.updateModel({time: points[0].time})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setModel(model) {
|
||||||
|
if (this.debug) console.log('vline setModel', this.model.time, model )
|
||||||
|
super.setModel(model)
|
||||||
|
if (model.time !== undefined && model.time !== this.model.time) {
|
||||||
|
this.model.time = model.time
|
||||||
|
const time = ohlcStart(model.time);
|
||||||
|
if (this.debug) console.log('vline setPoints', this.id, time)
|
||||||
|
this.setPoints([{time, price:1}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
this.model.time = null
|
||||||
|
super.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
dirtyPoints(pointsA, pointsB) {
|
||||||
|
const a = pointsA ? pointsA[0].time : null
|
||||||
|
const b = pointsB ? pointsB[0].time : null
|
||||||
|
const result = !timeAdjustmentTooSmall(a, b)
|
||||||
|
if (this.debug) console.log('vline dirty points?', a, b, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export function mixin(child, ...parents) {
|
|||||||
// assigned by parents order, highest priority first
|
// assigned by parents order, highest priority first
|
||||||
for( const parent of parents ) {
|
for( const parent of parents ) {
|
||||||
for ( const key in parent) {
|
for ( const key in parent) {
|
||||||
if (parent.hasOwnProperty(key) && !child.hasOwnProperty(key)) {
|
if (parent.hasOwnProperty(key) && !child.hasOwnProperty(key) && parent[key] !== undefined) {
|
||||||
child[key] = parent[key];
|
child[key] = parent[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,6 @@ export function mixin(child, ...parents) {
|
|||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function prototype(parent, child) {
|
export function prototype(parent, child) {
|
||||||
const result = Object.create(parent);
|
const result = Object.create(parent);
|
||||||
Object.assign(result, child)
|
Object.assign(result, child)
|
||||||
|
|||||||
@@ -1,28 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :key="builder.id">
|
<row-bar :color="builder.color">
|
||||||
<v-card-text :color="titleColor">
|
<color-band :color="builder.color"/>
|
||||||
<slot name="title"><span>Unimplemented</span></slot>
|
<slot/>
|
||||||
</v-card-text>
|
<div class="align-self-center">
|
||||||
<v-card-text>
|
<v-menu>
|
||||||
<slot name="text">
|
<template v-slot:activator="{ props }">
|
||||||
Unimplemented builder panel
|
<v-btn variant="plain" v-bind="props" icon="mdi-dots-vertical"/>
|
||||||
</slot>
|
</template>
|
||||||
<div><v-btn variant="tonal" color="error" @click="co.removeBuilder(builder)" prepend-icon="mdi-delete">DELETE</v-btn></div>
|
<v-list>
|
||||||
</v-card-text>
|
<!-- <v-list-subheader :title="'Limit '+ (lineAPrice?lineAPrice.toPrecision(5):'')"/>-->
|
||||||
</div>
|
<v-list-item title="Delete" key="withdraw" value="withdraw" prepend-icon="mdi-delete" color="red"
|
||||||
|
@click="deleteMyBuilder"/>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</div>
|
||||||
|
</row-bar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useChartOrderStore} from "@/orderbuild.js";
|
import {builderFuncs, deleteBuilder, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {computed} from "vue";
|
import ColorBand from "@/components/chart/ColorBand.vue";
|
||||||
|
import RowBar from "@/components/chart/RowBar.vue";
|
||||||
|
import {onBeforeUnmount, onMounted, onUnmounted, onUpdated, watchEffect} from "vue";
|
||||||
|
|
||||||
const props = defineProps(['builder', 'color', 'colorTag'])
|
const props = defineProps({
|
||||||
|
order: Object,
|
||||||
|
builder: Object,
|
||||||
|
buildTranches: {type: Function},
|
||||||
|
adjustShapes: {type: Function, default: null},
|
||||||
|
deleteShapes: {type: Function, default: null},
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:builder'])
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
|
|
||||||
const titleColor = computed(()=>props.color ? props.color : props.colorTag ? props.builder.props[props.colorTag]?.linecolor : props.builder.props.a.linecolor )
|
|
||||||
|
let lastId = props.builder.id
|
||||||
|
builderFuncs[props.builder.id] = props.buildTranches
|
||||||
|
onUpdated(()=>{
|
||||||
|
if (lastId !== props.builder.id ) {
|
||||||
|
delete builderFuncs[lastId]
|
||||||
|
builderFuncs[props.builder.id] = props.buildTranches
|
||||||
|
lastId = props.builder.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onUnmounted(() => delete builderFuncs[lastId])
|
||||||
|
|
||||||
|
if (props.adjustShapes) {
|
||||||
|
watchEffect(props.adjustShapes)
|
||||||
|
onMounted(props.adjustShapes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.deleteShapes)
|
||||||
|
onBeforeUnmount(props.deleteShapes)
|
||||||
|
|
||||||
|
function deleteMyBuilder() {
|
||||||
|
deleteBuilder(props.order, props.builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<div v-if="order.builders.length===0"> <!--todo remove gralpha limitation of one builder-->
|
<div v-if="order.builders.length===0"> <!--todo remove gralpha limitation of one builder-->
|
||||||
<span :style="colorStyle" class="ma-3">Add condition:</span>
|
<span :style="colorStyle" class="ma-3">Add condition:</span>
|
||||||
<!-- <v-btn variant="flat" prepend-icon="mdi-clock-outline" @click="build('DCABuilder')">DCA</v-btn>-->
|
<v-btn :color="color" variant="text" prepend-icon="mdi-clock-outline" @click="build(order,'DCABuilder')">DCA</v-btn>
|
||||||
<v-btn :color="color" variant="text" prepend-icon="mdi-ray-vertex" @click="build(order,'LimitBuilder')">Limit</v-btn>
|
<v-btn :color="color" variant="text" prepend-icon="mdi-ray-vertex" @click="build(order,'LimitBuilder')">Limit</v-btn>
|
||||||
<!-- <v-btn variant="flat" prepend-icon="mdi-vector-line">Line</v-btn>-->
|
<!-- <v-btn variant="flat" prepend-icon="mdi-vector-line">Line</v-btn>-->
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -1,33 +1,158 @@
|
|||||||
<template>
|
<template>
|
||||||
<builder-panel :builder="builder">
|
<rung-builder name='DCA' :order="order" :builder="builder"
|
||||||
<template v-slot:title>
|
v-model:value-a="absTimeA" v-model:value-b="absTimeB" :mode="1" :shape="VLine"
|
||||||
<span>DCA</span>
|
:get-model-value="getModelValue" :set-model-value="setModelValue" :set-values="setValues"
|
||||||
<span v-if="!builder.points">Draw your timeframe on the chart!</span>
|
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||||
</template>
|
<v-list style="background-color: inherit">
|
||||||
<template v-slot:text>
|
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
||||||
<input type="number" min="1" max="2">
|
</v-list>
|
||||||
</template>
|
</rung-builder>
|
||||||
</builder-panel>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
|
import {builderDefaults, MIN_EXECUTION_TIME, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {drawShape, ShapeCallback, VerboseCallback} from "@/charts/chart.js";
|
import {VLine} from "@/charts/shape.js";
|
||||||
import {prototype} from "@/common.js";
|
import {sideColor} from "@/misc.js";
|
||||||
import {ShapeType} from "@/charts/shape.js";
|
import {useTheme} from "vuetify";
|
||||||
|
import {useOrderStore, useStore} from "@/store/store.js";
|
||||||
|
import {DISTANT_FUTURE, MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||||
|
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
|
||||||
const props = defineProps(['builder'])
|
const s = useStore()
|
||||||
|
const os = useOrderStore()
|
||||||
|
const co = useChartOrderStore()
|
||||||
|
const theme = useTheme().current
|
||||||
|
const props = defineProps(['order', 'builder'])
|
||||||
|
|
||||||
const DCAArtist = prototype(VerboseCallback, {
|
const stdWidth = computed(()=>10 * co.intervalSecs)
|
||||||
onDraw(widget, chart) { console.log('dca start draw') },
|
|
||||||
onCreate(widget, chart) { console.log('dca start draw') },
|
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, {
|
||||||
|
timeA: s.clock, // todo 0
|
||||||
|
timeB: null,
|
||||||
|
// relative: true,
|
||||||
|
relative: false, // todo
|
||||||
|
rungs: 1,
|
||||||
|
skew: 0,
|
||||||
|
color: defaultColor,
|
||||||
})
|
})
|
||||||
|
|
||||||
drawShape(ShapeType.VLine, prototype(DCAArtist, {}))
|
const rawTimes = ref([])
|
||||||
|
const times = computed(()=>rawTimes.value.map((t)=>Math.round(t)))
|
||||||
|
const endTimes = computed(()=>{
|
||||||
|
if (props.builder.rungs === 1)
|
||||||
|
return DISTANT_FUTURE
|
||||||
|
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 absTimeA = computed({
|
||||||
|
get() {
|
||||||
|
let result = props.builder.timeA
|
||||||
|
if (props.builder.relative)
|
||||||
|
result += s.clock
|
||||||
|
console.log('absTimeA', result)
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
console.log('set absTimeA', props.builder.timeA, v)
|
||||||
|
if (props.builder.relative)
|
||||||
|
v -= s.clock
|
||||||
|
props.builder.timeA = v
|
||||||
|
console.log('absTimeA=',props.builder.timeA)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const absTimeB = computed({
|
||||||
|
get() {
|
||||||
|
let result = props.builder.timeB
|
||||||
|
if (props.builder.relative)
|
||||||
|
result += s.clock
|
||||||
|
console.log('absTimeB', result)
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
console.log('set absTimeB', props.builder.timeB, v)
|
||||||
|
if ( v !== null && props.builder.relative )
|
||||||
|
v -= s.clock
|
||||||
|
props.builder.timeB = v
|
||||||
|
console.log('absTimeB=',props.builder.timeB)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function buildTranches() {
|
||||||
|
const order = props.order
|
||||||
|
const builder = props.builder
|
||||||
|
const tranches = []
|
||||||
|
|
||||||
|
console.log('buildTranches', builder, order, tranches)
|
||||||
|
const ts = times.value
|
||||||
|
const ets = endTimes.value
|
||||||
|
const ws = weights.value
|
||||||
|
for(let i=0; i<ts.length; i++) {
|
||||||
|
const t = newTranche({
|
||||||
|
fraction: ws[i] * MAX_FRACTION,
|
||||||
|
startTime: ts[i],
|
||||||
|
endTime: ets[i],
|
||||||
|
})
|
||||||
|
tranches.push(t)
|
||||||
|
}
|
||||||
|
return tranches
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getModelValue(model) {
|
||||||
|
if(!model) {
|
||||||
|
console.log('getModelValue', model)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return model.time
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if (model.time !== v) {
|
||||||
|
console.log('DCA do set time', v)
|
||||||
|
model.time = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValues(values) {
|
||||||
|
if (!props.builder.relative)
|
||||||
|
rawTimes.value = values
|
||||||
|
else {
|
||||||
|
const now = s.clock
|
||||||
|
const vs = []
|
||||||
|
for (let i = 0; i < values.length; i++)
|
||||||
|
vs.push(values[i] - now)
|
||||||
|
rawTimes.value = vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueFromPoints(points) {
|
||||||
|
const result = points[0].time;
|
||||||
|
console.log('valueFromPoints', points, result);
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
</style>
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -75,10 +75,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {computed, onBeforeUnmount, onMounted, onUnmounted, onUpdated, watch, watchEffect} from "vue";
|
import {computed, onBeforeUnmount, onMounted, onUnmounted, onUpdated, watch, watchEffect} from "vue";
|
||||||
import {cancelDrawing, chart} from "@/charts/chart.js";
|
import {cancelDrawing, chart} from "@/charts/chart.js";
|
||||||
import {applyLine2, builderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
import {applyLine2, builderDefaults, builderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import Color from "color";
|
import Color from "color";
|
||||||
import {HLine} from "@/charts/shape.js";
|
import {HLine} from "@/charts/shape.js";
|
||||||
import {builderDefaults, lightenColor2, lineColor} from "@/misc.js";
|
import {lightenColor2, sideColor} from "@/misc.js";
|
||||||
import {useTheme} from "vuetify";
|
import {useTheme} from "vuetify";
|
||||||
import {useOrderStore} from "@/store/store.js";
|
import {useOrderStore} from "@/store/store.js";
|
||||||
import RowBar from "@/components/chart/RowBar.vue";
|
import RowBar from "@/components/chart/RowBar.vue";
|
||||||
@@ -93,15 +93,13 @@ const emit = defineEmits(['update:builder'])
|
|||||||
|
|
||||||
function computeDefaultColor() {
|
function computeDefaultColor() {
|
||||||
const index = props.order.builders.indexOf(props.builder)
|
const index = props.order.builders.indexOf(props.builder)
|
||||||
return lineColor(props.order.buy, index)
|
return sideColor(props.order.buy, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultColor = computeDefaultColor()
|
const defaultColor = computeDefaultColor()
|
||||||
|
|
||||||
// Fields must be defined in order to be reactive
|
// Fields must be defined in order to be reactive
|
||||||
builderDefaults(props, emit, {
|
builderDefaults(props.builder, {
|
||||||
valid: false,
|
|
||||||
allocation: 1.0,
|
|
||||||
start: null, // todo
|
start: null, // todo
|
||||||
end: null, // todo
|
end: null, // todo
|
||||||
priceA: null,
|
priceA: null,
|
||||||
|
|||||||
@@ -18,11 +18,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {builderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
import {builderDefaults, builderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||||
import {builderDefaults} from "@/misc.js";
|
|
||||||
import {useOrderStore} from "@/store/store.js";
|
|
||||||
import {computed, onMounted, onUnmounted} from "vue";
|
import {computed, onMounted, onUnmounted} from "vue";
|
||||||
import RowBar from "@/components/chart/RowBar.vue";
|
|
||||||
import {newTranche} from "@/blockchain/orderlib.js";
|
import {newTranche} from "@/blockchain/orderlib.js";
|
||||||
|
|
||||||
const co = useChartOrderStore()
|
const co = useChartOrderStore()
|
||||||
@@ -30,7 +27,8 @@ const props = defineProps(['order', 'builder'])
|
|||||||
const emit = defineEmits(['update:builder'])
|
const emit = defineEmits(['update:builder'])
|
||||||
|
|
||||||
// Fields must be defined in order to be reactive
|
// Fields must be defined in order to be reactive
|
||||||
builderDefaults(props, emit, {valid: true, slippage: 0.10,})
|
builderDefaults(props.builder, {valid: true, slippage: 0.10,})
|
||||||
|
|
||||||
const slippage = computed({
|
const slippage = computed({
|
||||||
get() {console.log('slip',props.builder,props.builder.slippage); return props.builder.slippage},
|
get() {console.log('slip',props.builder,props.builder.slippage); return props.builder.slippage},
|
||||||
set(v) {props.builder.slippage=v; emit('update:builder', props.builder)}
|
set(v) {props.builder.slippage=v; emit('update:builder', props.builder)}
|
||||||
|
|||||||
352
src/components/chart/RungBuilder.vue
Normal file
352
src/components/chart/RungBuilder.vue
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<template>
|
||||||
|
<builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
|
||||||
|
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes">
|
||||||
|
<div style="min-width: 3em; font-size: larger" :style="colorStyle" class="align-self-start ml-2 pt-3">{{ name }}
|
||||||
|
</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="valueA===null"
|
||||||
|
style="width: 4.5em;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot/>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</builder-panel>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
|
||||||
|
import {useOrderStore} from "@/store/store.js";
|
||||||
|
import {allocationText, deleteBuilder, linearWeights, useChartOrderStore, weightColors} from "@/orderbuild.js";
|
||||||
|
import {useTheme} from "vuetify";
|
||||||
|
import {linspace, sideColor} from "@/misc.js";
|
||||||
|
import {computed, watchEffect} from "vue";
|
||||||
|
import Color from "color";
|
||||||
|
import {cancelDrawing} from "@/charts/chart.js";
|
||||||
|
|
||||||
|
const os = useOrderStore()
|
||||||
|
const co = useChartOrderStore()
|
||||||
|
const theme = useTheme().current
|
||||||
|
const valueA = defineModel('valueA')
|
||||||
|
const valueB = defineModel('valueB')
|
||||||
|
const props = defineProps({
|
||||||
|
name: String,
|
||||||
|
order: Object,
|
||||||
|
builder: Object,
|
||||||
|
buildTranches: Function,
|
||||||
|
stdWidth: Number,
|
||||||
|
shape: Function, // shape() -> Shape
|
||||||
|
mode: { type: Number, default: 0 }, // rung addition mode: 0 = split, 1 = extend
|
||||||
|
getModelValue: Function, // getModelValue(model) -> value
|
||||||
|
setModelValue: Function, // setModelValue(model,value) -> void
|
||||||
|
setValues: Function, // setValues(values:Array) -> void
|
||||||
|
})
|
||||||
|
|
||||||
|
const skew100 = computed( {
|
||||||
|
get() {return props.builder.skew*100},
|
||||||
|
set(v) {props.builder.skew = v/100; }
|
||||||
|
} )
|
||||||
|
|
||||||
|
// validity checks
|
||||||
|
watchEffect(()=>{
|
||||||
|
const order = props.order
|
||||||
|
const builder = props.builder
|
||||||
|
props.builder.valid &&=
|
||||||
|
order && builder &&
|
||||||
|
builder.rungs >= 1 && valueA.value &&
|
||||||
|
(builder.rungs < 2 || valueB.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const rungs = computed({
|
||||||
|
get() {
|
||||||
|
return props.builder.rungs
|
||||||
|
},
|
||||||
|
set(r) {
|
||||||
|
// todo this is subclass specific: make TWAP extend the range rather than compressing it
|
||||||
|
if (!r) {
|
||||||
|
props.builder.rungs = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r = Number(r)
|
||||||
|
props.builder.rungs = r
|
||||||
|
const b = valueB.value
|
||||||
|
console.log('set rungs', r, valueA.value, b)
|
||||||
|
if ( r > 0 && b === null ) {
|
||||||
|
// convert single shape to a range
|
||||||
|
if (props.mode===0) {
|
||||||
|
const width = props.stdWidth
|
||||||
|
const mid = valueA.value
|
||||||
|
console.log('single to range', mid - width/2, mid + width/2)
|
||||||
|
valueA.value = mid - width/2
|
||||||
|
valueB.value = mid + width/2
|
||||||
|
}
|
||||||
|
else if (props.mode===1 ) {
|
||||||
|
valueB.value = valueA.value + props.stdWidth
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw Error(`Unknown rung mode ${props.mode}`)
|
||||||
|
}
|
||||||
|
else if ( r === 1 && b !== null ) {
|
||||||
|
// convert from a range to a single shape
|
||||||
|
if (props.mode===0)
|
||||||
|
valueA.value = (valueA.value + b) / 2
|
||||||
|
valueB.value = null
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// from multi to multi
|
||||||
|
if (props.mode===1)
|
||||||
|
valueB.value = valueA.value + props.stdWidth * (r-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const values = computed(()=>{
|
||||||
|
let a = valueA.value
|
||||||
|
let b = valueB.value
|
||||||
|
const r = props.builder.rungs
|
||||||
|
let result
|
||||||
|
if ( a===null || !r )
|
||||||
|
result = [] // no data
|
||||||
|
else if (r===1)
|
||||||
|
result = [a] // single shape
|
||||||
|
else
|
||||||
|
result = linspace(a, b, r) // linear spacing
|
||||||
|
props.setValues(result)
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const weights = computed(() => linearWeights(props.builder.rungs, -props.builder.skew))
|
||||||
|
|
||||||
|
|
||||||
|
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||||
|
|
||||||
|
|
||||||
|
// colors
|
||||||
|
function computeDefaultColor() {
|
||||||
|
const index = props.order.builders.indexOf(props.builder)
|
||||||
|
return sideColor(props.order.buy, index)
|
||||||
|
}
|
||||||
|
const defaultColor = computeDefaultColor()
|
||||||
|
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 colors = computed( ()=> {
|
||||||
|
const color = props.builder.color !== null ? props.builder.color
|
||||||
|
: props.buy ? theme.value.colors.success : theme.value.colors.error
|
||||||
|
const ws = weights.value;
|
||||||
|
return weightColors(ws, color);
|
||||||
|
})
|
||||||
|
const colorStyle = computed(() => {
|
||||||
|
return {'color': color.value}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function allocText(weight) {
|
||||||
|
const alloc = props.builder.allocation
|
||||||
|
if (alloc===null) return ''
|
||||||
|
return allocationText(props.order.amount, weight * alloc, amountSymbol.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// SHAPE MANAGEMENT
|
||||||
|
//
|
||||||
|
|
||||||
|
// we keep two special control shapes as the edges of the range, with deletable shapes in-between
|
||||||
|
|
||||||
|
function createShape(value, model, onModel, onDelete) {
|
||||||
|
console.log('createShape setModelValue', model, value)
|
||||||
|
props.setModelValue(model, value)
|
||||||
|
return new props.shape(model, onModel, onDelete) // props.shape is the constructor function
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function translateOnDrag(shape) {
|
||||||
|
const oldOnPoints = shape.onPoints
|
||||||
|
shape.onPoints = function (points) {
|
||||||
|
if (!this.beingDragged()) {
|
||||||
|
oldOnPoints.call(this, points)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const prev = props.getModelValue(this.model)
|
||||||
|
oldOnPoints.call(this, points)
|
||||||
|
const cur = props.getModelValue(this.model)
|
||||||
|
const delta = cur - prev
|
||||||
|
console.log('move prev/cur', prev, cur, delta)
|
||||||
|
if (delta !== 0) {
|
||||||
|
valueA.value += delta
|
||||||
|
valueB.value += delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const shapeA = createShape(valueA.value, {color: defaultColor},
|
||||||
|
function (model) {
|
||||||
|
const value = props.getModelValue(model);
|
||||||
|
if (value!==null && value!==undefined)
|
||||||
|
valueA.value = value;
|
||||||
|
if (model.color)
|
||||||
|
props.builder.color = model.color;
|
||||||
|
},
|
||||||
|
deleteSelf)
|
||||||
|
|
||||||
|
if (props.mode===1)
|
||||||
|
translateOnDrag(shapeA)
|
||||||
|
|
||||||
|
const shapeB = createShape(valueB.value, {color:defaultColor},
|
||||||
|
function (model) {
|
||||||
|
const value = props.getModelValue(model);
|
||||||
|
if (value!==null && value!==undefined)
|
||||||
|
valueB.value = value;
|
||||||
|
if (model.color)
|
||||||
|
props.builder.color = model.color;
|
||||||
|
},
|
||||||
|
deleteSelf)
|
||||||
|
|
||||||
|
function interiorOnModel(model) {
|
||||||
|
const v = model.textColor || model.lineColor || model.color
|
||||||
|
if (v)
|
||||||
|
color.value = v
|
||||||
|
}
|
||||||
|
|
||||||
|
let interiorShapes = []
|
||||||
|
|
||||||
|
function createInteriorShape(price, weight) {
|
||||||
|
const model = {text: allocText(weight), color: props.builder.color};
|
||||||
|
const shape = createShape(price, model, interiorOnModel, deleteSelf)
|
||||||
|
shape.debug = true
|
||||||
|
translateOnDrag(shape)
|
||||||
|
interiorShapes.push(shape)
|
||||||
|
// shape.create() // should happen automatically when a model with valid points is set
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function removeInteriorShape() {
|
||||||
|
if (interiorShapes.length) {
|
||||||
|
const shape = interiorShapes.pop()
|
||||||
|
shape.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustShapes() {
|
||||||
|
// this is where all the shapes are created or adjusted
|
||||||
|
console.log('adjustShapes()', valueA.value, valueB.value)
|
||||||
|
const vs = values.value
|
||||||
|
if (vs.length)
|
||||||
|
cancelDrawing()
|
||||||
|
const ws = weights.value
|
||||||
|
const colorStrings = colors.value
|
||||||
|
// shape properties
|
||||||
|
if( vs.length === 0 ) {
|
||||||
|
shapeA.delete()
|
||||||
|
shapeB.delete()
|
||||||
|
for( const shape of interiorShapes )
|
||||||
|
shape.delete()
|
||||||
|
interiorShapes = []
|
||||||
|
}
|
||||||
|
else if (vs.length === 1) {
|
||||||
|
//
|
||||||
|
// SINGLE SHAPE
|
||||||
|
//
|
||||||
|
if (!shapeA.beingDragged()) {
|
||||||
|
const model = {text: allocText(ws[0]), color: colorStrings[0]};
|
||||||
|
console.log('single shape A setModelValue', model, vs[0])
|
||||||
|
props.setModelValue(model, vs[0])
|
||||||
|
shapeA.setModel(model)
|
||||||
|
}
|
||||||
|
shapeB.delete()
|
||||||
|
if (interiorShapes.length) {
|
||||||
|
for( const shape of interiorShapes )
|
||||||
|
shape.delete()
|
||||||
|
interiorShapes = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//
|
||||||
|
// VALUE RANGE
|
||||||
|
//
|
||||||
|
if (!shapeA.beingDragged()) {
|
||||||
|
const model = {text: allocText(ws[0]), color: colorStrings[0]};
|
||||||
|
console.log('shape A not dragged setModelValue', model, vs[0])
|
||||||
|
props.setModelValue(model, vs[0])
|
||||||
|
shapeA.setModel(model)
|
||||||
|
}
|
||||||
|
if (!shapeB.beingDragged()) {
|
||||||
|
const last = colorStrings.length - 1
|
||||||
|
const model = {text: allocText(ws[last]), color: colorStrings[last]};
|
||||||
|
console.log('shape B not dragged setModelValue', model, vs[last])
|
||||||
|
props.setModelValue(model, vs[last])
|
||||||
|
shapeB.setModel(model)
|
||||||
|
}
|
||||||
|
const numInterior = Math.max(0,vs.length-2);
|
||||||
|
// trim excess interior shapes
|
||||||
|
while( interiorShapes.length > numInterior )
|
||||||
|
removeInteriorShape()
|
||||||
|
// adjust the interior shape values and/or add shapes
|
||||||
|
for( let i=1; i<vs.length-1; i++ ) {
|
||||||
|
const v = vs[i]
|
||||||
|
const w = ws[i];
|
||||||
|
if (i-1 === interiorShapes.length)
|
||||||
|
createInteriorShape(v, w)
|
||||||
|
else if (!interiorShapes[i-1].beingDragged()) {
|
||||||
|
const model = {text: allocText(w), color: colorStrings[i]};
|
||||||
|
console.log('interior setModelValue', model, v)
|
||||||
|
props.setModelValue(model, v)
|
||||||
|
interiorShapes[i-1].setModel(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelf() {
|
||||||
|
deleteBuilder(props.order, props.builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteShapes() {
|
||||||
|
shapeA.delete()
|
||||||
|
shapeB.delete()
|
||||||
|
for (const shape of interiorShapes)
|
||||||
|
shape.delete()
|
||||||
|
interiorShapes = []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log('valueA', valueA.value, shapeA)
|
||||||
|
if (!valueA.value)
|
||||||
|
shapeA.createOrDraw(); // initiate drawing mode
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
35
src/misc.js
35
src/misc.js
@@ -172,18 +172,8 @@ export function pairPrice(chainId, baseToken, quoteToken, price) {
|
|||||||
|
|
||||||
export const sleep = ms => new Promise(r => setTimeout(r, ms))
|
export const sleep = ms => new Promise(r => setTimeout(r, ms))
|
||||||
|
|
||||||
export function builderDefaults(props, emit, defaults) {
|
|
||||||
let changed = false
|
|
||||||
for (const k in defaults)
|
|
||||||
if (props.builder[k] === undefined) {
|
|
||||||
props.builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k]
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if (changed)
|
|
||||||
emit('update:builder', props.builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function uuid() {
|
export function uuid() {
|
||||||
|
// noinspection JSUnresolvedReference
|
||||||
return crypto.randomUUID();
|
return crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +194,7 @@ const colorRanges = {
|
|||||||
sell: ['#CC0033', '#CCCC33'],
|
sell: ['#CC0033', '#CCCC33'],
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lineColor(buy, index) {
|
export function sideColor(buy, index) {
|
||||||
const range = buy ? colorRanges.buy : colorRanges.sell
|
const range = buy ? colorRanges.buy : colorRanges.sell
|
||||||
const a = new Color(range[0]).rgb()
|
const a = new Color(range[0]).rgb()
|
||||||
const b = new Color(range[1]).rgb()
|
const b = new Color(range[1]).rgb()
|
||||||
@@ -229,3 +219,24 @@ export function unique(arr) {
|
|||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function linspace(a, b, n) {
|
||||||
|
if (n===1) return [(a+b)/2] // single line
|
||||||
|
const spacing = (b - a) / (n - 1)
|
||||||
|
// console.log('spacing', a, b)
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < n; i++)
|
||||||
|
result.push(a + i * spacing)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function intervalToSeconds(interval) {
|
||||||
|
if (interval.endsWith('T'))
|
||||||
|
throw Error('Tick intervals not supported')
|
||||||
|
return interval.endsWith('M') ? 30 * 24 * 60 * 60
|
||||||
|
: interval.endsWith('W') ? 7 * 24 * 60 * 60
|
||||||
|
: interval.endsWith('D') ? 24 * 60 * 60
|
||||||
|
: interval.endsWith('S') ? 1
|
||||||
|
: 60 // if no unit char, then it's minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import {useOrderStore, useStore} from "@/store/store.js";
|
|||||||
import {encodeIEE754} from "@/common.js";
|
import {encodeIEE754} from "@/common.js";
|
||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import {computed, ref} from "vue";
|
import {computed, ref} from "vue";
|
||||||
|
import Color from "color";
|
||||||
|
|
||||||
|
|
||||||
|
export const MIN_EXECUTION_TIME = 60 // give at least one full minute for each tranche to trigger
|
||||||
|
|
||||||
|
|
||||||
function unimplemented() { throw Error('Unimplemented') }
|
function unimplemented() { throw Error('Unimplemented') }
|
||||||
@@ -13,7 +17,10 @@ function unimplemented() { throw Error('Unimplemented') }
|
|||||||
// for instantiating the UI component for a given builder dictionary, based on its builder.component field.
|
// for instantiating the UI component for a given builder dictionary, based on its builder.component field.
|
||||||
export function newBuilder( component, options = {}) {
|
export function newBuilder( component, options = {}) {
|
||||||
const id = uuid()
|
const id = uuid()
|
||||||
return {id, component, options, points: {}, shapes: {}, props: {}, build: unimplemented}
|
return {
|
||||||
|
id, component, options,
|
||||||
|
allocation: 1.0, points: {}, shapes: {}, props: {}, valid: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orders hold an amount and builders
|
// Orders hold an amount and builders
|
||||||
@@ -44,6 +51,7 @@ export const useChartOrderStore = defineStore('chart_orders', () => {
|
|||||||
const selectedOrder = ref(null)
|
const selectedOrder = ref(null)
|
||||||
const selectedSymbol = ref(null)
|
const selectedSymbol = ref(null)
|
||||||
const selectedPool = ref(null)
|
const selectedPool = ref(null)
|
||||||
|
const intervalSecs = ref(0)
|
||||||
const baseToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.base)
|
const baseToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.base)
|
||||||
const quoteToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.quote)
|
const quoteToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.quote)
|
||||||
const price = computed(() => {
|
const price = computed(() => {
|
||||||
@@ -87,7 +95,7 @@ export const useChartOrderStore = defineStore('chart_orders', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chartReady, selectedSymbol, selectedPool, baseToken, quoteToken, price,
|
chartReady, selectedSymbol, selectedPool, intervalSecs, baseToken, quoteToken, price,
|
||||||
orders, drawing, drawingCallbacks, newOrder, removeOrder, resetOrders,
|
orders, drawing, drawingCallbacks, newOrder, removeOrder, resetOrders,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -222,3 +230,51 @@ export function timesliceTranches() {
|
|||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function builderDefaults(builder, defaults) {
|
||||||
|
for (const k in defaults)
|
||||||
|
if (builder[k] === undefined)
|
||||||
|
builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function linearWeights(n, s) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
export function weightColors(weights, color) {
|
||||||
|
const c = new Color(color).rgb()
|
||||||
|
const max = Math.max(...weights)
|
||||||
|
const ns = weights.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())
|
||||||
|
}
|
||||||
|
|
||||||
|
export function allocationText(amount, weight, symbol) {
|
||||||
|
// console.log('weight', weight, alloc, props.amount)
|
||||||
|
const a = amount * weight
|
||||||
|
return `${(weight * 100).toFixed(1)}% = ${a.toLocaleString('fullwide')} ${symbol}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteBuilder(order, builder) {
|
||||||
|
order.builders = order.builders.filter((b) => b !== builder)
|
||||||
|
// if (props.deleteShapes) // todo is this redundant?
|
||||||
|
// props.deleteShapes()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user