interactive horizontal limit lines
This commit is contained in:
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>
|
||||
Reference in New Issue
Block a user