interactive horizontal limit lines
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "6.9.96",
|
||||
"color": "^4.2.3",
|
||||
"core-js": "^3.29.0",
|
||||
"ethers": "^6.7.1",
|
||||
"pinia": "2.1.6",
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
|
||||
export function mixin(child, ...parents) {
|
||||
for( const parent of parents ) {
|
||||
for ( const key in parent) {
|
||||
if (parent.hasOwnProperty(key)) {
|
||||
child[key] = parent[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
export function prototype(parent, child) {
|
||||
const result = Object.create(parent);
|
||||
Object.assign(result, child)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
export function encodeIEE754(value) {
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
203
src/chart.js
Normal file
203
src/chart.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {prototype} from "@/blockchain/common.js";
|
||||
|
||||
export let widget = null
|
||||
export let chart = null
|
||||
export let crosshairPoint = null
|
||||
|
||||
|
||||
/*
|
||||
TradingView drawing tool codes:
|
||||
https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library/#supportedlinetools
|
||||
text anchored_text note anchored_note signpost double_curve arc icon emoji sticker arrow_up arrow_down arrow_left arrow_right price_label price_note arrow_marker flag vertical_line horizontal_line cross_line horizontal_ray trend_line info_line trend_angle arrow ray extended parallel_channel disjoint_angle flat_bottom anchored_vwap pitchfork schiff_pitchfork_modified schiff_pitchfork balloon comment inside_pitchfork pitchfan gannbox gannbox_square gannbox_fixed gannbox_fan fib_retracement fib_trend_ext fib_speed_resist_fan fib_timezone fib_trend_time fib_circles fib_spiral fib_speed_resist_arcs fib_channel xabcd_pattern cypher_pattern abcd_pattern callout triangle_pattern 3divers_pattern head_and_shoulders fib_wedge elliott_impulse_wave elliott_triangle_wave elliott_triple_combo elliott_correction elliott_double_combo cyclic_lines time_cycles sine_line long_position short_position forecast date_range price_range date_and_price_range bars_pattern ghost_feed projection rectangle rotated_rectangle circle ellipse triangle polyline path curve cursor dot arrow_cursor eraser measure zoom brush highlighter regression_trend fixed_range_volume_profile
|
||||
*/
|
||||
|
||||
export const ShapeType = {
|
||||
Segment: {name: 'Trend Line', code: 'trend_line'},
|
||||
Ray: {name: 'Ray', code: 'ray'},
|
||||
Line: {name: 'Extended Line', code: 'extended'},
|
||||
HRay: {name: 'Horizontal Ray', code: 'horizontal_ray'},
|
||||
HLine: {name: 'Horizontal Line', code: 'horizontal_line'},
|
||||
VLine: {name: 'Vertical Line', code: 'vertical_line'}
|
||||
}
|
||||
|
||||
|
||||
export function initWidget(el) {
|
||||
widget = window.tvWidget = new TradingView.widget({
|
||||
library_path: "/tradingview/charting_library/",
|
||||
// debug: true,
|
||||
autosize: true,
|
||||
symbol: 'AAPL',
|
||||
interval: '1D',
|
||||
container: el,
|
||||
datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"),
|
||||
locale: "en",
|
||||
disabled_features: [],
|
||||
enabled_features: [],
|
||||
drawings_access: {type: 'white', tools: [],}, // show no tools
|
||||
});
|
||||
widget.subscribe('drawing_event', handleDrawingEvent)
|
||||
widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged)
|
||||
widget.onChartReady(initChart)
|
||||
}
|
||||
|
||||
|
||||
function initChart() {
|
||||
console.log('init chart')
|
||||
chart = widget.activeChart()
|
||||
chart.crossHairMoved().subscribe(null, (point)=>{
|
||||
crosshairPoint=point
|
||||
const co = useChartOrderStore()
|
||||
invoke(co.drawingCallbacks, 'onRedraw')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
export const ShapeCallback = {
|
||||
// a better word than "draw-er", an ShapeCallback renders one or more shapes in accordance with its
|
||||
// backing builder. for example, a TWAPArtist would manage multiple vertical line shapes, starting
|
||||
// at the current time and spaced out by the tranche interval.
|
||||
// use this base class with misc.js/prototype()
|
||||
|
||||
//
|
||||
// ShapeType Events
|
||||
//
|
||||
onDraw: ()=>{}, // start drawing a new shape for the builder
|
||||
onRedraw: ()=>{}, // the mouse moved while in drawing mode
|
||||
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)
|
||||
onCreate: (shapeId, points, props)=>{}, // the user has finished creating all the control points. drawing mode is exited and the initial shape is created.
|
||||
onPoints: (shapeId, points)=>{}, // the control points of an existing shape were changed
|
||||
onProps: (shapeId, props)=>{}, // the display properties of an existing shape were changed
|
||||
onMove: (shapeId, points)=>{}, // the entire shape was moved. todo same as onPoints?
|
||||
onHide: (shapeId, props)=>{},
|
||||
onShow: (shapeId, props)=>{},
|
||||
onClick: (shapeId)=>{}, // the shape was selected
|
||||
onDeleted: (shapeId)=>{},
|
||||
}
|
||||
|
||||
|
||||
export const VerboseCallback = prototype(ShapeCallback, {
|
||||
onDraw: ()=>{console.log('onDraw')},
|
||||
// onRedraw: ()=>{console.log('onRedraw')},
|
||||
onUndraw: ()=>{console.log('onUndraw')},
|
||||
onAddPoint: ()=>{console.log('onAddPoint')},
|
||||
onCreate: (shapeId, points, props)=>{console.log('onCreate')},
|
||||
onPoints: (shapeId, points)=>{console.log('onPoints')},
|
||||
onProps: (shapeId, props)=>{console.log('onProps')},
|
||||
onMove: (shapeId, points)=>{console.log('onMove')},
|
||||
onHide: (shapeId, props)=>{console.log('onHide')},
|
||||
onShow: (shapeId, props)=>{console.log('onShow')},
|
||||
onClick: (shapeId)=>{console.log('onClick')},
|
||||
onDelete: (shapeId)=>{console.log('onDelete')},
|
||||
})
|
||||
|
||||
|
||||
|
||||
export const BuilderUpdateCallback = prototype(ShapeCallback,{
|
||||
onCreate(shapeId, points, props) {
|
||||
this.builder.shapes[this.tag] = shapeId;
|
||||
},
|
||||
onPoints(shapeId, points) {
|
||||
this.builder.points[this.tag] = points;
|
||||
},
|
||||
onProps(shapeId, props) {
|
||||
this.builder.props[this.tag] = props;
|
||||
},
|
||||
onMove(shapeId, points) {
|
||||
this.builder.points[this.tag] = points;
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
function invoke(callbacks, prop, ...args) {
|
||||
if (!callbacks) return
|
||||
callbacks.forEach((cb)=>{if(prop in cb) cb[prop].call(cb, ...args)})
|
||||
}
|
||||
|
||||
|
||||
export function drawShape(shapeType, ...callbacks) {
|
||||
console.log('drawShape', callbacks, shapeType.name, shapeType.code)
|
||||
const co = useChartOrderStore()
|
||||
if( co.drawingCallbacks )
|
||||
invoke(co.drawingCallbacks, 'onUndraw')
|
||||
co.drawingCallbacks = callbacks
|
||||
co.drawing = true
|
||||
widget.selectLineTool(shapeType.code)
|
||||
invoke(callbacks, 'onDraw')
|
||||
}
|
||||
|
||||
|
||||
export function cancelDrawing() {
|
||||
const co = useChartOrderStore()
|
||||
if (co.drawing) {
|
||||
invoke(co.drawingCallbacks, 'onUndraw')
|
||||
co.drawing = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function builderUpdater(builder, tag='a') {
|
||||
return prototype(BuilderUpdateCallback, {builder,tag})
|
||||
}
|
||||
|
||||
|
||||
export function builderShape(builder, tag, shapeType, ...callbacks) {
|
||||
const updater = builderUpdater(builder, tag)
|
||||
console.log('updater', updater)
|
||||
drawShape(shapeType, updater, ...callbacks)
|
||||
}
|
||||
|
||||
|
||||
export function setPoints( shapeId, points ) {
|
||||
console.log('setting points', shapeId, points)
|
||||
widget.activeChart().getShapeById(shapeId).setPoints(points)
|
||||
}
|
||||
|
||||
|
||||
const shapeCallbacks = {}
|
||||
|
||||
function onSelectedLineToolChanged() {
|
||||
const tool = widget.selectedLineTool();
|
||||
console.log('line tool changed', tool)
|
||||
if( tool !== 'cursor' ) // 'cursor' cannot be selected manually and only happens just before the 'create' event is issued
|
||||
cancelDrawing();
|
||||
}
|
||||
|
||||
function handleDrawingEvent(id, event) {
|
||||
console.log('drawing event', id, event)
|
||||
const shape = event === 'remove' ? null : chart.getShapeById(id);
|
||||
if (event === 'create') {
|
||||
const co = useChartOrderStore();
|
||||
const callbacks = co.drawingCallbacks
|
||||
console.log('drawing callbacks', callbacks)
|
||||
if (callbacks !== null) {
|
||||
shapeCallbacks[id] = callbacks
|
||||
co.drawing = false
|
||||
const points = shape.getPoints()
|
||||
const props = shape.getProperties()
|
||||
invoke(shapeCallbacks[id], 'onCreate', id, points, props)
|
||||
invoke(shapeCallbacks[id], 'onPoints', id, points)
|
||||
invoke(shapeCallbacks[id], 'onProps', id, props)
|
||||
}
|
||||
} else if (event === 'points_changed') {
|
||||
if (id in shapeCallbacks) {
|
||||
const points = shape.getPoints()
|
||||
console.log('points', points)
|
||||
invoke(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)
|
||||
invoke(shapeCallbacks[id], 'onProps', id, props)
|
||||
}
|
||||
} else if (event === 'remove') {
|
||||
if (id in shapeCallbacks) {
|
||||
invoke(shapeCallbacks[id], 'onDelete', id)
|
||||
}
|
||||
} else
|
||||
console.log('unknown drawing event', event)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<v-btn variant="outlined">
|
||||
<v-icon v-if="icon" :icon="icon" :color="color"></v-icon> {{text}}
|
||||
<slot/>
|
||||
<v-btn :prepend-icon="icon" variant="plain" class="mx-2">
|
||||
<template v-slot:prepend>
|
||||
<v-icon :color="color"></v-icon>
|
||||
</template>
|
||||
<slot name="text">{{text}}</slot>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<script setup>
|
||||
import {FixedNumber} from "ethers";
|
||||
import {useStore} from "@/store/store";
|
||||
import {computed, defineAsyncComponent, reactive} from "vue";
|
||||
import {computed, defineAsyncComponent, ref} from "vue";
|
||||
import Btn from "@/components/Btn.vue"
|
||||
import {cancelAll, cancelOrder} from "@/blockchain/wallet.js";
|
||||
import {dateString, pairPriceAddr} from "@/misc.js";
|
||||
@@ -108,7 +108,7 @@ const TokenAmount = defineAsyncComponent(()=>import('./TokenAmount.vue'))
|
||||
const s = useStore()
|
||||
const props = defineProps(['vault'])
|
||||
const vaultAddr = computed(()=>props.vault?props.vault:s.vault)
|
||||
const inverted = reactive({})
|
||||
const inverted = ref({})
|
||||
|
||||
|
||||
// <thead>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<slot name="top"/>
|
||||
</div>
|
||||
<div class="resizer bg-grey-lighten-2" :data-direction="horizontal?'horizontal':'vertical'" ref="resizerElement"></div>
|
||||
<div class="bottom">
|
||||
<div class="scrollpane">
|
||||
<slot name="bottom"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,7 +138,7 @@ body {
|
||||
min-height: 5em;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
.scrollpane {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 5em;
|
||||
|
||||
28
src/components/chart/Builder.vue
Normal file
28
src/components/chart/Builder.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<component :is="component" :builder="builder"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from "vue";
|
||||
import DCABuilder from "@/components/chart/DCABuilder.vue";
|
||||
import LimitBuilder from "@/components/chart/LimitBuilder.vue";
|
||||
|
||||
const props = defineProps(['builder'])
|
||||
|
||||
const component = computed(()=>{
|
||||
console.log('builder component', props.builder)
|
||||
switch (props.builder.component) {
|
||||
case 'DCABuilder':
|
||||
return DCABuilder
|
||||
case 'LimitBuilder':
|
||||
return LimitBuilder
|
||||
default:
|
||||
console.error('Unknown builder component '+props.builder.component)
|
||||
return null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
28
src/components/chart/BuilderPanel.vue
Normal file
28
src/components/chart/BuilderPanel.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div :key="builder.id">
|
||||
<v-card-text :color="titleColor">
|
||||
<slot name="title"><span>Unimplemented</span></slot>
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
<slot name="text">
|
||||
Unimplemented builder panel
|
||||
</slot>
|
||||
<div><v-btn variant="tonal" color="error" @click="co.removeBuilder(builder)" prepend-icon="mdi-delete">DELETE</v-btn></div>
|
||||
</v-card-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {computed} from "vue";
|
||||
|
||||
const props = defineProps(['builder', 'color', 'colorTag'])
|
||||
const co = useChartOrderStore()
|
||||
|
||||
const titleColor = computed(()=>props.color ? props.color : props.colorTag ? props.builder.props[props.colorTag]?.linecolor : props.builder.props.a.linecolor )
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -5,46 +5,19 @@
|
||||
<script setup>
|
||||
import "/tradingview/charting_library/charting_library.js"
|
||||
import "/tradingview/datafeeds/udf/dist/bundle.js"
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {useShapeStore} from "@/store/store.js";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {initWidget} from "@/chart.js";
|
||||
|
||||
const element = ref()
|
||||
|
||||
let widget = null;
|
||||
|
||||
onMounted(() => {
|
||||
const el = element.value;
|
||||
widget = window.tvWidget = new TradingView.widget({
|
||||
library_path: "/tradingview/charting_library/",
|
||||
// debug: true,
|
||||
autosize: true,
|
||||
symbol: 'AAPL',
|
||||
interval: '1D',
|
||||
container: el,
|
||||
datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"),
|
||||
locale: "en",
|
||||
disabled_features: [],
|
||||
enabled_features: [],
|
||||
drawings_access: {
|
||||
type: 'white',
|
||||
tools: [
|
||||
{name: 'Ray'},
|
||||
{name: 'Trend Line'},
|
||||
{name: 'Horizontal Line'},
|
||||
{name: 'Horizontal Ray'},
|
||||
{name: 'Vertical Line'},
|
||||
{name: 'Extended Line'},
|
||||
]
|
||||
},
|
||||
});
|
||||
widget.subscribe('drawing_event', handleDrawingEvent)
|
||||
initWidget(el)
|
||||
initShapes()
|
||||
})
|
||||
|
||||
const ss = useShapeStore()
|
||||
|
||||
function initShapes() {
|
||||
// const c = widget.activeChart()
|
||||
// const c = widget.chart()
|
||||
// for( const s of ss.shapes ) {
|
||||
// const type = s.type.toLowerCase().replace(' ','_')
|
||||
// console.log('create type', type)
|
||||
@@ -52,46 +25,6 @@ function initShapes() {
|
||||
// }
|
||||
}
|
||||
|
||||
function handleDrawingEvent(id, event) {
|
||||
if (event === 'create') {
|
||||
const shape = widget.activeChart().getShapeById(id);
|
||||
const points = shape.getPoints()
|
||||
const props = shape.getProperties()
|
||||
let type;
|
||||
if (points.length === 1)
|
||||
// vertical or horizontal line
|
||||
type = props.textOrientation === 'vertical' ? 'Vertical Line' : props.extendLeft || props.extendRight ? 'Horizontal Ray' : 'Horizontal Line'
|
||||
else if (points.length === 2) {
|
||||
type = !props.extendLeft && !props.extendRight ? 'Trend Line' : props.extendLeft && props.extendRight ? 'Extended Line' : 'Ray'
|
||||
} else
|
||||
console.log('unknown shape', points, props)
|
||||
console.log('create shape', type, points)
|
||||
ss.shapes.push({id, type, points, props})
|
||||
} else if (event === 'points_changed') {
|
||||
const s = ss.shape(id)
|
||||
if( !s ) {
|
||||
console.log('warning: ignoring drawing event for non-existant shape')
|
||||
return
|
||||
}
|
||||
const points = widget.activeChart().getShapeById(id).getPoints()
|
||||
if (s.points_changed)
|
||||
s.points_changed(points)
|
||||
ss.shape(id).points = points
|
||||
} else if (event === 'properties_changed') {
|
||||
const s = ss.shape(id)
|
||||
if( !s ) {
|
||||
const props = widget.activeChart().getShapeById(id).getProperties()
|
||||
console.log('warning: ignoring drawing event for non-existant shape', props)
|
||||
return
|
||||
}
|
||||
const props = widget.activeChart().getShapeById(id).getProperties()
|
||||
if (s.properties_changed)
|
||||
s.properties_changed(props)
|
||||
ss.shape(id).props = props
|
||||
} else
|
||||
console.log('unknown drawing event', event)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-expansion-panels class="d-flex">
|
||||
<chart-tranche v-for="s in ss.shapes" :shape="s"/>
|
||||
<!-- <v-expansion-panel v-for="s in ss.shapes" :title="s.type" :text="JSON.stringify(s)"/>-->
|
||||
</v-expansion-panels>
|
||||
<div class="d-flex flex-column h-100">
|
||||
<toolbar/>
|
||||
<!-- <div v-for="b in co.builderList">{{JSON.stringify(b)}}</div>-->
|
||||
<div class="overflow-y-auto">
|
||||
<template v-for="b in co.builderList">
|
||||
<v-divider/>
|
||||
<builder :builder="b"/>
|
||||
</template>
|
||||
<v-divider/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useShapeStore} from "@/store/store.js";
|
||||
import ChartTranche from "@/components/chart/ChartTranche.vue";
|
||||
const ss = useShapeStore()
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import Toolbar from "@/components/chart/Toolbar.vue";
|
||||
import Builder from "@/components/chart/Builder.vue";
|
||||
|
||||
const co = useChartOrderStore()
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style lang="scss">
|
||||
body {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<limit-tranche v-if="shape?.type==='Horizontal Line' || shape?.type==='Horizontal Ray'" :shape="shape"/>
|
||||
<line-tranche v-if="shape?.type==='Trend Line' || shape?.type==='Extended Line' || shape?.type==='Ray'" :shape="shape"/>
|
||||
<market-tranche v-if="shape?.type==='Vertical Line'" :shape="shape"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LimitTranche from "@/components/chart/LimitTranche.vue";
|
||||
import LineTranche from "@/components/chart/LineTranche.vue";
|
||||
import MarketTranche from "@/components/chart/MarketTranche.vue";
|
||||
|
||||
const props = defineProps(['shape'])
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
32
src/components/chart/DCABuilder.vue
Normal file
32
src/components/chart/DCABuilder.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<builder-panel :builder="builder">
|
||||
<template v-slot:title>
|
||||
<span>DCA</span>
|
||||
<span v-if="!builder.points">Draw your timeframe on the chart!</span>
|
||||
</template>
|
||||
<template v-slot:text>
|
||||
<input type="number" min="1" max="2">
|
||||
</template>
|
||||
</builder-panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
|
||||
import {ShapeCallback, drawShape, ShapeType, VerboseCallback} from "@/chart.js";
|
||||
import {prototype} from "@/blockchain/common.js";
|
||||
|
||||
const props = defineProps(['builder'])
|
||||
|
||||
const DCAArtist = prototype(VerboseCallback, {
|
||||
onDraw(widget, chart) { console.log('dca start draw') },
|
||||
onCreate(widget, chart) { console.log('dca start draw') },
|
||||
})
|
||||
|
||||
drawShape(ShapeType.VLine, prototype(DCAArtist, {}))
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
86
src/components/chart/LimitBuilder.vue
Normal file
86
src/components/chart/LimitBuilder.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<!-- todo extract builder-panel --> <!-- :builder="builder" color-tag="limit" -->
|
||||
<v-card flat rounded="0">
|
||||
<v-toolbar dense :color="color">
|
||||
<v-toolbar-title>Limit</v-toolbar-title>
|
||||
<v-text-field type="number" v-model="price" density="compact" hide-details single-line class="justify-self-start"/>
|
||||
<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 '+ (price?price.toPrecision(5):'')"/>
|
||||
<v-list-item title="Delete" key="withdraw" value="withdraw" prepend-icon="mdi-delete" color="red" @click="del"/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-toolbar>
|
||||
<!-- <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>-->
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import {prototype} from "@/blockchain/common.js";
|
||||
import {builderShape, cancelDrawing, chart, crosshairPoint, setPoints, ShapeType, VerboseCallback} from "@/chart.js";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {timestamp} from "@/misc.js";
|
||||
|
||||
const co = useChartOrderStore()
|
||||
const props = defineProps(['builder'])
|
||||
|
||||
let updating = false
|
||||
|
||||
const color = computed(()=>{
|
||||
// todo saturate the color when the corresponding shape is selected https://github.com/Qix-/color
|
||||
return props.builder.props.limit?.linecolor
|
||||
})
|
||||
|
||||
const price = computed({
|
||||
get() {return props.builder.limit},
|
||||
set(p) {
|
||||
p = Number(p)
|
||||
console.log('set price', p)
|
||||
props.builder.limit = p;
|
||||
if( drawingShapeId ) {
|
||||
const shapeId = props.builder.shapes.limit;
|
||||
console.log('adjust shape', shapeId)
|
||||
updating = true
|
||||
setPoints(shapeId, [{time: timestamp(), price:p}])
|
||||
updating = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const drawingShapeId = ref(null)
|
||||
|
||||
|
||||
function del() {
|
||||
co.removeBuilder(props.builder)
|
||||
console.log('remove shape', drawingShapeId.value)
|
||||
if( drawingShapeId.value )
|
||||
chart.removeEntity(drawingShapeId.value)
|
||||
}
|
||||
|
||||
|
||||
const callbacks = {
|
||||
onUndraw() {console.log('on undraw')},
|
||||
onCreate(shapeId, points, props) {drawingShapeId.value = shapeId},
|
||||
onPoints(shapeId, points) {props.builder.limit = points[0].price; console.log('limit onPoints', points[0].price)},
|
||||
onDelete(shapeId) {drawingShapeId.value=null; del()},
|
||||
}
|
||||
|
||||
|
||||
if (!props.builder.limit)
|
||||
builderShape(props.builder, 'limit', ShapeType.HLine, callbacks)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<tranche-panel :shape="shape">
|
||||
<template v-slot:title>
|
||||
<span>Limit {{shape.points[0].price}}</span>
|
||||
</template>
|
||||
</tranche-panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TranchePanel from "@/components/chart/TranchePanel.vue";
|
||||
const props = defineProps(['shape'])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<tranche-panel :shape="shape">
|
||||
<template v-slot:title>
|
||||
<span>Diagonal Limit (current value {{curValue.toPrecision(5)}})</span>
|
||||
</template>
|
||||
<template v-slot:text>
|
||||
<div>{{shape.points}}</div>
|
||||
<div>{{s.clock}}</div>
|
||||
<div>{{JSON.stringify(shape.props)}}</div>
|
||||
</template>
|
||||
</tranche-panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TranchePanel from "@/components/chart/TranchePanel.vue";
|
||||
import {computed} from "vue";
|
||||
import {linePointsValue} from "@/orderbuild.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
|
||||
const props = defineProps(['shape'])
|
||||
|
||||
const s = useStore()
|
||||
|
||||
const curValue = computed(()=>{
|
||||
if( ! props.shape?.points )
|
||||
return 'N/A'
|
||||
const p0 = props.shape.points[0]
|
||||
const p1 = props.shape.points[1]
|
||||
return linePointsValue(p0.time, p0.price, p1.time, p1.price, s.clock)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<tranche-panel :shape="shape">
|
||||
<template v-slot:title>
|
||||
<span>Market Order at {{dateString(shape.points[0].time)}}</span>
|
||||
</template>
|
||||
</tranche-panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TranchePanel from "@/components/chart/TranchePanel.vue";
|
||||
import {dateString} from "@/misc.js";
|
||||
const props = defineProps(['shape'])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
49
src/components/chart/Toolbar.vue
Normal file
49
src/components/chart/Toolbar.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="d-flex">
|
||||
<span class="arrow align-self-start"><v-icon icon="mdi-arrow-up-bold" color="green"/></span>
|
||||
<span class="logo">dexorder</span>
|
||||
<v-chip text="ALPHA" size='x-small' color="red" class="align-self-start" variant="text"/>
|
||||
<v-btn variant="flat" prepend-icon="mdi-clock-outline" @click="build('DCABuilder')">DCA</v-btn>
|
||||
<v-btn variant="flat" prepend-icon="mdi-ray-vertex" @click="build('LimitBuilder')">Limit</v-btn>
|
||||
<v-btn variant="flat" prepend-icon="mdi-vector-line">Line</v-btn>
|
||||
<v-btn variant="flat" prepend-icon="mdi-question" @click="test">Test</v-btn>
|
||||
<!--
|
||||
mdi-ray-start-end
|
||||
mdi-vector-polyline
|
||||
-->
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {chart, drawShape, ShapeType} from "@/chart.js";
|
||||
import {timestamp} from "@/misc.js";
|
||||
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
|
||||
|
||||
let shape = null
|
||||
|
||||
|
||||
function test() {
|
||||
if( shape === null )
|
||||
drawShape(ShapeType.VLine, {onCreate:function (shapeId){shape=shapeId}})
|
||||
else
|
||||
chart.getShapeById(shape).setPoints([{time:timestamp()}])
|
||||
}
|
||||
|
||||
|
||||
function build(component, options={}) {
|
||||
co.addBuilder(component, options)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.arrow {
|
||||
font-size: 22px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<v-expansion-panel rounded="0">
|
||||
<v-expansion-panel-title :color="shape.props.linecolor">
|
||||
<slot name="title"><span>{{shape.type}}</span></slot>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<slot name="text">
|
||||
<div>{{shape.points}}</div>
|
||||
<div>{{JSON.stringify(shape.props)}}</div>
|
||||
</slot>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(['shape'])
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,8 +1,6 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<nav-drawer/>
|
||||
<main-view/>
|
||||
<footer/>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +9,5 @@
|
||||
import NavDrawer from "@/components/NavDrawer.vue";
|
||||
import Footer from "@/components/Footer.vue";
|
||||
import {useStore} from "@/store/store.js";
|
||||
|
||||
const s = useStore()
|
||||
</script>
|
||||
|
||||
@@ -2,10 +2,53 @@ import {routeInverted, timestamp} from "@/misc.js";
|
||||
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||
import {useOrderStore} from "@/store/store.js";
|
||||
import {encodeIEE754} from "@/blockchain/common.js";
|
||||
import {defineStore} from "pinia";
|
||||
import {computed, ref} from "vue";
|
||||
|
||||
|
||||
export function applyLimit(tranche, price=null, isMinimum=null) {
|
||||
if( price === null ) {
|
||||
function unimplemented() { throw Error('Unimplemented') }
|
||||
|
||||
// Builders are data objects which store a configuration state
|
||||
function TrancheBuilder( component, options = {}) {
|
||||
const id = 'builder-'+Date.now();
|
||||
return {id, component, options, points: {}, shapes: {}, props: {}, build: unimplemented}
|
||||
}
|
||||
|
||||
|
||||
export const useChartOrderStore = defineStore('chart_orders', () => {
|
||||
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]))
|
||||
return builderIdList.value.map((id)=>builderDict.value[id])
|
||||
}) // convenience
|
||||
const builderDict = ref({}) // builders stored by id
|
||||
|
||||
const drawing = ref(false)
|
||||
const drawingCallbacks = ref(null) // only during draw mode
|
||||
|
||||
function addBuilder(component, options={}) {
|
||||
const b = TrancheBuilder(component,options)
|
||||
builderIdList.value.push(b.id)
|
||||
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 }
|
||||
})
|
||||
|
||||
|
||||
export function applyLimit(tranche, price = null, isMinimum = null) {
|
||||
if (price === null) {
|
||||
const os = useOrderStore()
|
||||
price = os.limitPrice
|
||||
if (!price)
|
||||
@@ -29,27 +72,26 @@ function computeInterceptSlope(time0, price0, time1, price1) {
|
||||
}
|
||||
|
||||
|
||||
export function linePointsValue(time0, price0, time1, price1, unixTime=null) {
|
||||
if(unixTime===null)
|
||||
export function linePointsValue(time0, price0, time1, price1, unixTime = null) {
|
||||
if (unixTime === null)
|
||||
unixTime = timestamp()
|
||||
try {
|
||||
const [intercept, slope] = computeInterceptSlope(time0, price0, time1, price1)
|
||||
return intercept + unixTime * slope
|
||||
}
|
||||
catch (e) {
|
||||
console.log('error computing line',e)
|
||||
} catch (e) {
|
||||
console.log('error computing line', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function applyLinePoints(tranche, time0, price0, time1, price1, isMinimum=null) {
|
||||
export function applyLinePoints(tranche, time0, price0, time1, price1, isMinimum = null) {
|
||||
const [intercept, slope] = computeInterceptSlope(time0, price0, time1, price1);
|
||||
applyLine(tranche, intercept, slope, isMinimum)
|
||||
}
|
||||
|
||||
|
||||
export function applyLine(tranche, intercept, slope, isMinimum=null) {
|
||||
export function applyLine(tranche, intercept, slope, isMinimum = null) {
|
||||
console.log('intercept, slope', intercept, slope)
|
||||
// intercept and slope are still in "human" units of decimal-adjusted prices
|
||||
const os = useOrderStore()
|
||||
@@ -62,7 +104,7 @@ export function applyLine(tranche, intercept, slope, isMinimum=null) {
|
||||
console.log('inverted b, m, cur', inverted, b, m, cur)
|
||||
m = encodeIEE754(m)
|
||||
b = encodeIEE754(b)
|
||||
if( isMinimum === null )
|
||||
if (isMinimum === null)
|
||||
isMinimum = os.limitIsMinimum
|
||||
console.log('limit is minimum', isMinimum)
|
||||
if (isMinimum) {
|
||||
@@ -83,8 +125,8 @@ export function timesliceTranches() {
|
||||
const timeUnitIndex = os.timeUnitIndex;
|
||||
let duration =
|
||||
timeUnitIndex === 0 ? interval * 60 : // minutes
|
||||
timeUnitIndex === 1 ? interval * 60 * 60 : // hours
|
||||
interval * 24 * 60 * 60; // days
|
||||
timeUnitIndex === 1 ? interval * 60 * 60 : // hours
|
||||
interval * 24 * 60 * 60; // days
|
||||
let window
|
||||
if (!os.intervalIsTotal) {
|
||||
window = duration
|
||||
@@ -92,7 +134,7 @@ export function timesliceTranches() {
|
||||
} else {
|
||||
window = Math.round(duration / n)
|
||||
}
|
||||
if( window < 60 )
|
||||
if (window < 60)
|
||||
window = 60 // always allow at least one minute for execution
|
||||
const amtPerTranche = Math.ceil(MAX_FRACTION / n)
|
||||
duration -= 15 // subtract 15 seconds so the last tranche completes before the deadline
|
||||
@@ -109,3 +151,4 @@ export function timesliceTranches() {
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Utilities
|
||||
import {defineStore} from 'pinia'
|
||||
import {knownTokens} from "@/knownTokens.js";
|
||||
import {computed, reactive, ref} from "vue";
|
||||
import {computed, ref} from "vue";
|
||||
|
||||
// USING THE STORE:
|
||||
//
|
||||
@@ -10,7 +10,7 @@ import {computed, reactive, ref} from "vue";
|
||||
//
|
||||
// defineStore('foostore', ()=>{
|
||||
// const foo = ref(true)
|
||||
// const obj = reactive({})
|
||||
// const obj = ref({})
|
||||
// function reset() {
|
||||
// obj.value = {}
|
||||
// foo.value = false
|
||||
@@ -36,11 +36,6 @@ console.log('version', version)
|
||||
|
||||
export const useStore = defineStore('app', ()=> {
|
||||
const clock = ref(timestamp())
|
||||
setInterval(()=>{
|
||||
clock.value= timestamp()
|
||||
console.log('triggered clock', clock.value)
|
||||
}, 10*1000)
|
||||
|
||||
const nav = ref(false) // controls opening navigation drawer
|
||||
|
||||
const _chainId = ref(null)
|
||||
@@ -121,6 +116,8 @@ export const useStore = defineStore('app', ()=> {
|
||||
this.extraTokens = extras
|
||||
}
|
||||
|
||||
setInterval(()=>clock.value=timestamp(), 10*1000)
|
||||
|
||||
return {
|
||||
nav, chainId, chainInfo, chain, provider, vaultInitCodeHash, account, vaults, transactionSenders, errors,
|
||||
extraTokens, poolPrices, vaultBalances, orders, vault, tokens, factory, helper, mockenv, mockCoins,
|
||||
@@ -190,15 +187,3 @@ export const usePrefStore = defineStore('prefs', ()=> {
|
||||
return {inverted,}
|
||||
})
|
||||
|
||||
export const useShapeStore = defineStore('shapes', ()=> {
|
||||
const shapes = reactive([])
|
||||
|
||||
function shape(id) {
|
||||
for (const s of shapes) {
|
||||
if (s.id === id)
|
||||
return s
|
||||
}
|
||||
return null
|
||||
}
|
||||
return {shapes, shape}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
@use "/src/styles/vars" as v;
|
||||
|
||||
#app {
|
||||
.logo {
|
||||
font-family: v.$heading-font-family;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
@@ -25,26 +31,27 @@
|
||||
font-family: v.$heading-font-family;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.v-field__input {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.v-text-field.text-end input {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.v-input {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
//input {
|
||||
// text-align: center;
|
||||
//}
|
||||
//
|
||||
//.v-field__input {
|
||||
// justify-content: center;
|
||||
//}
|
||||
//
|
||||
//.v-text-field.text-end input {
|
||||
// text-align: end;
|
||||
//}
|
||||
//
|
||||
//.v-input {
|
||||
// margin-top: 1em;
|
||||
// margin-bottom: 1em;
|
||||
//}
|
||||
|
||||
.maxw {
|
||||
max-width: v.$card-maxw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.uniswap-color {
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
<script setup>
|
||||
import SplitPane from "@/components/SplitPane.vue";
|
||||
import Chart from "@/components/chart/Chart.vue";
|
||||
import {useShapeStore} from "@/store/store.js";
|
||||
|
||||
const ss = useShapeStore()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
30
yarn.lock
30
yarn.lock
@@ -448,11 +448,27 @@ color-convert@^2.0.1:
|
||||
dependencies:
|
||||
color-name "~1.1.4"
|
||||
|
||||
color-name@~1.1.4:
|
||||
color-name@^1.0.0, color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-string@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
|
||||
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
|
||||
dependencies:
|
||||
color-name "^1.0.0"
|
||||
simple-swizzle "^0.2.2"
|
||||
|
||||
color@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
|
||||
integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
|
||||
dependencies:
|
||||
color-convert "^2.0.1"
|
||||
color-string "^1.9.0"
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
@@ -845,6 +861,11 @@ inherits@2:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
is-arrayish@^0.3.1:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
|
||||
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
@@ -1202,6 +1223,13 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
simple-swizzle@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
|
||||
integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
|
||||
dependencies:
|
||||
is-arrayish "^0.3.1"
|
||||
|
||||
socket.io-client@^4.7.2:
|
||||
version "4.7.3"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.3.tgz#b49e006fc1ccaea65229a4b435c083b10439ecc4"
|
||||
|
||||
Reference in New Issue
Block a user