import {timestamp, uuid} from "@/misc.js"; import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js"; import {useOrderStore, useStore} from "@/store/store.js"; import {encodeIEE754} from "@/common.js"; import {defineStore} from "pinia"; import {computed, ref} from "vue"; import Color from "color"; export const MIN_EXECUTION_TIME = 60 // give at least one full minute for each tranche to trigger export const DEFAULT_SLIPPAGE = 0.0010; // Builders are data objects which store a configuration state // the component name must match a corresponding Vue component in the BuilderFactory.vue component, which is responsible // for instantiating the UI component for a given builder dictionary, based on its builder.component field. export function newBuilder( component, options = {}) { const id = uuid() return { id, component, options, allocation: 1.0, points: {}, shapes: {}, props: {} } } // Orders hold an amount and builders // noinspection JSUnusedLocalSymbols const Order = { id: uuid(), amount: 0, builders: [], } // the key is order.id and the value is a function() that returns an order export const orderFuncs = {} // the key is order.builder.id and the value is a function() that returns an array of tranches // if null is returned, the builder inputs are not valid and the place order button will be disabled export const builderFuncs = {} function newDefaultOrder() { return { id:uuid(), valid: false, amount:null, amountIsTokenA: true, buy: true, builders:[] } } export const useChartOrderStore = defineStore('chart_orders', () => { const chartReady = ref(false) const defaultOrder = newDefaultOrder() const orders = ref([defaultOrder]) // order models in UI format const selectedOrder = ref(null) const selectedSymbol = ref(null) const intervalSecs = ref(0) const baseToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.base) const quoteToken = computed(()=>selectedSymbol.value === null ? null : selectedSymbol.value.quote) const price = computed(() => { if (!selectedSymbol.value) return null const s = useStore() let result = s.poolPrices[[s.chainId, selectedSymbol.address]] if (selectedSymbol.value.inverted) result = 1 / result return result }) const meanRange = ref(1) const drawing = ref(false) function newOrder() { const order = newDefaultOrder() orders.value.push(order) selectedOrder.value = order } function removeOrder(order) { let index = orders.value.findIndex((o)=>o.id===order.id) if (index === -1) return const result = orders.value.filter((o)=>o.id!==order.id) if (result.length === 0) { const order = newDefaultOrder() result.push(order) selectedOrder.value = order } else selectedOrder.value = result[Math.max(0,index-1)] // select the order above the removed one orders.value = result } function resetOrders() { const order = newDefaultOrder() orders.value = [order] selectedOrder.value = order } return { chartReady, selectedSymbol, intervalSecs, baseToken, quoteToken, price, orders, drawing, newOrder, removeOrder, resetOrders, meanRange, } }) export function applyLimitOld(tranche, price = null, isMinimum = null) { // todo deprecate. used by old forms-based components. if (price === null) { const os = useOrderStore() price = os.limitPrice if (!price) return } applyLineOld(tranche, price, 0, isMinimum) } function computeInterceptSlope(time0, price0, time1, price1) { if (!time0 || !price0 && price0 !== 0 || !time1 || !price1 && price1 !== 0) throw Error(`invalid line points data ${time0} ${price0} ${time1} ${price1}`) const t0 = time0 const t1 = time1 if (t0 === t1) throw Error("line points' times must be different") const slope = (price1 - price0) / (t1 - t0) const intercept = price1 - slope * t1 return [intercept, slope] } 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) return null } } export function applyLinePoint(tranche, symbol, buy, price0, breakout=false) { applyLine(tranche, symbol, buy, price0, 0, breakout) } export function applyLinePoints(tranche, symbol, buy, time0, price0, time1, price1, breakout=false) { const [intercept, slope] = computeInterceptSlope(time0, price0, time1, price1); applyLine(tranche, symbol, buy, intercept, slope, breakout) } function applyLine(tranche, symbol, buy, intercept, slope=0, breakout=false) { const scale = 10 ** -symbol.decimals let m = slope * scale let b = intercept * scale m = encodeIEE754(m) b = encodeIEE754(b) const isMax = (buy !== symbol.inverted) !== breakout // buy XOR inverted XOR breakout console.log('apply line point isMax?', buy?'buy':'sell', symbol.inverted, breakout, isMax) console.log('applyLine current line value', isMax?'max':'min', m*timestamp()+b, 1./(m*timestamp()+b)) if (isMax) { tranche.maxLine.intercept = b; tranche.maxLine.slope = m; } else { tranche.minLine.intercept = b; tranche.minLine.slope = m; } tranche.marketOrder = false; } export function timesliceTranches() { const ts = [] const os = useOrderStore() const n = os.tranches // num tranches const interval = os.interval; const timeUnitIndex = os.timeUnitIndex; let duration = timeUnitIndex === 0 ? interval * 60 : // minutes timeUnitIndex === 1 ? interval * 60 * 60 : // hours interval * 24 * 60 * 60; // days let window if (!os.intervalIsTotal) { window = duration duration *= n // duration is the total time for all tranches } else { window = Math.round(duration / n) } 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 for (let i = 0; i < n; i++) { const start = Math.floor(i * (duration / Math.max((n - 1), 1))) const end = start + window ts.push(newTranche({ fraction: amtPerTranche, startTimeIsRelative: true, startTime: start, endTimeIsRelative: true, endTime: end, })) } return ts } export function builderDefaults(builder, defaults) { for (const k in defaults) if (builder[k] === undefined) builder[k] = defaults[k] instanceof Function ? defaults[k]() : defaults[k] } export function linearWeights(num, skew) { if (num === 1) return [1] const result = [] if (skew === 0) { // equal weighted for (let i = 0; i < num; i++) result.push(1 / num) } else if (skew === 1) { result.push(1) for (let i = 1; i < num; i++) result.push(0) } else if (skew === -1) { for (let i = 1; i < num; i++) result.push(0) result.push(1) } else { for (let i = 0; i < num; i++) result.push((1 - skew * (2 * i / (num - 1) - 1)) / num) } // console.log('weights', result) return result } export function weightColors(weights, color) { const c = new Color(color).rgb() const max = Math.max(...weights) const ns = weights.map((w) => w / max) // set largest weight to 100% const adj = ns.map((w) => c.alpha(Math.pow(w, 0.67))) // https://en.wikipedia.org/wiki/Stevens's_power_law return adj.map((a) => a.string()) } export function deleteBuilder(order, builder) { order.builders = order.builders.filter((b) => b !== builder) } // Fields must be defined in order to be reactive