rate limit DCA
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {invokeCallbacks, prototype} from "@/common.js";
|
||||
import {DataFeed, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js";
|
||||
import {DataFeed, defaultSymbol, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js";
|
||||
import {intervalToSeconds, SingletonCoroutine} from "@/misc.js";
|
||||
import {usePrefStore, useStore} from "@/store/store.js";
|
||||
import {tvCustomThemes} from "../../theme.js";
|
||||
@@ -24,9 +24,10 @@ export function removeSymbolChangedCallback(cb) {
|
||||
}
|
||||
|
||||
function symbolChanged(symbol) {
|
||||
const info = symbol===null ? null : lookupSymbol(symbol.ticker)
|
||||
const info = symbol===null ? (defaultSymbol===null?'default':defaultSymbol) : lookupSymbol(symbol.ticker)
|
||||
co.selectedSymbol = info
|
||||
prefs.selectedTicker = info?.ticker
|
||||
console.log('setting prefs ticker', info.ticker)
|
||||
prefs.selectedTicker = info.ticker
|
||||
symbolChangedCbs.forEach((cb) => cb(info))
|
||||
updateFeeDropdown()
|
||||
console.log('symbol changed', info)
|
||||
@@ -149,6 +150,7 @@ export function initWidget(el) {
|
||||
drawings_access: {type: 'white', tools: [],}, // show no tools
|
||||
custom_themes: tvCustomThemes,
|
||||
theme: useStore().theme,
|
||||
timezone: prefs.timezone,
|
||||
|
||||
// Chart Overrides
|
||||
// https://www.tradingview.com/charting-library-docs/latest/customization/overrides/chart-overrides
|
||||
@@ -182,8 +184,7 @@ function initChart() {
|
||||
chart.onIntervalChanged().subscribe(null, changeInterval)
|
||||
chart.onDataLoaded().subscribe(null, dataLoaded)
|
||||
const tzapi = chart.getTimezoneApi();
|
||||
tzapi.onTimezoneChanged().subscribe(null, (tz)=>{if (tz==='exchange') tz='Etc/UTC'; s.timeZone=tz})
|
||||
s.timeZone = tzapi.getTimezone().id
|
||||
tzapi.onTimezoneChanged().subscribe(null, (tz)=>{if (tz==='exchange') tz='Etc/UTC'; prefs.timezone=tz;})
|
||||
// chart.onHoveredSourceChanged().subscribe(null, ()=>console.log('hovered source changed', arguments))
|
||||
// chart.selection().onChanged().subscribe(null, s => console.log('selection', chart.selection().allSources()));
|
||||
const symbolExt = chart.symbolExt();
|
||||
@@ -358,7 +359,7 @@ function doHandleCrosshairMovement(point) {
|
||||
const lpbe = shape._model._linePointBeingEdited
|
||||
points[lpbe] = point
|
||||
// console.log('drag calling onPoints', points, shape, lpbe)
|
||||
invokeCallbacks(shapeCallbacks[shapeId], 'onPoints', shapeId, shape, points)
|
||||
invokeCallbacks(shapeCallbacks[shapeId], 'onDrag', shapeId, shape, points)
|
||||
}
|
||||
}
|
||||
else if (draggingShapeIds.length > 0) {
|
||||
|
||||
@@ -131,8 +131,13 @@ function addSymbol(chainId, p, base, quote, inverted) {
|
||||
else
|
||||
feeGroups[feelessKey] = [[symbolInfo.address, symbolInfo.fee]]
|
||||
symbolInfo.feeGroup = feeGroups[feelessKey]
|
||||
if (defaultSymbol===null && !invertedDefault(symbolInfo.base.a, symbolInfo.quote.a))
|
||||
if (defaultSymbol===null) {
|
||||
console.log(`invertedDefault(${symbolInfo.base.s}, ${symbolInfo.quote.s})`,invertedDefault(symbolInfo.base.a, symbolInfo.quote.a))
|
||||
}
|
||||
if (defaultSymbol===null && !invertedDefault(symbolInfo.base.a, symbolInfo.quote.a)) {
|
||||
console.log('setting default symbol', symbolInfo.base.s, symbolInfo.quote.s, symbolInfo.base.a, symbolInfo.quote.a)
|
||||
defaultSymbol = _symbols[ticker]
|
||||
}
|
||||
log('new symbol', ticker, _symbols[ticker])
|
||||
}
|
||||
|
||||
@@ -684,4 +689,4 @@ export const DataFeed = {
|
||||
|
||||
|
||||
let _rolloverBumper = null
|
||||
let defaultSymbol = null
|
||||
export let defaultSymbol = null
|
||||
|
||||
@@ -36,6 +36,7 @@ export const ShapeType = {
|
||||
HLine: {name: 'Horizontal Line', code: 'horizontal_line', drawingProp: 'linetoolhorzline'},
|
||||
VLine: {name: 'Vertical Line', code: 'vertical_line', drawingProp: 'linetoolvertline'},
|
||||
PriceRange: {name: 'Price Range', code: 'price_range'},
|
||||
DateRange: {name: 'Date Range', code: 'date_range', drawingProp: 'linetooldaterange'},
|
||||
}
|
||||
|
||||
|
||||
@@ -335,6 +336,8 @@ export class Shape {
|
||||
|
||||
onPoints(points) {} // the control points of an existing shape were changed
|
||||
|
||||
onDrag(points) { console.log('shape ondrag'); this.onPoints(points) }
|
||||
|
||||
setProps(props) {
|
||||
if (!props || Object.keys(props).length===0) return
|
||||
if (this.debug) console.log('setProps', this.id, props)
|
||||
@@ -384,7 +387,6 @@ export class Shape {
|
||||
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)
|
||||
onMove(points) {} // the shape was moved by dragging a drawing element not the control point
|
||||
onDrag(points) {}
|
||||
onHide(props) {}
|
||||
onShow(props) {}
|
||||
onClick() {} // the shape was selected
|
||||
@@ -479,16 +481,6 @@ class ShapeTVCallbacks {
|
||||
|
||||
|
||||
export class Line extends Shape {
|
||||
onDrag(points) {
|
||||
const s = this.tvShape();
|
||||
if (this.debug) {
|
||||
console.log('shape', s.id, 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -656,3 +648,17 @@ export class DLine extends Line {
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
export class DateRange extends Shape {
|
||||
constructor(model, onModel=null, onDelete=null, props=null) {
|
||||
super(ShapeType.DateRange, onModel, onDelete, props)
|
||||
}
|
||||
|
||||
|
||||
setModel(model) {
|
||||
super.setModel(model);
|
||||
if (model.startTime !== this.model.startTime || model.endTime !== this.model.endTime)
|
||||
this.setPoints([{time: model.startTime}, {time: model.endTime}])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import {usePrefStore, useStore} from "@/store/store";
|
||||
import {computed} from "vue";
|
||||
import {DateTime, Info} from "luxon";
|
||||
|
||||
const s = useStore()
|
||||
const prefs = usePrefStore()
|
||||
const props = defineProps(['modelValue'])
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const hideDetails = true
|
||||
|
||||
const now = computed(()=>DateTime.fromSeconds(props.modelValue?props.modelValue:0).setZone(s.timeZone))
|
||||
const now = computed(()=>DateTime.fromSeconds(props.modelValue?props.modelValue:0).setZone(prefs.timezone))
|
||||
|
||||
const year = computed({
|
||||
get() { return now.value.year },
|
||||
|
||||
@@ -93,16 +93,9 @@
|
||||
<template v-for="(t, i) in item.order.tranches">
|
||||
<tr>
|
||||
<td class="text-right" colspan="2">
|
||||
<div class="text-right">
|
||||
<div v-if="s.clock < item.trancheStatus[i].startTime">
|
||||
Activates {{ timestampString(item.trancheStatus[i].startTime) }}
|
||||
</div>
|
||||
<div v-if="s.clock < item.trancheStatus[i].endTime && item.trancheStatus[i].endTime < DISTANT_FUTURE">
|
||||
Expires {{ timestampString(item.trancheStatus[i].endTime) }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mx-3" v-if="t.marketOrder">market order</div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitFraction === MAX_FRACTION">market order</div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitFraction < MAX_FRACTION">DCA {{Math.round(MAX_FRACTION/t.rateLimitFraction)}} parts</div>
|
||||
<line-price class="mx-3" v-if="!t.marketOrder"
|
||||
:base="item.base" :quote="item.quote"
|
||||
:b="t.minLine.intercept" :m="t.minLine.slope" :is-breakout="!item.minIsLimit"
|
||||
@@ -112,7 +105,10 @@
|
||||
:b="t.maxLine.intercept" :m="t.maxLine.slope" :is-breakout="item.minIsLimit"
|
||||
:show-btn="true"/>
|
||||
</div>
|
||||
|
||||
<div class="text-right" v-if="item.state===OrderState.Open">
|
||||
<div>{{ describeTrancheTime(item, i, true) }}</div>
|
||||
<div>{{ describeTrancheTime(item, i, false) }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<suspense>
|
||||
@@ -170,7 +166,7 @@ import {useStore} from "@/store/store";
|
||||
import {computed, defineAsyncComponent, onUnmounted, ref, watch} from "vue";
|
||||
import Btn from "@/components/Btn.vue"
|
||||
import {cancelOrder, useWalletStore} from "@/blockchain/wallet.js";
|
||||
import {DISTANT_FUTURE, isOpen, OrderState} from "@/blockchain/orderlib.js";
|
||||
import {DISTANT_FUTURE, isOpen, MAX_FRACTION, OrderState} from "@/blockchain/orderlib.js";
|
||||
import Pulse from "@/components/Pulse.vue";
|
||||
import {OrderShapes} from "@/charts/ordershapes.js";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
@@ -376,7 +372,6 @@ const orders = computed(()=>{
|
||||
function describeTrancheTime(st, trancheIndex, isStart) {
|
||||
const t = st.order.tranches[trancheIndex]
|
||||
const ts = st.trancheStatus[trancheIndex]
|
||||
console.log('describeTT', t, ts)
|
||||
let result = ''
|
||||
if( t.minIsBarrier || t.maxIsBarrier )
|
||||
return 'barrier'
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
<script setup>
|
||||
import {computed} from "vue";
|
||||
import DCABuilder from "@/components/chart/DCABuilder.vue";
|
||||
import DateBuilder from "@/components/chart/DateBuilder.vue";
|
||||
import LimitBuilder from "@/components/chart/LimitBuilder.vue";
|
||||
import MarketBuilder from "@/components/chart/MarketBuilder.vue";
|
||||
import DiagonalBuilder from "@/components/chart/DiagonalBuilder.vue";
|
||||
import DCABuilder from "@/components/chart/DCABuilder.vue";
|
||||
|
||||
const props = defineProps(['order', 'builder'])
|
||||
|
||||
@@ -21,6 +22,8 @@ const component = computed(()=>{
|
||||
return LimitBuilder
|
||||
case 'DiagonalBuilder':
|
||||
return DiagonalBuilder
|
||||
case 'DateBuilder':
|
||||
return DateBuilder
|
||||
default:
|
||||
console.error('Unknown builder component '+props.builder.component)
|
||||
return null
|
||||
|
||||
@@ -2,17 +2,8 @@
|
||||
<row-bar :color="builder.color">
|
||||
<color-band :color="builder.color"/>
|
||||
<slot/>
|
||||
<div class="align-self-center">
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="plain" v-bind="props" icon="mdi-dots-vertical"/>
|
||||
</template>
|
||||
<v-list>
|
||||
<!-- <v-list-subheader :title="'Limit '+ (priceA?priceA.toPrecision(5):'')"/>-->
|
||||
<v-list-item title="Delete" key="withdraw" value="withdraw" prepend-icon="mdi-delete" color="red"
|
||||
@click="deleteMyBuilder"/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<div class="align-self-center ml-auto mr-3 trashcan">
|
||||
<v-btn icon="mdi-delete" @click="deleteMyBuilder"/>
|
||||
</div>
|
||||
</row-bar>
|
||||
</template>
|
||||
@@ -60,5 +51,11 @@ function deleteMyBuilder() {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/vars";
|
||||
|
||||
.trashcan {
|
||||
:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,33 +3,13 @@
|
||||
<row-bar :color="color">
|
||||
<color-band :color="color"/>
|
||||
<div style="width: 100%" class="justify-start align-content-start">
|
||||
<v-text-field type="number" inputmode="numeric" pattern="[0-9]*\.?[0-9]*" v-model="order.amount" variant="outlined"
|
||||
density="compact"
|
||||
:hint="available" :persistent-hint="true"
|
||||
min="0"
|
||||
class="amount py-2" :color="color"
|
||||
:label="order.amountIsTokenA ? 'Amount':('Value in '+co.selectedSymbol.quote.s)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-btn variant="outlined" :color="color" class="ml-3"
|
||||
:text="(order.buy ? 'Buy ' : 'Sell ') + co.selectedSymbol.base.s"
|
||||
@click="order.buy=!order.buy"/>
|
||||
</template>
|
||||
<template #prepend-inner>
|
||||
<v-btn variant="text" text="max" class="px-0" size="small"
|
||||
:disabled="!maxAmount || order.amountIsTokenA===order.buy && !co.price" @click="setMax"/>
|
||||
</template>
|
||||
<template #append-inner>
|
||||
<v-btn :text="order.amountIsTokenA?co.baseToken.s:co.quoteToken.s+' worth'"
|
||||
:color="color" variant="text" @click="toggleAmountToken"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
<order-amount :order="props.order" :color="color"/>
|
||||
<template v-for="b in builders">
|
||||
<builder-factory :order="order" :builder="b"/>
|
||||
</template>
|
||||
<div class="my-3">
|
||||
<div v-if="order.builders.length===0"> <!--todo remove gralpha limitation of one builder-->
|
||||
<v-tooltip text="Spread order across time" location="top">
|
||||
<v-tooltip text="Up to 1000 equal parts spread across time" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
<v-btn :color="color" variant="text" prepend-icon="mdi-clock-outline" @click="build(order,'DCABuilder')">DCA</v-btn>
|
||||
@@ -50,6 +30,13 @@
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip text="Up to 10 weighted parts spaced out in time" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
<v-btn :color="color" variant="text" prepend-icon="mdi-reorder-vertical" @click="build(order,'DateBuilder')">Dates</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip text="Coming Soon! Stoplosses and Takeprofits" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
@@ -69,13 +56,14 @@
|
||||
import BuilderFactory from "@/components/chart/BuilderFactory.vue";
|
||||
import {builderFuncs, newBuilder, orderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {useOrderStore, useStore} from "@/store/store.js";
|
||||
import {computed, onUnmounted, onUpdated, ref, watchEffect} from "vue";
|
||||
import {lightenColor, lightenColor2, toPrecision} from "@/misc.js";
|
||||
import {computed, onUnmounted, onUpdated, watchEffect} from "vue";
|
||||
import {toPrecision} from "@/misc.js";
|
||||
import {useTheme} from "vuetify";
|
||||
import RowBar from "@/components/chart/RowBar.vue";
|
||||
import ColorBand from "@/components/chart/ColorBand.vue";
|
||||
import Color from "color";
|
||||
import {newOrder} from "@/blockchain/orderlib.js";
|
||||
import OrderAmount from "@/components/chart/OrderAmount.vue";
|
||||
|
||||
const props = defineProps(['order'])
|
||||
const s = useStore()
|
||||
@@ -199,55 +187,5 @@ const color = computed(()=>new Color(props.order.buy?theme.value.colors.success:
|
||||
// const lightColorStyle = computed(() => { return {'background-color': lightColor.value} })
|
||||
// const faintColorStyle = computed(() => { return {'background-color': faintColor.value} })
|
||||
|
||||
const inToken = computed( ()=>props.order.buy ? co.quoteToken : co.baseToken )
|
||||
const maxAmount = computed(()=>{
|
||||
const token = inToken.value;
|
||||
if (!token)
|
||||
return null
|
||||
const balance = s.balances[token.a]
|
||||
if( !balance )
|
||||
return null
|
||||
const divisor = os.amountIsTotal ? 1 : os.tranches
|
||||
return balance / 10**token.d / divisor
|
||||
})
|
||||
const available = computed(()=>{
|
||||
const max = maxAmount.value
|
||||
return max === null ? '' : `Available: ${maxAmount.value} ${tokenIn.value.s}`
|
||||
})
|
||||
|
||||
const lastMaxValue = ref(-1)
|
||||
|
||||
function setMax() {
|
||||
let amount = maxAmount.value
|
||||
if (amount) {
|
||||
const order = props.order
|
||||
const price = co.price
|
||||
if (order.amountIsTokenA===order.buy) {
|
||||
if (order.buy)
|
||||
amount /= price
|
||||
else
|
||||
amount *= price
|
||||
}
|
||||
amount = Number(amount.toPrecision(7))
|
||||
lastMaxValue.value = amount
|
||||
order.amount = amount
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAmountToken() {
|
||||
const order = props.order
|
||||
order.amountIsTokenA=!order.amountIsTokenA
|
||||
if (order.amount === lastMaxValue.value)
|
||||
setMax()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.amount {
|
||||
max-width: 30em;
|
||||
div.v-field {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,248 +1,267 @@
|
||||
<template class="d-flex align-content-center flex-column" style="height: 100%; width: 100%;">
|
||||
<rung-builder name='DCA' :order="order" :builder="builder" v-model="timeEndpoints"
|
||||
:shape="VLine"
|
||||
:mode="1" :flip="flipped" :orientation="0"
|
||||
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
||||
:get-points-value="getPointsValue"
|
||||
:set-values="setValues" :set-weights="setWeights"
|
||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||
<builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
|
||||
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes">
|
||||
<div class="d-flex flex-column" style="width: 100%;">
|
||||
<div class="d-flex flex-row">
|
||||
<div class="align-self-center">Start:</div>
|
||||
<absolute-time-entry v-model="startTime"/>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<v-list style="background-color: inherit">
|
||||
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
||||
</v-list>
|
||||
-->
|
||||
<div class="d-flex flex-row">
|
||||
<div>
|
||||
<v-text-field label="Split into" type="number" variant="outlined"
|
||||
aria-valuemin="1" aria-valuemax="100" min="1" max="1000" step="1"
|
||||
v-model="parts" v-auto-select class="parts mr-3">
|
||||
<template v-slot:append-inner>
|
||||
parts
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeA"/>
|
||||
</td>
|
||||
<td class="weight">{{ weights.length ? allocationTexts[0] : '' }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>2" v-for="i in weights.length-2" class="ml-5"> <!-- vue uses 1-based loops -->
|
||||
<td class="d-flex justify-end pr-3">{{ dateStrings[i] }}</td>
|
||||
<td class="weight">{{ allocationTexts[i] }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>1">
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeB"/>
|
||||
</td>
|
||||
<td class="weight">{{ allocationTexts[weights.length-1] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="mr-3">
|
||||
<order-amount :order="props.order" :multiplier="props.builder.tranches" :color="null" style="width: 20em"/>
|
||||
</div>
|
||||
|
||||
|
||||
</rung-builder>
|
||||
<div>
|
||||
<v-text-field type="number" variant="outlined" :min="1" v-model="displayedInterval" class="interval"
|
||||
:label="intervalIsTotal ? 'Total completion time' : 'Time between parts'" v-auto-select>
|
||||
<template v-slot:prepend-inner>
|
||||
<v-btn variant="outlined"
|
||||
:text="intervalIsTotal ? 'All within' : 'Spaced apart by'" class="within mr-2"
|
||||
style="width: 10em"
|
||||
@click="intervalIsTotal=!intervalIsTotal"/>
|
||||
</template>
|
||||
<template v-slot:append-inner>
|
||||
<v-btn variant="outlined" :text="timeUnitsStr" style="width: 5em"
|
||||
@click="toggleTimeUnits" class="time-units"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</builder-panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {builderDefaults, DEFAULT_SLIPPAGE, MIN_EXECUTION_TIME, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {allocationText, VLine} from "@/charts/shape.js";
|
||||
import {sideColor} from "@/misc.js";
|
||||
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 {builderDefaults, DEFAULT_SLIPPAGE, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {ShapeType} from "@/charts/shape.js";
|
||||
import {sideColor, vAutoSelect} from "@/misc.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {computed, ref, watchEffect} from "vue";
|
||||
import {chart, dragging} from "@/charts/chart.js";
|
||||
import {createShape, deleteShapeId, widget} from "@/charts/chart.js";
|
||||
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
||||
import {DateTime} from "luxon";
|
||||
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
|
||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||
import Color from "color";
|
||||
import OrderAmount from "@/components/chart/OrderAmount.vue";
|
||||
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
const co = useChartOrderStore()
|
||||
const props = defineProps(['order', 'builder'])
|
||||
const emit = defineEmits(['update:builder'])
|
||||
|
||||
const minWidth = computed(()=>co.intervalSecs)
|
||||
const stdWidth = computed(()=>10 * minWidth.value)
|
||||
|
||||
function computeDefaultColor() {
|
||||
const index = props.order.builders.indexOf(props.builder)
|
||||
return sideColor(props.order.buy, index)
|
||||
}
|
||||
|
||||
const defaultColor = computeDefaultColor()
|
||||
const color = computed(()=>computeDefaultColor())
|
||||
|
||||
const defaultTranches = 10
|
||||
|
||||
builderDefaults(props.builder, {
|
||||
timeA: s.clock, // todo relative 0
|
||||
timeB: null,
|
||||
startTime: s.clock, // todo relative 0
|
||||
interval: co.intervalSecs,
|
||||
tranches: defaultTranches,
|
||||
percentage: 100/defaultTranches,
|
||||
// relative: true, // todo
|
||||
relative: false,
|
||||
slippage: DEFAULT_SLIPPAGE,
|
||||
rungs: 1,
|
||||
skew: 0,
|
||||
color: defaultColor,
|
||||
buy: true,
|
||||
color: color.value,
|
||||
valid: true,
|
||||
})
|
||||
|
||||
const times = ref([])
|
||||
const weights = ref([])
|
||||
|
||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||
|
||||
const endTimes = computed(()=>{
|
||||
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 dateStrings = computed(()=>absoluteTimes.value.map((t)=>{
|
||||
const n = DateTime.fromSeconds(t).setZone(s.timeZone)
|
||||
const y = n.toLocaleString({year:'numeric'})
|
||||
const m = n.toLocaleString({month:'long'})
|
||||
const d = n.toLocaleString({day:'numeric'})
|
||||
const h = n.toLocaleString({hour:'numeric', minute:'numeric'})
|
||||
return `${y} ${m} ${d} ${h}`
|
||||
}))
|
||||
|
||||
watchEffect(()=>{
|
||||
// auto scroll
|
||||
if (!dragging && absoluteTimes.value.length) {
|
||||
const endTime = absoluteTimes.value[absoluteTimes.value.length-1]
|
||||
const range = chart.getVisibleRange()
|
||||
const width = range.to - range.from
|
||||
const now = s.clock
|
||||
const extra = (Math.max(0,endTime - now) + stdWidth.value/2) / width
|
||||
// console.log('visrange', range, width, absV)
|
||||
if (range.to < endTime) {
|
||||
// console.log('scrolling')
|
||||
chart.setVisibleRange({from: now - width, to: now}, {
|
||||
percentRightMargin: Math.round(100 * extra),
|
||||
applyDefaultRightMargin: false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const absTimeA = computed({
|
||||
get() { return _timeEndpoints.value[0] },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateA(v)
|
||||
}
|
||||
})
|
||||
const absTimeB = computed({
|
||||
get() { return _timeEndpoints.value[1] },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateB(v)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const _timeEndpoints = ref([props.builder.timeA, props.builder.timeB])
|
||||
const timeEndpoints = computed({
|
||||
get() { return _timeEndpoints.value},
|
||||
set(v) {
|
||||
const [a, b] = v
|
||||
update(a,b, true, true)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function updateA(a) {
|
||||
update(a, _timeEndpoints.value[1], true, false)
|
||||
}
|
||||
|
||||
|
||||
function updateB(b) {
|
||||
update(_timeEndpoints.value[0], b, false, true)
|
||||
}
|
||||
|
||||
|
||||
function update(a, b, updateA, updateB) {
|
||||
if (updateA) {
|
||||
const minB = a + minWidth.value
|
||||
if (b < minB)
|
||||
b = minB
|
||||
}
|
||||
if (updateB) {
|
||||
const maxA = b - minWidth.value
|
||||
if (a > maxA)
|
||||
a = maxA
|
||||
}
|
||||
_timeEndpoints.value = [a, b]
|
||||
const newBuilder = {...props.builder}
|
||||
newBuilder.timeA = a
|
||||
newBuilder.timeB = b
|
||||
emit('update:builder', newBuilder)
|
||||
}
|
||||
|
||||
const flipped = computed(()=>{
|
||||
const a = props.builder.timeA
|
||||
const b = props.builder.timeB
|
||||
return a !== null && b !== null && a > b
|
||||
})
|
||||
function adjustShapes() {}
|
||||
|
||||
function buildTranches() {
|
||||
const order = props.order
|
||||
const builder = props.builder
|
||||
const tranches = []
|
||||
const warnings = []
|
||||
|
||||
console.log('buildTranches', builder, order, tranches)
|
||||
const ts = times.value
|
||||
const ets = endTimes.value
|
||||
const ws = weights.value
|
||||
console.log('buildTranches times ends weights', ts, ets, ws)
|
||||
for(let i=0; i<ts.length; i++) {
|
||||
const endTime = Math.max(ets[i],ts[i]+60);
|
||||
console.log('time check', endTime, s.clock)
|
||||
if (endTime <= s.clock)
|
||||
warnings.push(`Tranche already expired at ${new Date(endTime*1000)}`)
|
||||
const t = newTranche({
|
||||
fraction: ws[i] * MAX_FRACTION,
|
||||
startTime: ts[i],
|
||||
endTime, // always give at least 60 seconds of window to execute
|
||||
slippage: builder.slippage,
|
||||
})
|
||||
tranches.push(t)
|
||||
let interval = props.builder.interval
|
||||
let parts = props.builder.tranches
|
||||
if (interval < 60) {
|
||||
interval = 60
|
||||
parts = (props.builder.endTime - props.builder.startTime) / interval
|
||||
warnings.push(`DCA parts cannot be closer than one minute apart. Using ${parts} parts.`)
|
||||
}
|
||||
const rate = Math.ceil(MAX_FRACTION / parts)
|
||||
const endTime = Math.ceil(props.builder.endTime + 10*props.builder.tranches)
|
||||
const startTime = Math.floor(props.builder.startTime);
|
||||
const tranches = [newTranche({marketOrder:true,
|
||||
startTime, endTime, rateLimitFraction: rate, rateLimitPeriod: Math.floor(interval)})]
|
||||
return {tranches, warnings}
|
||||
}
|
||||
|
||||
|
||||
function getModelValue(model) {
|
||||
if(!model) {
|
||||
console.log('getModelValue', model)
|
||||
return null
|
||||
const parts = computed({
|
||||
get() { return props.builder.tranches },
|
||||
set(v) {
|
||||
v = Number(v)
|
||||
v = Math.max(1, Math.min(1000,v))
|
||||
props.builder.tranches = v
|
||||
let interval = (props.builder.endTime-props.builder.startTime) / v
|
||||
if (interval < 60) {
|
||||
interval = 60
|
||||
props.builder.endTime = props.builder.startTime + interval * v
|
||||
setPoints(null, true)
|
||||
}
|
||||
return model.time
|
||||
}
|
||||
})
|
||||
|
||||
const intervalIsTotal = ref(false)
|
||||
const displayedInterval = computed({
|
||||
get() {
|
||||
let result = props.builder.interval / timeUnits[timeUnitIndex.value][1]
|
||||
if (intervalIsTotal.value)
|
||||
// express as per-part intervals
|
||||
result *= props.builder.tranches
|
||||
return result
|
||||
},
|
||||
set(v) {
|
||||
v = Number(v)
|
||||
let newValue = v * timeUnits[timeUnitIndex.value][1];
|
||||
if (intervalIsTotal.value)
|
||||
newValue /= props.builder.tranches
|
||||
if (Math.abs(newValue - props.builder.interval) >= Number.EPSILON) {
|
||||
props.builder.interval = newValue
|
||||
props.builder.endTime = props.builder.startTime + newValue * props.builder.tranches
|
||||
setPoints(null, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const timeUnits = [['minutes', 60], ['hours', 3600], ['days', 86400]]
|
||||
const _timeUnitIndex = ref(0);
|
||||
const timeUnitIndex = computed({
|
||||
get() {return _timeUnitIndex.value},
|
||||
set(v) {_timeUnitIndex.value = v % timeUnits.length}
|
||||
})
|
||||
const timeUnitsStr = computed(()=>timeUnits[timeUnitIndex.value][0])
|
||||
|
||||
function toggleTimeUnits() {
|
||||
timeUnitIndex.value += 1
|
||||
}
|
||||
|
||||
function getPointsValue(points) {
|
||||
return points[0].price
|
||||
const startTime = computed({
|
||||
get() { return props.builder.relative ? s.clock + props.builder.startTime : props.builder.startTime },
|
||||
set(v) {emitUpdate({startTime: v})}
|
||||
})
|
||||
|
||||
const endTime = computed({
|
||||
get() { return startTime.value + props.builder.interval * props.builder.tranches },
|
||||
set(v) {
|
||||
emitUpdate({endTime: v, interval: Math.abs(v - startTime.value)})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const barStart = computed(()=>ohlcStart(startTime.value, props.builder.interval))
|
||||
const barEnd = computed(()=>ohlcStart(endTime.value, props.builder.interval))
|
||||
let shapeWidth = barEnd.value - barStart.value // NOT reactively computed. We compare in onPoints()
|
||||
|
||||
function emitUpdatedPoints(a, b) {
|
||||
const updates = {}
|
||||
if (a.time !== barStart.value)
|
||||
updates.startTime = a.time
|
||||
// only set the end if it was moved relative to the start
|
||||
if (b.time - a.time === barEnd.value - barStart.value)
|
||||
// the end has moved exactly the same amount as the start. add a relative amount.
|
||||
updates.endTime = a.time + (endTime.value - startTime.value)
|
||||
else
|
||||
updates.endTime = b.time
|
||||
updates.interval = (b.time - a.time) / props.builder.tranches
|
||||
emitUpdate(updates)
|
||||
}
|
||||
|
||||
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)
|
||||
const v = value === null ? null : Math.round(value)
|
||||
if (model.time !== v) {
|
||||
// console.log('DCA do set time', v)
|
||||
model.time = v
|
||||
function setPoints(points=null, shapeCorrectionNeeded=false) {
|
||||
if (points === null)
|
||||
points = [{time:props.builder.startTime}, {time:props.builder.endTime}]
|
||||
let [a, b] = points
|
||||
const period = co.intervalSecs;
|
||||
|
||||
if (b.time < a.time) {
|
||||
[a, b] = [b, a]
|
||||
shapeCorrectionNeeded = true
|
||||
} else if (b.time === a.time) {
|
||||
b.time = a.time + period
|
||||
shapeCorrectionNeeded = true
|
||||
}
|
||||
|
||||
const curBarStart = ohlcStart(s.clock, period);
|
||||
if (a.time < curBarStart) {
|
||||
const width = b.time - a.time;
|
||||
a.time = s.clock
|
||||
b.time = a.time + width
|
||||
shapeCorrectionNeeded = true
|
||||
}
|
||||
emitUpdatedPoints(a, b);
|
||||
if (shapeCorrectionNeeded) {
|
||||
const [sa, sb] = shape.getPoints()
|
||||
const aTime = ohlcStart(a.time, period);
|
||||
const bTime = ohlcStart(b.time, period);
|
||||
if (sa.time !== aTime || sb.time !== bTime) {
|
||||
const aa = {time: aTime}
|
||||
const bb = {time: bTime}
|
||||
shape.setPoints([aa, bb])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setValues(values) {
|
||||
times.value = values.map((t)=>Math.round(t))
|
||||
const callbacks = {
|
||||
onDrag(shapeId, shape, points) {
|
||||
let [a, b] = points
|
||||
if (b.time < a.time)
|
||||
[a, b] = [b, a]
|
||||
emitUpdatedPoints(a, b)
|
||||
},
|
||||
onPoints(shapeId, shape, points) {
|
||||
setPoints(points, shape);
|
||||
},
|
||||
}
|
||||
|
||||
function setWeights(ws) { weights.value = ws }
|
||||
function emitUpdate(changes) {
|
||||
for (const [k, v] of Object.entries(changes))
|
||||
props.builder[k] = v
|
||||
}
|
||||
|
||||
const text = computed(()=>{
|
||||
let msg = props.order.buy ? 'Buy ' : 'Sell '
|
||||
msg += props.builder.tranches + ' parts'
|
||||
return msg
|
||||
})
|
||||
|
||||
let shapeId = createShape(ShapeType.DateRange, [{time:barStart.value}, {time:barEnd.value}], {}, callbacks)
|
||||
let shape = widget.activeChart().getShapeById(shapeId)
|
||||
function setProperties() {
|
||||
const color = computeDefaultColor()
|
||||
shape.setProperties({
|
||||
extendTop: true,
|
||||
extendBottom: true,
|
||||
linecolor: color,
|
||||
backgroundColor: new Color(color).alpha(0.2).toString(),
|
||||
backgroundTransparency: 60,
|
||||
customText: {
|
||||
text: text.value,
|
||||
visible: true,
|
||||
color: color,
|
||||
},
|
||||
})
|
||||
}
|
||||
setProperties()
|
||||
|
||||
watchEffect(setProperties)
|
||||
|
||||
function deleteShapes() {
|
||||
deleteShapeId(shapeId)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -251,5 +270,11 @@ td.weight {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.parts {
|
||||
width: 8em;
|
||||
}
|
||||
.interval {
|
||||
width: 22em;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
256
src/components/chart/DateBuilder.vue
Normal file
256
src/components/chart/DateBuilder.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template class="d-flex align-content-center flex-column" style="height: 100%; width: 100%;">
|
||||
<rung-builder name='Dates' :order="order" :builder="builder" v-model="timeEndpoints"
|
||||
:shape="VLine"
|
||||
:mode="1" :flip="flipped" :orientation="0"
|
||||
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
||||
:get-points-value="getPointsValue"
|
||||
:set-values="setValues" :set-weights="setWeights"
|
||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||
|
||||
<!--
|
||||
<v-list style="background-color: inherit">
|
||||
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
||||
</v-list>
|
||||
-->
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeA"/>
|
||||
</td>
|
||||
<td class="weight">{{ weights.length ? allocationTexts[0] : '' }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>2" v-for="i in weights.length-2" class="ml-5"> <!-- vue uses 1-based loops -->
|
||||
<td class="d-flex justify-end pr-3">{{ dateStrings[i] }}</td>
|
||||
<td class="weight">{{ allocationTexts[i] }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>1">
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeB"/>
|
||||
</td>
|
||||
<td class="weight">{{ allocationTexts[weights.length-1] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</rung-builder>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {builderDefaults, DEFAULT_SLIPPAGE, MIN_EXECUTION_TIME, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {allocationText, VLine} from "@/charts/shape.js";
|
||||
import {sideColor} from "@/misc.js";
|
||||
import {useOrderStore, usePrefStore, 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, watchEffect} from "vue";
|
||||
import {chart, dragging} from "@/charts/chart.js";
|
||||
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
||||
import {DateTime} from "luxon";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
const co = useChartOrderStore()
|
||||
const prefs = usePrefStore()
|
||||
const props = defineProps(['order', 'builder'])
|
||||
const emit = defineEmits(['update:builder'])
|
||||
|
||||
const minWidth = computed(()=>co.intervalSecs)
|
||||
const stdWidth = computed(()=>10 * minWidth.value)
|
||||
|
||||
function computeDefaultColor() {
|
||||
const index = props.order.builders.indexOf(props.builder)
|
||||
return sideColor(props.order.buy, index)
|
||||
}
|
||||
|
||||
const defaultColor = computeDefaultColor()
|
||||
|
||||
builderDefaults(props.builder, {
|
||||
timeA: s.clock, // todo relative 0
|
||||
timeB: null,
|
||||
// relative: true, // todo
|
||||
relative: false,
|
||||
slippage: DEFAULT_SLIPPAGE,
|
||||
rungs: 1,
|
||||
skew: 0,
|
||||
color: defaultColor,
|
||||
buy: true,
|
||||
})
|
||||
|
||||
const times = ref([])
|
||||
const weights = ref([])
|
||||
|
||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||
|
||||
const endTimes = computed(()=>{
|
||||
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 dateStrings = computed(()=>absoluteTimes.value.map((t)=>{
|
||||
const n = DateTime.fromSeconds(t).setZone(prefs.timezone)
|
||||
const y = n.toLocaleString({year:'numeric'})
|
||||
const m = n.toLocaleString({month:'long'})
|
||||
const d = n.toLocaleString({day:'numeric'})
|
||||
const h = n.toLocaleString({hour:'numeric', minute:'numeric'})
|
||||
return `${y} ${m} ${d} ${h}`
|
||||
}))
|
||||
|
||||
watchEffect(()=>{
|
||||
// auto scroll
|
||||
if (!dragging && absoluteTimes.value.length) {
|
||||
const endTime = absoluteTimes.value[absoluteTimes.value.length-1]
|
||||
const range = chart.getVisibleRange()
|
||||
const width = range.to - range.from
|
||||
const now = s.clock
|
||||
const extra = (Math.max(0,endTime - now) + stdWidth.value/2) / width
|
||||
// console.log('visrange', range, width, absV)
|
||||
if (range.to < endTime) {
|
||||
// console.log('scrolling')
|
||||
chart.setVisibleRange({from: now - width, to: now}, {
|
||||
percentRightMargin: Math.round(100 * extra),
|
||||
applyDefaultRightMargin: false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const absTimeA = computed({
|
||||
get() { return _timeEndpoints.value[0] },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateA(v)
|
||||
}
|
||||
})
|
||||
const absTimeB = computed({
|
||||
get() { return _timeEndpoints.value[1] },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateB(v)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const _timeEndpoints = ref([props.builder.timeA, props.builder.timeB])
|
||||
const timeEndpoints = computed({
|
||||
get() { return _timeEndpoints.value},
|
||||
set(v) {
|
||||
const [a, b] = v
|
||||
update(a,b, true, true)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function updateA(a) {
|
||||
update(a, _timeEndpoints.value[1], true, false)
|
||||
}
|
||||
|
||||
|
||||
function updateB(b) {
|
||||
update(_timeEndpoints.value[0], b, false, true)
|
||||
}
|
||||
|
||||
|
||||
function update(a, b, updateA, updateB) {
|
||||
if (updateA) {
|
||||
const minB = a + minWidth.value
|
||||
if (b < minB)
|
||||
b = minB
|
||||
}
|
||||
if (updateB) {
|
||||
const maxA = b - minWidth.value
|
||||
if (a > maxA)
|
||||
a = maxA
|
||||
}
|
||||
_timeEndpoints.value = [a, b]
|
||||
const newBuilder = {...props.builder}
|
||||
newBuilder.timeA = a
|
||||
newBuilder.timeB = b
|
||||
emit('update:builder', newBuilder)
|
||||
}
|
||||
|
||||
const flipped = computed(()=>{
|
||||
const a = props.builder.timeA
|
||||
const b = props.builder.timeB
|
||||
return a !== null && b !== null && a > b
|
||||
})
|
||||
|
||||
function buildTranches() {
|
||||
const order = props.order
|
||||
const builder = props.builder
|
||||
const tranches = []
|
||||
const warnings = []
|
||||
|
||||
console.log('buildTranches', builder, order, tranches)
|
||||
const ts = times.value
|
||||
const ets = endTimes.value
|
||||
const ws = weights.value
|
||||
console.log('buildTranches times ends weights', ts, ets, ws)
|
||||
for(let i=0; i<ts.length; i++) {
|
||||
const endTime = Math.max(ets[i],ts[i]+60);
|
||||
console.log('time check', endTime, s.clock)
|
||||
if (endTime <= s.clock)
|
||||
warnings.push(`Tranche already expired at ${new Date(endTime*1000)}`)
|
||||
const t = newTranche({
|
||||
fraction: ws[i] * MAX_FRACTION,
|
||||
startTime: ts[i],
|
||||
endTime, // always give at least 60 seconds of window to execute
|
||||
slippage: builder.slippage,
|
||||
})
|
||||
tranches.push(t)
|
||||
}
|
||||
return {tranches, warnings}
|
||||
}
|
||||
|
||||
|
||||
function getModelValue(model) {
|
||||
if(!model) {
|
||||
console.log('getModelValue', model)
|
||||
return null
|
||||
}
|
||||
return model.time
|
||||
}
|
||||
|
||||
function getPointsValue(points) {
|
||||
return points[0].price
|
||||
}
|
||||
|
||||
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)
|
||||
const v = value === null ? null : Math.round(value)
|
||||
if (model.time !== v) {
|
||||
// console.log('DCA do set time', v)
|
||||
model.time = v
|
||||
}
|
||||
}
|
||||
|
||||
function setValues(values) {
|
||||
times.value = values.map((t)=>Math.round(t))
|
||||
}
|
||||
|
||||
function setWeights(ws) { weights.value = ws }
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
td.weight {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
100
src/components/chart/OrderAmount.vue
Normal file
100
src/components/chart/OrderAmount.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<v-text-field type="number" inputmode="numeric" pattern="[0-9]*\.?[0-9]*" v-model="amount" variant="outlined"
|
||||
density="compact"
|
||||
:hint="available" :persistent-hint="true"
|
||||
min="0"
|
||||
class="amount py-2" :color="color"
|
||||
:label="props.order.amountIsTokenA ?
|
||||
(multiplier ? 'Amount each' : 'Amount') :
|
||||
((multiplier ? 'Value each in ' : 'Value in ')+co.selectedSymbol.quote.s)">
|
||||
<template #prepend>
|
||||
<v-btn v-if="!multiplier"
|
||||
variant="outlined" :color="color" class="ml-3"
|
||||
:text="(props.order.buy ? 'Buy ' : 'Sell ') + co.selectedSymbol.base.s"
|
||||
@click="props.order.buy=!props.order.buy"/>
|
||||
</template>
|
||||
<template #prepend-inner>
|
||||
<v-btn variant="text" text="max" class="px-0" size="small"
|
||||
:disabled="!maxAmount || props.order.amountIsTokenA===props.order.buy && !co.price" @click="setMax"/>
|
||||
</template>
|
||||
<template #append-inner>
|
||||
<v-btn :text="props.order.amountIsTokenA?co.baseToken.s:co.quoteToken.s+' worth'"
|
||||
:color="color" variant="text" @click="toggleAmountToken" style="width: 7em"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {computed, ref} from "vue";
|
||||
import {useStore} from "@/store/store.js";
|
||||
|
||||
const props = defineProps(['order', 'multiplier', 'color'])
|
||||
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
|
||||
const amount = computed({
|
||||
get() {
|
||||
let result = props.order.amount
|
||||
if (result !== null && props.multiplier)
|
||||
result /= props.multiplier
|
||||
return result
|
||||
},
|
||||
set(v) {
|
||||
if (v !== null && props.multiplier)
|
||||
v *= props.multiplier
|
||||
props.order.amount = v
|
||||
}
|
||||
})
|
||||
|
||||
const available = computed(()=>{
|
||||
const max = maxAmount.value
|
||||
return max === null ? '' : `Available: ${maxAmount.value} ${inToken.value.s}`
|
||||
})
|
||||
|
||||
const inToken = computed( ()=>props.order.buy ? co.quoteToken : co.baseToken )
|
||||
|
||||
const maxAmount = computed(()=>{
|
||||
const token = inToken.value;
|
||||
if (!token)
|
||||
return null
|
||||
const balance = s.balances[token.a]
|
||||
if( !balance )
|
||||
return null
|
||||
return balance / 10**token.d
|
||||
})
|
||||
|
||||
const lastMaxValue = ref(-1)
|
||||
|
||||
function setMax() {
|
||||
let amount = maxAmount.value
|
||||
if (amount) {
|
||||
const order = props.order
|
||||
const price = co.price
|
||||
if (order.amountIsTokenA===order.buy) {
|
||||
if (order.buy)
|
||||
amount /= price
|
||||
else
|
||||
amount *= price
|
||||
}
|
||||
amount = Number(amount.toPrecision(7))
|
||||
lastMaxValue.value = amount
|
||||
order.amount = amount
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAmountToken() {
|
||||
const order = props.order
|
||||
order.amountIsTokenA=!order.amountIsTokenA
|
||||
if (order.amount === lastMaxValue.value)
|
||||
setMax()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.amount {
|
||||
max-width: 30em;
|
||||
}
|
||||
</style>
|
||||
@@ -97,7 +97,7 @@ export function intervalString(seconds) {
|
||||
}
|
||||
|
||||
export function timestampString(seconds) {
|
||||
const date = DateTime.fromSeconds(seconds).setZone(useStore().timeZone)
|
||||
const date = DateTime.fromSeconds(seconds).setZone(usePrefStore().timezone)
|
||||
return dateString(date)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ const REQUIRE_APPROVAL = import.meta.env.VITE_REQUIRE_APPROVAL !== 'NO';
|
||||
export const useStore = defineStore('app', ()=> {
|
||||
const clock = ref(timestamp()) // the clock ticks infrequently enough to be mostly stable for user display
|
||||
setInterval(()=>clock.value=timestamp(), 10*1000) // 10 secs
|
||||
const timeZone = ref('Etc/UTC')
|
||||
|
||||
const nav = ref(false) // controls opening navigation drawer
|
||||
const theme = ref('dark')
|
||||
@@ -144,7 +143,7 @@ export const useStore = defineStore('app', ()=> {
|
||||
transactionSenders, errors, extraTokens, poolPrices, vaultBalances, orders, vault, version, upgrade, vaultOrders,
|
||||
tokens, factory, helper, theme,
|
||||
mockenv, mockCoins,
|
||||
removeTransactionSender, error, closeError, addToken, clock, timeZone, balances,
|
||||
removeTransactionSender, error, closeError, addToken, clock, balances,
|
||||
approved, regionApproved, walletApproved,
|
||||
getBalance
|
||||
}
|
||||
@@ -215,7 +214,8 @@ export const usePrefStore = defineStore({
|
||||
const acceptedTos = ref('NO TOS ACCEPTED')
|
||||
const selectedTicker = ref(null)
|
||||
const selectedTimeframe = ref(null)
|
||||
return {inverted, acceptedTos, selectedTicker, selectedTimeframe}
|
||||
const timezone = ref('Etc/UTC')
|
||||
return {inverted, acceptedTos, selectedTicker, selectedTimeframe, timezone}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user