247 lines
8.1 KiB
JavaScript
247 lines
8.1 KiB
JavaScript
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
|