diff --git a/src/blockchain/uniswap.js b/src/blockchain/uniswap.js index 2ae1f67..ea4969c 100644 --- a/src/blockchain/uniswap.js +++ b/src/blockchain/uniswap.js @@ -1,4 +1,4 @@ -import {ethers} from "ethers"; +import {ethers, FixedNumber} from "ethers"; const UNISWAPV3_POOL_INIT_CODE_HASH = '0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54' const uniswapV3Addresses = { @@ -26,4 +26,14 @@ export function uniswapV3PoolAddress(chainId, tokenAddrA, tokenAddrB, fee) { return null } return ethers.getCreate2Address(factory, salt, UNISWAPV3_POOL_INIT_CODE_HASH) +} + + +export function uniswapV3AveragePrice(amountIn, amountOut, fee) { + if (amountIn === 0n || amountOut === 0n) return null + const fmtX18 = {decimals: 18, width: 256, signed: false}; + let result = FixedNumber.fromValue(amountOut, 0, fmtX18) + .div(FixedNumber.fromValue(amountIn, 0, fmtX18)).toUnsafeFloat() + result /= (1 - fee / 1_000_000) // adjust for pool fee + return result } \ No newline at end of file diff --git a/src/charts/chart.js b/src/charts/chart.js index fa186e2..35ed386 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -8,7 +8,6 @@ export let widget = null export let chart = null export let crosshairPoint = null let symbolChangedCbs = [] // callbacks for TV's chart.onSymbolChanged() -let lastSymbolChangedArgs = null const s = useStore() @@ -23,17 +22,22 @@ export function removeSymbolChangedCallback(cb) { } function changeSymbol(symbol) { + console.log('change symbol', symbol) if (symbol===null) co.selectedSymbol = null else { - const info = lookupSymbol(symbol.full_name) - lastSymbolChangedArgs = info + const info = lookupSymbol(symbol.ticker) symbolChangedCbs.forEach((cb) => cb(info)) co.selectedSymbol = info } } +export function setSymbol(symbol) { + widget.activeChart().setSymbol(symbol.ticker) +} + + function changeInterval(interval, _timeframe) { co.intervalSecs = intervalToSeconds(interval) DataFeed.intervalChanged(co.intervalSecs) @@ -100,7 +104,14 @@ function initChart() { s.timeZone = tzapi.getTimezone().id // chart.onHoveredSourceChanged().subscribe(null, ()=>console.log('hovered source changed', arguments)) // chart.selection().onChanged().subscribe(null, s => console.log('selection', chart.selection().allSources())); - changeSymbol(chart.symbolExt()) + const symbolExt = chart.symbolExt(); + console.log('symbolExt', symbolExt); + if(symbolExt) { + changeSymbol(symbolExt) + } + else { + changeSymbol(null) + } changeInterval(widget.symbolInterval().interval) co.chartReady = true console.log('chart ready') diff --git a/src/charts/datafeed.js b/src/charts/datafeed.js index c9a122e..44daf69 100644 --- a/src/charts/datafeed.js +++ b/src/charts/datafeed.js @@ -222,7 +222,17 @@ function getAllSymbols() { } export function lookupSymbol(key) { // lookup by ticker which is "0xbaseAddress/0xquoteAddress" - return getAllSymbols()[key] + const symbols = getAllSymbols(); + if (!(key in symbols)) { + // check the inverted symbol + const [base,quote] = key.split('/') + key = quote+'/'+base + if (!(key in symbols)) { + console.error('no symbol found for key', key, symbols) + return null + } + } + return symbols[key] } function checkBar(bar, msg) { diff --git a/src/charts/ordershapes.js b/src/charts/ordershapes.js index 7ab3ddc..6bc8d7d 100644 --- a/src/charts/ordershapes.js +++ b/src/charts/ordershapes.js @@ -70,7 +70,7 @@ class TrancheShapes { // console.log('price', price) const channel = buy?'low':'high'; const text = (buy ? 'Buy ' : 'Sell ') + allocationText(null, f.filled, '') - const s = createShape(buy?'arrow_up':'arrow_down', {time, price}, {channel,text}) + const s = createShape(buy?'arrow_up':'arrow_down', {time, price}, {channel,text,lock:true}) console.log('created fill shape at', time, price) this.fills.push(s) } @@ -90,7 +90,7 @@ class TrancheShapes { price: intercept * scale, color, // todo allocation maxAllocation amount amountSymbol - }) + }, null, null, null, true) this.shapes.push(s) } else { // diagonal line @@ -103,7 +103,7 @@ class TrancheShapes { extendRight: t.endTime === DISTANT_FUTURE, color, // todo allocation maxAllocation amount amountSymbol - }) + }, null, null, null, true) this.shapes.push(s) } } diff --git a/src/charts/shape.js b/src/charts/shape.js index 6c6c477..c6ee2b8 100644 --- a/src/charts/shape.js +++ b/src/charts/shape.js @@ -41,7 +41,7 @@ export const ShapeType = { export class Shape { - constructor(type, onModel=null, onDelete=null, props=null) { + constructor(type, onModel=null, onDelete=null, props=null, readonly=false, overrides={}) { // the Shape object manages synchronizing internal data with a corresponding TradingView shape // each shape in the class hierarchy overrides setModel() to cause effects in TradingView @@ -64,7 +64,10 @@ export class Shape { this.tvCallbacks = null this.ourPoints = null this.tvPoints = null - this.creationOptions = {disableSave:true, disableUndo:true, disableSelection:false} + this.creationOptions = readonly ? + {disableSave:true, disableUndo:true, disableSelection:true, lock:true} : + {disableSave:true, disableUndo:true, disableSelection:false} + this.creationOptions.overrides = overrides this.ourProps = {} if (props !== null) this.ourProps = mixin(props, this.ourProps) @@ -437,8 +440,13 @@ export class Line extends Shape { export class HLine extends Line { - constructor(model, onModel=null, onDelete=null, props=null) { - super(ShapeType.HLine, onModel, onDelete, props) + constructor(model, onModel=null, onDelete=null, props=null, readonly=false) { + super(ShapeType.HLine, onModel, onDelete, props, readonly, { + // todo this isnt working + linestyle: 0, + linewidth: 2, + italic: false, + }) // Model this.model.price = null @@ -513,8 +521,10 @@ export class VLine extends Line { export class DLine extends Line { - constructor(model, onModel=null, onDelete=null, props=null) { - super(ShapeType.Ray, onModel, onDelete, props) + constructor(model, onModel=null, onDelete=null, props=null, readonly=false) { + super(ShapeType.Ray, onModel, onDelete, props, readonly,{ + // todo style overrides + }) // Model this.model.pointA = null // {time:..., price:...} diff --git a/src/components/Status.vue b/src/components/Status.vue index a8acab8..ce007f5 100644 --- a/src/components/Status.vue +++ b/src/components/Status.vue @@ -31,12 +31,9 @@ / - - - - + - + Tranche {{ i + 1 }} @@ -99,15 +96,15 @@ + :buy="item.order.tokenIn===item.token1" :show-btn="false"/> + :buy="item.order.tokenIn===item.token1" :show-btn="false"/> - + @@ -122,12 +119,13 @@ - + + @@ -137,8 +135,12 @@ - avg price - status + + + + + + {{ item.trancheStatus[i].status }}(todo:status) @@ -149,7 +151,7 @@ import LinePrice from "@/components/LinePrice.vue"; import {FixedNumber} from "ethers"; import {useStore} from "@/store/store"; -import {computed, defineAsyncComponent, ref, watch, watchEffect} from "vue"; +import {computed, defineAsyncComponent, onUnmounted, ref, watch} from "vue"; import Btn from "@/components/Btn.vue" import {cancelOrder, PendingOrderState, useWalletStore} from "@/blockchain/wallet.js"; import {timestampString} from "@/misc.js"; @@ -158,6 +160,8 @@ import Pulse from "@/components/Pulse.vue"; import {OrderShapes} from "@/charts/ordershapes.js"; import {useChartOrderStore} from "@/orderbuild.js"; import {lookupSymbol} from "@/charts/datafeed.js"; +import {setSymbol} from "@/charts/chart.js"; +import {uniswapV3AveragePrice} from "@/blockchain/uniswap.js"; const PairPrice = defineAsyncComponent(()=>import("@/components/PairPrice.vue")) const TokenAmount = defineAsyncComponent(()=>import('./TokenAmount.vue')) @@ -169,6 +173,7 @@ const ws = useWalletStore() const props = defineProps(['vault']) const vaultAddr = computed(()=>props.vault?props.vault:s.vault) const selected = ref([]) + watch(selected, ()=>{ const statusIndex = {} for (const order of orders.value) @@ -184,6 +189,10 @@ watch(selected, ()=>{ [base, quote] = [quote, base] const symbolKey = `${base}/${quote}` const symbol = lookupSymbol(symbolKey) + if (co.selectedSymbol.ticker !== symbolKey) { + co.selectedSymbol = symbol + setSymbol(symbol) + } orderShapes[id] = new OrderShapes(symbol, status) } } @@ -196,8 +205,13 @@ watch(selected, ()=>{ } } }) + const orderShapes = {} +onUnmounted(()=>{ + for (const s of Object.values(orderShapes)) + s.delete() +}) const datatableHeaders = [ {title: 'Date', align: 'start', key: 'start'}, @@ -283,14 +297,12 @@ const orders = computed(()=>{ return t }) */ - const fmtX18 = {decimals: 18, width: 256, signed: false}; st.filled = o.amountIsInput ? st.filledIn : st.filledOut + const fee = st.order.route.fee; if(st.filled === 0n) st.avg = null - else { - st.avg = FixedNumber.fromValue(status.filledOut, 0, fmtX18) - .div(FixedNumber.fromValue(status.filledIn, 0, fmtX18)).toUnsafeFloat() * (1+st.order.route.fee/1_000_000); - } + else + st.avg = uniswapV3AveragePrice(status.filledIn, status.filledOut, fee) st.amountToken = o.amountIsInput ? o.tokenIn : o.tokenOut st.input = o.amountIsInput ? o.amount : 0 st.output = !o.amountIsInput ? o.amount : 0 @@ -308,6 +320,7 @@ const orders = computed(()=>{ ts.filledIn = filledIn ts.filledOut = filledOut ts.filled = o.amountIsInput ? filledIn : filledOut + ts.avg = uniswapV3AveragePrice(filledIn, filledOut, fee) } } } diff --git a/src/components/TokenAmount.vue b/src/components/TokenAmount.vue index a32de9b..4632841 100644 --- a/src/components/TokenAmount.vue +++ b/src/components/TokenAmount.vue @@ -12,10 +12,14 @@ const token = await getToken(props.chainId, props.addr) const fmtAmount = computed(() => { if( props.amount === null || props.amount === undefined ) return '' - return FixedNumber.fromValue(props.amount, token.d, { + const fixed = FixedNumber.fromValue(props.amount, token.d, { width: 256, decimals: token.d - }).toUnsafeFloat().toPrecision(5) // todo precision + }) + let p = fixed.toUnsafeFloat().toPrecision(5); + if (p.includes('e')) + p = parseFloat(p) + return p }) diff --git a/src/orderbuild.js b/src/orderbuild.js index 7f975b6..552131d 100644 --- a/src/orderbuild.js +++ b/src/orderbuild.js @@ -140,39 +140,40 @@ export function linePointsValue(time0, price0, time1, price1, unixTime = null) { export function applyLinePoint(tranche, symbol, buy, price0) { console.log('applyLinePoint', buy?'BUY':'SELL', symbol, price0) - if (symbol.inverted) - price0 = 1/price0 price0 *= 10 ** -symbol.decimals + const inverted = buy === symbol.inverted + if (inverted) + price0 = 1/price0 applyLine(tranche, symbol, buy, price0, 0) } export function applyLinePoints(tranche, symbol, buy, time0, price0, time1, price1) { - if (symbol.inverted) { - price0 = 1/price0 - price1 = 1/price1 - } const scale = 10 ** -symbol.decimals price0 *= scale price1 *= scale + const inverted = buy === symbol.inverted + if (inverted) { + price0 = 1/price0 + price1 = 1/price1 + } const [intercept, slope] = computeInterceptSlope(time0, price0, time1, price1); applyLine(tranche, symbol, buy, intercept, slope) } -function applyLine(tranche, symbol, buy, intercept, slope) { +function applyLine(tranche, symbol, buy, intercept, slope, isMaximum=false) { let m = slope let b = intercept - const isMinimum = !buy; - console.log('applyLine current line value', isMinimum?'min':'max', m*timestamp()+b, 1./(m*timestamp()+b)) + console.log('applyLine current line value', isMaximum?'min':'max', m*timestamp()+b, 1./(m*timestamp()+b)) m = encodeIEE754(m) b = encodeIEE754(b) - if (isMinimum) { - tranche.minLine.intercept = b; - tranche.minLine.slope = m; - } else { + if (isMaximum) { tranche.maxLine.intercept = b; tranche.maxLine.slope = m; + } else { + tranche.minLine.intercept = b; + tranche.minLine.slope = m; } tranche.marketOrder = false; } diff --git a/src/socket.js b/src/socket.js index a478d42..3dc4246 100644 --- a/src/socket.js +++ b/src/socket.js @@ -98,8 +98,8 @@ socket.on( 'of', (chainId, vault, orderIndex, filled)=>{ let orderIn = 0n let orderOut = 0n - const ts = status.trancheStatus[i] for (const i in filled) { + const ts = status.trancheStatus[i] let filledIn = 0n let filledOut = 0n const [activationTime, fills] = filled[i]; diff --git a/src/styles/style.scss b/src/styles/style.scss index d7fb34e..35ee1cf 100644 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -1,5 +1,9 @@ @use "vars" as v; +html { + overflow-y: hidden; +} + #app { a {