diff --git a/python/valuecell/agents/common/trading/_internal/coordinator.py b/python/valuecell/agents/common/trading/_internal/coordinator.py index 0932c6493..606e1a809 100644 --- a/python/valuecell/agents/common/trading/_internal/coordinator.py +++ b/python/valuecell/agents/common/trading/_internal/coordinator.py @@ -91,7 +91,7 @@ def __init__( self._request = request self.strategy_id = strategy_id self.portfolio_service = portfolio_service - self._features_pipeline = features_pipeline + self.features_pipeline = features_pipeline self._composer = composer self._execution_gateway = execution_gateway self._history_recorder = history_recorder @@ -144,7 +144,7 @@ async def run_once(self) -> DecisionCycleResult: if self._request.exchange_config.market_type == MarketType.SPOT: portfolio.buying_power = max(0.0, float(portfolio.account_balance)) - pipeline_result = await self._features_pipeline.build() + pipeline_result = await self.features_pipeline.build() features = list(pipeline_result.features or []) market_features = extract_market_snapshot_features(features) digest = self._digest_builder.build(self._history_recorder.get_records()) @@ -612,7 +612,7 @@ async def close_all_positions(self) -> List[TradeHistoryEntry]: market_features: List[FeatureVector] = [] if self._request.exchange_config.trading_mode == TradingMode.VIRTUAL: try: - pipeline_result = await self._features_pipeline.build() + pipeline_result = await self.features_pipeline.build() market_features = extract_market_snapshot_features( pipeline_result.features or [] ) diff --git a/python/valuecell/agents/common/trading/base_agent.py b/python/valuecell/agents/common/trading/base_agent.py index c1776e3b8..c59de2d1d 100644 --- a/python/valuecell/agents/common/trading/base_agent.py +++ b/python/valuecell/agents/common/trading/base_agent.py @@ -244,6 +244,15 @@ async def _run_background_decision( # Call user hook for custom initialization try: + # Initialize long-lived features resources if available + try: + await runtime.coordinator.features_pipeline.open() + except Exception: + logger.exception( + "Error initializing features pipeline resources for strategy {}", + strategy_id, + ) + await self._on_start(runtime, request) except Exception: logger.exception("Error in _on_start hook for strategy {}", strategy_id) @@ -336,6 +345,13 @@ async def _run_background_decision( ) # Finalize: close resources and mark stopped/paused/error + try: + await runtime.coordinator.features_pipeline.close() + except Exception: + logger.exception( + "Error closing features pipeline resources for strategy {}", + strategy_id, + ) await controller.finalize( runtime, reason=stop_reason, reason_detail=stop_reason_detail ) diff --git a/python/valuecell/agents/common/trading/constants.py b/python/valuecell/agents/common/trading/constants.py index 74f262fa9..6dda52bb6 100644 --- a/python/valuecell/agents/common/trading/constants.py +++ b/python/valuecell/agents/common/trading/constants.py @@ -15,3 +15,4 @@ FEATURE_GROUP_BY_KEY = "group_by_key" FEATURE_GROUP_BY_INTERVAL_PREFIX = "interval_" FEATURE_GROUP_BY_MARKET_SNAPSHOT = "market_snapshot" +FEATURE_GROUP_BY_IMAGE_ANALYSIS = "image_analysis" diff --git a/python/valuecell/agents/common/trading/data/configs/aggr/charts.json b/python/valuecell/agents/common/trading/data/configs/aggr/charts.json new file mode 100644 index 000000000..ba06c4cee --- /dev/null +++ b/python/valuecell/agents/common/trading/data/configs/aggr/charts.json @@ -0,0 +1,3150 @@ +{ + "version": 7, + "createdAt": 1764666200000, + "updatedAt": 1764666205739, + "name": "Charts", + "id": "charts", + "states": { + "panes": { + "_id": "panes", + "locked": false, + "layout": [ + { + "i": "chart", + "type": "chart", + "x": 0, + "y": 0, + "w": 24, + "h": 23, + "moved": false + }, + { + "i": "delta", + "type": "chart", + "x": 0, + "y": 23, + "w": 24, + "h": 18, + "moved": false + }, + { + "i": "chart copy 1", + "type": "chart", + "x": 0, + "y": 41, + "w": 24, + "h": 17, + "moved": false + } + ], + "panes": { + "chart": { + "id": "chart", + "name": "AGGR", + "type": "chart", + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdc", + "BINANCE_FUTURES:btcusdt", + "BINANCE:btcfdusd", + "BINANCE:btctusd", + "BINANCE:btcusdc", + "BINANCE:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITGET:BTCPERP_CMCBL", + "BITGET:BTCUSD_DMCBL", + "BITGET:BTCUSDC", + "BITGET:BTCUSDT", + "BITGET:BTCUSDT_UMCBL", + "BITMEX:XBT_USDT", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BITSTAMP:btcusd", + "BITSTAMP:btcusdc", + "BITSTAMP:btcusdt", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDC-SPOT", + "BYBIT:BTCUSDT", + "BYBIT:BTCUSDT-SPOT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDC", + "COINBASE:BTC-USDT", + "DERIBIT:BTC_USDC-PERPETUAL", + "DERIBIT:BTC-PERPETUAL", + "DYDX:BTC-USD", + "HUOBI:BTC-USD", + "HUOBI:BTC-USDT", + "HUOBI:btcusdc", + "HUOBI:btcusdd", + "HUOBI:btcusdt", + "KRAKEN:PF_XBTUSD", + "KRAKEN:PI_XBTUSD", + "KRAKEN:XBT/USD", + "KRAKEN:XBT/USDC", + "KRAKEN:XBT/USDT", + "KUCOIN:BTC-USDC", + "KUCOIN:BTC-USDT", + "KUCOIN:XBTUSDCM", + "KUCOIN:XBTUSDM", + "KUCOIN:XBTUSDTM", + "MEXC:BTC_USD", + "MEXC:BTC_USDT", + "MEXC:BTCUSDC", + "MEXC:BTCUSDT", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDC", + "OKEX:BTC-USDC-SWAP", + "OKEX:BTC-USDT", + "OKEX:BTC-USDT-SWAP", + "PHEMEX:BTCUSD", + "PHEMEX:BTCUSDT", + "POLONIEX:BTC_TUSD", + "POLONIEX:BTC_USDC", + "POLONIEX:BTC_USDD", + "POLONIEX:BTC_USDT" + ], + "zoom": 1 + }, + "delta": { + "id": "delta", + "name": "PAIRS", + "type": "chart", + "zoom": 1, + "settings": { + "indicatorsErrors": {}, + "indicators": { + "_rmg9m2zvgvdyfzgl": { + "enabled": true, + "name": "Liquidations", + "description": "Liquidations by side", + "script": "plothistogram(lbuy, color=options.upColor)\nplothistogram(-lsell, color=options.downColor)", + "options": { + "priceScaleId": "_rmg9m2zvgvdyfzgl", + "priceFormat": { + "type": "volume" + }, + "upColor": "rgb(100,181,246)", + "downColor": "rgb(240,98,146)", + "scaleMargins": { + "top": 0.87, + "bottom": 0 + }, + "baseLineVisible": false, + "lastValueVisible": true, + "priceLineVisible": false + }, + "id": "_rmg9m2zvgvdyfzgl", + "createdAt": 1648450392186, + "updatedAt": 1713565132907, + "uses": 2, + "series": [ + "liquidations", + "5hbxmosl" + ], + "displayName": "Liquidations", + "optionsDefinitions": {}, + "unsavedChanges": false, + "preview": {}, + "libraryId": "liquidations" + }, + "_7cd5k7cv9ka4qp0s": { + "id": "_7cd5k7cv9ka4qp0s", + "libraryId": "delta-binance-spot", + "name": "BINANCE SPOT USDT", + "script": "_vbuy = (BINANCE:btcusdt.vbuy)\n_vsell = (BINANCE:btcusdt.vsell)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\"BN.S”)", + "createdAt": 1649330197719, + "updatedAt": 1713583022144, + "options": { + "priceScaleId": "_7cd5k7cv9ka4qp0s", + "upColor": "rgb(100,181,246)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 14, + "upColorHighVol": "rgb(100,181,246)", + "upColorLowVol": "rgb(10,96,162)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "scaleMargins": { + "top": 0.14, + "bottom": 0.75 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-binance-spot" + ], + "displayName": "BINANCE SPOT USDT", + "unsavedChanges": false + }, + "_kvcozjh87f50e1gs": { + "id": "_kvcozjh87f50e1gs", + "libraryId": "_7cd5k7cv9ka4qp0s", + "name": "BINANCE SPOT FDUSD", + "script": "_vbuy = (BINANCE:btcfdusd.vbuy)\n_vsell = (BINANCE:btcfdusd.vsell)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\"BN.FD”)", + "createdAt": 1713560579658, + "updatedAt": 1713570376824, + "options": { + "priceScaleId": "_kvcozjh87f50e1gs", + "upColor": "rgb(100,181,246)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 14, + "upColorHighVol": "rgb(100,181,246)", + "upColorLowVol": "rgb(10,96,162)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "scaleMargins": { + "top": 0.02, + "bottom": 0.86 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "_7cd5k7cv9ka4qp0s" + ], + "displayName": "BINANCE SPOT FDUSD", + "unsavedChanges": true + }, + "_mdzsxr1kv4zlvk1x": { + "id": "_mdzsxr1kv4zlvk1x", + "libraryId": "_kvcozjh87f50e1gs", + "name": "CB", + "script": "_vbuy = (COINBASE:BTC-USD.vbuy+COINBASE:BTC-USDC.vbuy+COINBASE:BTC-USDT.vbuy)\n_vsell = (COINBASE:BTC-USD.vsell+COINBASE:BTC-USDC.vsell+COINBASE:BTC-USDT.vsell)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\"CB\")", + "createdAt": 1713560951314, + "updatedAt": 1713568342377, + "options": { + "priceScaleId": "_mdzsxr1kv4zlvk1x", + "upColor": "rgb(100,181,246)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 14, + "upColorHighVol": "rgb(100,181,246)", + "upColorLowVol": "rgb(10,96,162)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "scaleMargins": { + "top": 0.25, + "bottom": 0.63 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "displayName": "CB", + "series": [ + "_kvcozjh87f50e1gs" + ], + "unsavedChanges": true + }, + "_krppkscmyqxrjfyd": { + "id": "_krppkscmyqxrjfyd", + "libraryId": "_mdzsxr1kv4zlvk1x", + "name": "BF", + "script": "_vbuy = (BITFINEX:BTCUSD.vbuy+BITFINEX:BTCUST.vbuy)\n_vsell = (BITFINEX:BTCUSD.vsell+BITFINEX:BTCUST.vsell)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\"BF\")", + "createdAt": 1713561083390, + "updatedAt": 1713561205371, + "options": { + "priceScaleId": "_krppkscmyqxrjfyd", + "upColor": "rgb(100,181,246)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 14, + "upColorHighVol": "rgb(100,181,246)", + "upColorLowVol": "rgb(10,96,162)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "scaleMargins": { + "top": 0.37, + "bottom": 0.51 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "_mdzsxr1kv4zlvk1x" + ], + "displayName": "BF", + "unsavedChanges": true + }, + "_9yf0uhfo6ak1ynmb": { + "id": "_9yf0uhfo6ak1ynmb", + "libraryId": "bns", + "name": "CVD BN.S", + "script": "plotline(cum(BINANCE:btcusdt.vbuy - BINANCE:btcusdt.vsell), title=BN.S)", + "createdAt": 1713582936533, + "updatedAt": 1713583364683, + "options": { + "priceScaleId": "_9yf0uhfo6ak1ynmb", + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "color": "rgb(100,181,246)", + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "visible": true + }, + "optionsDefinitions": {}, + "series": [ + "bns" + ], + "displayName": "CVD BN.S", + "unsavedChanges": false + }, + "_tvmy75hrwj7387ye": { + "id": "_tvmy75hrwj7387ye", + "libraryId": "bnfd", + "name": "CVD BN.FD", + "script": "plotline(cum(BINANCE:btcfdusd.vbuy - BINANCE:btcfdusd.vsell), title=BN.FD)", + "createdAt": 1713582982235, + "updatedAt": 1713644723785, + "options": { + "priceScaleId": "_tvmy75hrwj7387ye", + "color": "rgb(10,96,162)", + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "visible": true + }, + "optionsDefinitions": {}, + "series": [ + "bnfd" + ], + "displayName": "CVD BN.FD", + "unsavedChanges": false + }, + "_shfiapc2wfqzrlq1": { + "id": "_shfiapc2wfqzrlq1", + "libraryId": "cvd-cb", + "name": "CVD CB", + "script": "plotline(cum(COINBASE:BTC-USD.vbuy - COINBASE:BTC-USD.vsell), title=CB)", + "createdAt": 1713583125412, + "updatedAt": 1713583357548, + "options": { + "priceScaleId": "_shfiapc2wfqzrlq1", + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "color": "rgb(129,199,132)", + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "visible": true + }, + "optionsDefinitions": {}, + "series": [ + "cvd-cb" + ], + "displayName": "CVD CB", + "unsavedChanges": false + }, + "_igcwun84g630m5nk": { + "id": "_igcwun84g630m5nk", + "libraryId": "cvd-bnp", + "name": "CVD BN.P", + "script": "plotline(cum(BINANCE_FUTURES:btcusdt.vbuy - BINANCE_FUTURES:btcusdt.vsell), title=BN.P)", + "createdAt": 1713583209180, + "updatedAt": 1713583450753, + "options": { + "priceScaleId": "_igcwun84g630m5nk", + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "color": "rgb(232,219,125)", + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "visible": true + }, + "optionsDefinitions": {}, + "series": [ + "cvd-bnp" + ], + "displayName": "CVD BN.P", + "unsavedChanges": false + }, + "_f8y8f2f4126fpzpd": { + "id": "_f8y8f2f4126fpzpd", + "libraryId": "cvd-bbp", + "name": "CVD BB.P", + "script": "plotline(cum(BYBIT:BTCUSDT.vbuy - BYBIT:BTCUSDT.vsell), title=BB.P)", + "createdAt": 1713583272290, + "updatedAt": 1713583383128, + "options": { + "priceScaleId": "_f8y8f2f4126fpzpd", + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "color": "rgb(240,98,146)", + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "visible": true + }, + "optionsDefinitions": {}, + "series": [ + "cvd-bbp" + ], + "displayName": "CVD BB.P", + "unsavedChanges": false + }, + "_e1esdjt32movky8x": { + "id": "_e1esdjt32movky8x", + "libraryId": "price", + "name": "Price", + "script": "plotcandlestick(options.useHeikinAshi ? avg_heikinashi(bar) : options.useGaps ? avg_ohlc_with_gaps(bar) : avg_ohlc(bar))", + "createdAt": 1696203148430, + "updatedAt": 1713643790838, + "options": { + "priceScaleId": "_e1esdjt32movky8x", + "priceFormat": { + "auto": true, + "precision": 1, + "minMove": 0.1 + }, + "priceLineVisible": false, + "lastValueVisible": true, + "borderVisible": true, + "upColor": "rgba(255,255,255,0.5)", + "downColor": "rgba(255,255,255,0)", + "borderUpColor": "rgba(255,255,255,0.5)", + "borderDownColor": "rgba(255,255,255,0.5)", + "wickUpColor": "rgba(255,255,255,0.5)", + "wickDownColor": "rgba(255,255,255,0.5)", + "useGaps": false, + "useHeikinAshi": false, + "scaleMargins": { + "top": 0.49, + "bottom": 0.35 + }, + "priceLineColor": "rgba(255,255,255,0.5)", + "visible": true, + "baseLineVisible": false + }, + "optionsDefinitions": {}, + "series": [ + "price" + ], + "displayName": "Price" + } + }, + "indicatorOrder": [ + "_rmg9m2zvgvdyfzgl", + "_7cd5k7cv9ka4qp0s", + "_kvcozjh87f50e1gs", + "_mdzsxr1kv4zlvk1x", + "_krppkscmyqxrjfyd", + "_9yf0uhfo6ak1ynmb", + "_tvmy75hrwj7387ye", + "_shfiapc2wfqzrlq1", + "_igcwun84g630m5nk", + "_f8y8f2f4126fpzpd", + "_e1esdjt32movky8x" + ], + "priceScales": { + "right": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.35 + }, + "indicators": [ + "Liquidation Heatmap" + ] + }, + "volume_liquidations": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.2 + }, + "indicators": [ + "Liquidations" + ] + }, + "volume": { + "scaleMargins": { + "top": 0.84, + "bottom": 0 + } + }, + "delta-binance-spot": { + "scaleMargins": { + "top": 0, + "bottom": 0.88 + }, + "indicators": [ + "BINANCE SPOT FDUSD" + ], + "mode": 0 + }, + "_7cd5k7cv9ka4qp0s": { + "scaleMargins": { + "top": 0.14, + "bottom": 0.75 + }, + "indicators": [ + "BINANCE SPOT USDT" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "_kvcozjh87f50e1gs": { + "scaleMargins": { + "top": 0.02, + "bottom": 0.86 + }, + "indicators": [ + "BINANCE SPOT FDUSD" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "_mdzsxr1kv4zlvk1x": { + "scaleMargins": { + "top": 0.25, + "bottom": 0.63 + }, + "indicators": [ + "CB" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "_krppkscmyqxrjfyd": { + "scaleMargins": { + "top": 0.37, + "bottom": 0.51 + }, + "indicators": [ + "BF" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "cvdperp": { + "scaleMargins": { + "top": 0.55, + "bottom": 0.17 + }, + "indicators": [ + "CVD (BTC PERP)" + ] + }, + "_rufs083y0fbvc7ay": { + "scaleMargins": { + "top": 0.54, + "bottom": 0.17 + }, + "indicators": [ + "CVD CB" + ] + }, + "_7dsa9mx4zmn1o9wi": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.22 + }, + "indicators": [ + "CVD CB" + ] + }, + "_5xwq57x2pjjgqg3a": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.22 + }, + "indicators": [ + "CVD BN.P" + ] + }, + "left": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.35 + } + }, + "_oq7moq2202szuuqs": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.22 + }, + "indicators": [ + "CVD BN.S" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": false + } + }, + "_9t6pmojrlx5wc9nt": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.22 + } + }, + "_wtj5a0lw9ypkovyp": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.22 + } + }, + "_blmgdmljx1z1o0tz": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.22 + } + }, + "_b9b5e0ifnnfuyomd": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.22 + } + }, + "_9yf0uhfo6ak1ynmb": { + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "indicators": [ + "CVD BN.S" + ] + }, + "_shfiapc2wfqzrlq1": { + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "indicators": [ + "CVD CB" + ] + }, + "_tvmy75hrwj7387ye": { + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "indicators": [ + "CVD BN.FD" + ] + }, + "_igcwun84g630m5nk": { + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "indicators": [ + "CVD BN.P" + ] + }, + "_f8y8f2f4126fpzpd": { + "scaleMargins": { + "top": 0.65, + "bottom": 0.14 + }, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "indicators": [ + "CVD BB.P" + ] + }, + "_aoqxqm6ynw4ta9gv": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.2 + }, + "indicators": [ + "Price" + ] + }, + "_h6wn38lfbfomwaeb": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.2 + } + }, + "_bdyzmtbhap9tidl2": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.2 + }, + "indicators": [ + "cvd multi" + ] + }, + "_e1esdjt32movky8x": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.35 + }, + "indicators": [ + "Price" + ] + }, + "_rmg9m2zvgvdyfzgl": { + "scaleMargins": { + "top": 0.87, + "bottom": 0 + }, + "indicators": [ + "Liquidations" + ] + } + }, + "layouting": false, + "showIndicators": true, + "timeframe": "60", + "refreshRate": 1000, + "showAlerts": true, + "showAlertsLabel": true, + "showLegend": false, + "fillGapsWithEmpty": true, + "showHorizontalGridlines": false, + "horizontalGridlinesColor": "rgba(255,255,255,.1)", + "showVerticalGridlines": false, + "verticalGridlinesColor": "rgba(255,255,255,.1)", + "showWatermark": false, + "watermarkColor": "rgba(255,255,255,.1)", + "showBorder": true, + "borderColor": null, + "textColor": null, + "showLeftScale": false, + "showRightScale": true, + "showTimeScale": true, + "hiddenMarkets": { + "COINBASE:BTC-USD": false + }, + "barSpacing": 0.2599956189007877 + }, + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdc", + "BINANCE_FUTURES:btcusdt", + "BINANCE:btcfdusd", + "BINANCE:btctusd", + "BINANCE:btcusdc", + "BINANCE:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITGET:BTCPERP_CMCBL", + "BITGET:BTCUSD_DMCBL", + "BITGET:BTCUSDC", + "BITGET:BTCUSDT", + "BITGET:BTCUSDT_UMCBL", + "BITMEX:XBT_USDT", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BITSTAMP:btcusd", + "BITSTAMP:btcusdc", + "BITSTAMP:btcusdt", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDC-SPOT", + "BYBIT:BTCUSDT", + "BYBIT:BTCUSDT-SPOT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDC", + "COINBASE:BTC-USDT", + "DERIBIT:BTC_USDC-PERPETUAL", + "DERIBIT:BTC-PERPETUAL", + "DYDX:BTC-USD", + "HUOBI:BTC-USD", + "HUOBI:BTC-USDT", + "HUOBI:btcusdc", + "HUOBI:btcusdd", + "HUOBI:btcusdt", + "KRAKEN:PF_XBTUSD", + "KRAKEN:PI_XBTUSD", + "KRAKEN:XBT/USD", + "KRAKEN:XBT/USDC", + "KRAKEN:XBT/USDT", + "KUCOIN:BTC-USDC", + "KUCOIN:BTC-USDT", + "KUCOIN:XBTUSDCM", + "KUCOIN:XBTUSDM", + "KUCOIN:XBTUSDTM", + "MEXC:BTC_USD", + "MEXC:BTC_USDT", + "MEXC:BTCUSDC", + "MEXC:BTCUSDT", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDC", + "OKEX:BTC-USDC-SWAP", + "OKEX:BTC-USDT", + "OKEX:BTC-USDT-SWAP", + "PHEMEX:BTCUSD", + "PHEMEX:BTCUSDT", + "POLONIEX:BTC_TUSD", + "POLONIEX:BTC_USDC", + "POLONIEX:BTC_USDD", + "POLONIEX:BTC_USDT" + ] + }, + "chart copy 1": { + "id": "chart copy 1", + "name": "", + "type": "chart", + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdc", + "BINANCE_FUTURES:btcusdt", + "BINANCE:btcfdusd", + "BINANCE:btctusd", + "BINANCE:btcusdc", + "BINANCE:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITGET:BTCPERP_CMCBL", + "BITGET:BTCUSD_DMCBL", + "BITGET:BTCUSDC", + "BITGET:BTCUSDT", + "BITGET:BTCUSDT_UMCBL", + "BITMEX:XBT_USDT", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BITSTAMP:btcusd", + "BITSTAMP:btcusdc", + "BITSTAMP:btcusdt", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDC-SPOT", + "BYBIT:BTCUSDT", + "BYBIT:BTCUSDT-SPOT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDC", + "COINBASE:BTC-USDT", + "DERIBIT:BTC_USDC-PERPETUAL", + "DERIBIT:BTC-PERPETUAL", + "DYDX:BTC-USD", + "HUOBI:BTC-USD", + "HUOBI:BTC-USDT", + "HUOBI:btcusdc", + "HUOBI:btcusdd", + "HUOBI:btcusdt", + "KRAKEN:PF_XBTUSD", + "KRAKEN:PI_XBTUSD", + "KRAKEN:XBT/USD", + "KRAKEN:XBT/USDC", + "KRAKEN:XBT/USDT", + "KUCOIN:BTC-USDC", + "KUCOIN:BTC-USDT", + "KUCOIN:XBTUSDCM", + "KUCOIN:XBTUSDM", + "KUCOIN:XBTUSDTM", + "MEXC:BTC_USD", + "MEXC:BTC_USDT", + "MEXC:BTCUSDC", + "MEXC:BTCUSDT", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDC", + "OKEX:BTC-USDC-SWAP", + "OKEX:BTC-USDT", + "OKEX:BTC-USDT-SWAP", + "PHEMEX:BTCUSD", + "PHEMEX:BTCUSDT", + "POLONIEX:BTC_TUSD", + "POLONIEX:BTC_USDC", + "POLONIEX:BTC_USDD", + "POLONIEX:BTC_USDT" + ] + } + }, + "syncedWithParentFrame": [] + }, + "settings": { + "_id": "settings", + "preferQuoteCurrencySize": true, + "aggregationLength": 10, + "calculateSlippage": null, + "wsProxyUrl": null, + "disableAnimations": true, + "autoHideHeaders": true, + "autoHideNames": false, + "theme": "dark", + "backgroundColor": "rgb(25,25,25)", + "textColor": "", + "buyColor": "rgb(100, 157, 102)", + "sellColor": "rgb(239, 67, 82)", + "timezoneOffset": 7200000, + "useAudio": false, + "audioVolume": 0.1, + "audioFilters": { + "PingPongDelay": true, + "Compressor": false, + "Delay": false, + "HighPassFilter": true, + "LowPassFilter": false + }, + "sections": [ + "timeframe-minutes", + "search-type", + "timeframe-hours", + "indicator-right-scale", + "settings-trades", + "trades-display", + "trades-liquidations", + "search-extras", + "search-exchanges", + "search-quotes", + "settings-other", + "indicator-right-script", + "indicator-left-colors", + "indicator-left-script", + "indicator-left-other", + "indicator-right-default", + "indicator-right-format", + "indicator-right-colors", + "settings-workspaces" + ], + "searchTypes": { + "recentSearches": true, + "historical": false, + "spots": true, + "perpetuals": false, + "futures": false, + "normalize": false, + "mergeUsdt": false + }, + "searchQuotes": { + "USDT": false, + "USD": false, + "FDUSD": false, + "UST": false + }, + "previousSearchSelections": [ + { + "label": "COINBASE:BTCUSD", + "markets": [ + "COINBASE:BTC-USD", + "COINBASE:BTC-USDT" + ], + "count": 2 + }, + { + "label": "COINBASE:BTC-USD", + "markets": [ + "COINBASE:BTC-USD" + ], + "count": 0 + }, + { + "label": "BINANCE:BTCUSD", + "markets": [ + "BINANCE:btcfdusd", + "BINANCE:btctusd", + "BINANCE:btcusdt" + ], + "count": 3 + }, + { + "label": "BTCUSD", + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdc", + "BINANCE_FUTURES:btcusdt", + "BINANCE:btcfdusd", + "BINANCE:btctusd", + "BINANCE:btcusdc", + "BINANCE:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITGET:BTCPERP_CMCBL", + "BITGET:BTCUSD_DMCBL", + "BITGET:BTCUSDC", + "BITGET:BTCUSDT", + "BITGET:BTCUSDT_UMCBL", + "BITMEX:XBT_USDT", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BITSTAMP:btcusd", + "BITSTAMP:btcusdc", + "BITSTAMP:btcusdt", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDC-SPOT", + "BYBIT:BTCUSDT", + "BYBIT:BTCUSDT-SPOT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDC", + "COINBASE:BTC-USDT", + "DERIBIT:BTC_USDC-PERPETUAL", + "DERIBIT:BTC-PERPETUAL", + "DYDX:BTC-USD", + "HUOBI:BTC-USD", + "HUOBI:BTC-USDT", + "HUOBI:btcusdc", + "HUOBI:btcusdd", + "HUOBI:btcusdt", + "KRAKEN:PF_XBTUSD", + "KRAKEN:PI_XBTUSD", + "KRAKEN:XBT/USD", + "KRAKEN:XBT/USDC", + "KRAKEN:XBT/USDT", + "KUCOIN:BTC-USDC", + "KUCOIN:BTC-USDT", + "KUCOIN:XBTUSDCM", + "KUCOIN:XBTUSDM", + "KUCOIN:XBTUSDTM", + "MEXC:BTC_USD", + "MEXC:BTC_USDT", + "MEXC:BTCUSDC", + "MEXC:BTCUSDT", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDC", + "OKEX:BTC-USDC-SWAP", + "OKEX:BTC-USDT", + "OKEX:BTC-USDT-SWAP", + "PHEMEX:BTCUSD", + "PHEMEX:BTCUSDT", + "POLONIEX:BTC_TUSD", + "POLONIEX:BTC_USDC", + "POLONIEX:BTC_USDD", + "POLONIEX:BTC_USDT" + ], + "count": 61 + }, + { + "label": "BTCUSD", + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdc", + "BINANCE_FUTURES:btcusdt", + "BINANCE:btcusdc", + "BINANCE:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITGET:BTCPERP_CMCBL", + "BITGET:BTCUSD_DMCBL", + "BITGET:BTCUSDC", + "BITGET:BTCUSDT", + "BITGET:BTCUSDT_UMCBL", + "BITMEX:XBT_USDT", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BITSTAMP:btcusd", + "BITSTAMP:btcusdc", + "BITSTAMP:btcusdt", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDC-SPOT", + "BYBIT:BTCUSDT", + "BYBIT:BTCUSDT-SPOT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDC", + "COINBASE:BTC-USDT", + "DERIBIT:BTC_USDC-PERPETUAL", + "DERIBIT:BTC-PERPETUAL", + "DYDX:BTC-USD", + "HUOBI:BTC-USD", + "HUOBI:BTC-USDT", + "HUOBI:btcusdc", + "HUOBI:btcusdd", + "HUOBI:btcusdt", + "KRAKEN:PF_XBTUSD", + "KRAKEN:PI_XBTUSD", + "KRAKEN:XBT/USD", + "KRAKEN:XBT/USDC", + "KRAKEN:XBT/USDT", + "KUCOIN:BTC-USDC", + "KUCOIN:BTC-USDT", + "KUCOIN:XBTUSDCM", + "KUCOIN:XBTUSDM", + "KUCOIN:XBTUSDTM", + "MEXC:BTC_USD", + "MEXC:BTC_USDT", + "MEXC:BTCUSDC", + "MEXC:BTCUSDT", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDC", + "OKEX:BTC-USDC-SWAP", + "OKEX:BTC-USDT", + "OKEX:BTC-USDT-SWAP", + "PHEMEX:BTCUSD", + "PHEMEX:BTCUSDT", + "POLONIEX:BTC_USDC", + "POLONIEX:BTC_USDD", + "POLONIEX:BTC_USDT" + ], + "count": 58 + }, + { + "label": "AVAXUSD", + "markets": [ + "BINANCE_FUTURES:avaxusd_perp", + "BINANCE_FUTURES:avaxusdc", + "BINANCE_FUTURES:avaxusdt", + "BINANCE:avaxfdusd", + "BINANCE:avaxtusd", + "BINANCE:avaxusdc", + "BINANCE:avaxusdt", + "BITFINEX:AVAX:USD", + "BITFINEX:AVAX:UST", + "BITFINEX:AVAXF0:USTF0", + "BITGET:AVAXUSDC", + "BITGET:AVAXUSDT", + "BITGET:AVAXUSDT_UMCBL", + "BITMEX:AVAXUSD", + "BITMEX:AVAXUSDT", + "BITSTAMP:avaxusd", + "BYBIT:AVAXUSDC-SPOT", + "BYBIT:AVAXUSDT", + "BYBIT:AVAXUSDT-SPOT", + "COINBASE:AVAX-USD", + "COINBASE:AVAX-USDT", + "DERIBIT:AVAX_USDC-PERPETUAL", + "DYDX:AVAX-USD", + "HUOBI:AVAX-USDT", + "HUOBI:avaxusdc", + "HUOBI:avaxusdd", + "HUOBI:avaxusdt", + "KRAKEN:AVAX/USD", + "KRAKEN:AVAX/USDT", + "KRAKEN:PF_AVAXUSD", + "KUCOIN:AVAX-USDC", + "KUCOIN:AVAX-USDT", + "KUCOIN:AVAXUSDTM", + "MEXC:AVAX_USDT", + "MEXC:AVAXUSDC", + "MEXC:AVAXUSDT", + "OKEX:AVAX-USD-SWAP", + "OKEX:AVAX-USDC", + "OKEX:AVAX-USDT", + "OKEX:AVAX-USDT-SWAP", + "PHEMEX:AVAXUSD", + "PHEMEX:AVAXUSDT", + "POLONIEX:AVAX_USDC", + "POLONIEX:AVAX_USDT" + ], + "count": 44 + }, + { + "label": "BTCUSD", + "markets": [ + "BINANCE:btcfdusd", + "BINANCE:btcusdc", + "BINANCE:btcusdt", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITGET:BTCUSDC", + "BITGET:BTCUSDT", + "BITSTAMP:btcusd", + "BITSTAMP:btcusdt", + "BYBIT:BTCUSDC-SPOT", + "BYBIT:BTCUSDT-SPOT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDT", + "HUOBI:btcusdc", + "HUOBI:btcusdt", + "KRAKEN:XBT/USD", + "KRAKEN:XBT/USDC", + "KRAKEN:XBT/USDT", + "KUCOIN:BTC-USDC", + "KUCOIN:BTC-USDT", + "MEXC:BTCUSDT", + "OKEX:BTC-USDC", + "OKEX:BTC-USDT", + "POLONIEX:BTC_USDC", + "POLONIEX:BTC_USDD", + "POLONIEX:BTC_USDT" + ], + "count": 26 + }, + { + "label": "BTCUSD", + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITGET:BTCPERP_CMCBL", + "BITGET:BTCUSD_DMCBL", + "BITGET:BTCUSDT_UMCBL", + "BITMEX:XBT_USDT", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDT", + "DERIBIT:BTC_USDC-PERPETUAL", + "DERIBIT:BTC-PERPETUAL", + "HUOBI:BTC-USD", + "HUOBI:BTC-USDT", + "KRAKEN:PI_XBTUSD", + "KUCOIN:XBTUSDM", + "KUCOIN:XBTUSDTM", + "MEXC:BTC_USD", + "MEXC:BTC_USDT", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDC-SWAP", + "OKEX:BTC-USDT-SWAP" + ], + "count": 23 + }, + { + "label": "BTCUSD", + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdt", + "BINANCE:btcfdusd", + "BINANCE:btcusdc", + "BINANCE:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITGET:BTCPERP_CMCBL", + "BITGET:BTCUSD_DMCBL", + "BITGET:BTCUSDC", + "BITGET:BTCUSDT", + "BITGET:BTCUSDT_UMCBL", + "BITMEX:XBT_USDT", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BITSTAMP:btcusd", + "BITSTAMP:btcusdt", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDC-SPOT", + "BYBIT:BTCUSDT", + "BYBIT:BTCUSDT-SPOT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDT", + "DERIBIT:BTC_USDC-PERPETUAL", + "DERIBIT:BTC-PERPETUAL", + "HUOBI:BTC-USD", + "HUOBI:BTC-USDT", + "HUOBI:btcusdc", + "HUOBI:btcusdt", + "KRAKEN:PI_XBTUSD", + "KRAKEN:XBT/USD", + "KRAKEN:XBT/USDC", + "KRAKEN:XBT/USDT", + "KUCOIN:BTC-USDC", + "KUCOIN:BTC-USDT", + "KUCOIN:XBTUSDM", + "KUCOIN:XBTUSDTM", + "MEXC:BTC_USD", + "MEXC:BTC_USDT", + "MEXC:BTCUSDT", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDC", + "OKEX:BTC-USDC-SWAP", + "OKEX:BTC-USDT", + "OKEX:BTC-USDT-SWAP", + "POLONIEX:BTC_USDC", + "POLONIEX:BTC_USDD", + "POLONIEX:BTC_USDT" + ], + "count": 49 + }, + { + "label": "BTCUSD", + "markets": [ + "BINANCE_FUTURES:btcusd_perp", + "BINANCE_FUTURES:btcusdt", + "BINANCE:btcusdt", + "BITFINEX:BTCF0:USTF0", + "BITFINEX:BTCUSD", + "BITFINEX:BTCUST", + "BITMEX:XBTUSD", + "BITMEX:XBTUSDT", + "BITSTAMP:btcusd", + "BYBIT:BTCUSD", + "BYBIT:BTCUSDT", + "COINBASE:BTC-USD", + "COINBASE:BTC-USDT", + "DERIBIT:BTC-PERPETUAL", + "KRAKEN:PI_XBTUSD", + "OKEX:BTC-USD-SWAP", + "OKEX:BTC-USDT-SWAP" + ], + "count": 17 + } + ], + "searchExchanges": { + "AGGR": false, + "BINANCE": false, + "BINANCE_FUTURES": false, + "BINANCE_US": false, + "BITFINEX": false, + "BITGET": false, + "BITMART": false, + "BITMEX": false, + "BITSTAMP": false, + "BYBIT": false, + "COINBASE": true, + "CRYPTOCOM": false, + "DERIBIT": false, + "DYDX": false, + "GATEIO": false, + "HITBTC": false, + "HUOBI": false, + "KRAKEN": false, + "KUCOIN": false, + "MEXC": false, + "OKEX": false, + "PHEMEX": false, + "POLONIEX": false, + "UNISWAP": false + }, + "timeframes": [ + { + "label": "1s", + "value": "1" + }, + { + "label": "3s", + "value": "3" + }, + { + "label": "5s", + "value": "5" + }, + { + "label": "10s", + "value": "10" + }, + { + "label": "15s", + "value": "15" + }, + { + "label": "30s", + "value": "30" + }, + { + "label": "1m", + "value": "60" + }, + { + "label": "3m", + "value": "180" + }, + { + "label": "5m", + "value": "300" + }, + { + "label": "15m", + "value": "900" + }, + { + "label": "21m", + "value": "1260" + }, + { + "label": "30m", + "value": "1800" + }, + { + "label": "1h", + "value": "3600" + }, + { + "label": "2h", + "value": "7200" + }, + { + "label": "4h", + "value": "14400" + }, + { + "label": "6h", + "value": "21600" + }, + { + "label": "8h", + "value": "28800" + }, + { + "label": "12h", + "value": "43200" + }, + { + "label": "1d", + "value": "86400" + }, + { + "label": "21 ticks", + "value": "21t" + }, + { + "label": "50 ticks", + "value": "50t" + }, + { + "label": "89 ticks", + "value": "89t" + }, + { + "label": "100 ticks", + "value": "100t" + }, + { + "label": "200 ticks", + "value": "200t" + }, + { + "label": "610 ticks", + "value": "610t" + }, + { + "label": "1000 ticks", + "value": "1000t" + }, + { + "label": "1597 ticks", + "value": "1597t" + } + ], + "favoriteTimeframes": { + "60": "1m", + "300": "5m", + "900": "15m", + "3600": "1h", + "7200": "2h", + "14400": "4h" + }, + "normalizeWatermarks": false, + "alerts": false, + "alertsColor": "rgb(0,255,0)", + "alertsLineStyle": 1, + "alertsLineWidth": 1, + "alertsClick": false, + "alertSound": null, + "showThresholdsAsTable": false, + "indicatorDialogNavigation": "{\"optionsQuery\":\"\",\"editorOptions\":{},\"columnWidth\":240,\"tab\":\"options\"}" + }, + "exchanges": { + "AGGR": { + "disabled": false + }, + "BINANCE": { + "disabled": false + }, + "BINANCE_FUTURES": { + "disabled": false + }, + "BINANCE_US": { + "disabled": true + }, + "BITFINEX": { + "disabled": false + }, + "BITGET": { + "disabled": false + }, + "BITMART": { + "disabled": true + }, + "BITMEX": { + "disabled": false + }, + "BITSTAMP": { + "disabled": false + }, + "BYBIT": { + "disabled": false + }, + "COINBASE": { + "disabled": false + }, + "CRYPTOCOM": { + "disabled": true + }, + "DERIBIT": { + "disabled": false + }, + "DYDX": { + "disabled": false + }, + "GATEIO": { + "disabled": true + }, + "HITBTC": { + "disabled": true + }, + "HUOBI": { + "disabled": false + }, + "KRAKEN": { + "disabled": false + }, + "KUCOIN": { + "disabled": false + }, + "MEXC": { + "disabled": false + }, + "OKEX": { + "disabled": false + }, + "PHEMEX": { + "disabled": false + }, + "POLONIEX": { + "disabled": false + }, + "UNISWAP": { + "disabled": true + }, + "_id": "exchanges" + }, + "chart copy 1": { + "indicatorsErrors": {}, + "indicators": { + "_yc5ewfpz6pjfe1ru": { + "enabled": true, + "name": "Price", + "script": "plotcandlestick(avg_ohlc(bar))", + "options": { + "priceScaleId": "right", + "priceFormat": { + "auto": true, + "precision": 1, + "minMove": 0.1 + }, + "priceLineVisible": false, + "lastValueVisible": false, + "borderVisible": true, + "upColor": "rgba(255,255,255,0.7)", + "downColor": "rgb(0,0,0)", + "borderUpColor": "rgba(255,255,255,0.5)", + "borderDownColor": "rgba(255,255,255,0.5)", + "wickUpColor": "rgba(255,255,255,0.5)", + "wickDownColor": "rgba(255,255,255,0.5)", + "useGaps": false, + "useHeikinAshi": false, + "visible": false, + "scaleMargins": { + "top": 0.03, + "bottom": 0.73 + } + }, + "id": "_yc5ewfpz6pjfe1ru", + "createdAt": 1697564385199, + "updatedAt": 1716533408000, + "libraryId": "price", + "series": [ + "price" + ], + "optionsDefinitions": {}, + "unsavedChanges": true + }, + "cvd-exchange-BINANCE": { + "id": "cvd-exchange-BINANCE", + "libraryId": "cvd-exchange-BINANCE", + "name": "cvd-exchange-BINANCE", + "script": "_vbuy=(BINANCE:btcusdt.vbuy+BINANCE:btcbusd.vbuy+BINANCE:btcusdc.vbuy)\n_vsell=(BINANCE:btcusdt.vsell+BINANCE:btcbusd.vsell+BINANCE:btcusdc.vsell)\nline(cum(_vbuy-_vsell), title=BINANCE)", + "createdAt": 1716531496274, + "updatedAt": 1726323528484, + "options": { + "priceScaleId": "cvd-exchange-BINANCE", + "scaleMargins": { + "top": 0.04, + "bottom": 0.74 + }, + "color": "rgb(245,127,23)", + "visible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "lastValueVisible": true + }, + "optionsDefinitions": {}, + "series": [ + "cvd-exchange-BINANCE" + ], + "displayName": "CVD Exchange BINANCE", + "unsavedChanges": false + }, + "cvd-exchange-BINANCE_FUTURES": { + "id": "cvd-exchange-BINANCE_FUTURES", + "libraryId": "cvd-exchange-BINANCE_FUTURES", + "name": "cvd-exchange-BINANCE_F", + "script": "_vbuy=(BINANCE_FUTURES:btcusdt.vbuy+BINANCE_FUTURES:btcbusd.vbuy+BINANCE_FUTURES:btcusd_perp.vbuy)\n_vsell=(BINANCE_FUTURES:btcusdt.vsell+BINANCE_FUTURES:btcbusd.vsell+BINANCE_FUTURES:btcusd_perp.vsell)\nline(cum(_vbuy-_vsell), title=BINANCE_FUTURES)", + "createdAt": 1716531496274, + "updatedAt": 1726323548438, + "options": { + "priceScaleId": "cvd-exchange-BINANCE_FUTURES", + "scaleMargins": { + "top": 0.05, + "bottom": 0.74 + }, + "visible": true, + "lastValueVisible": false + }, + "optionsDefinitions": {}, + "series": [ + "cvd-exchange-BINANCE_FUTURES" + ], + "displayName": "cvd-exchange-BINANCE_F", + "unsavedChanges": false + }, + "cvd-exchange-BYBIT": { + "id": "cvd-exchange-BYBIT", + "libraryId": "cvd-exchange-BYBIT", + "name": "cvd-exchange-BYBIT", + "script": "_vbuy=(BYBIT:BTCUSDT-SPOT.vbuy+BYBIT:BTCUSDC-SPOT.vbuy+BYBIT:BTCUSD.vbuy+BYBIT:BTCUSDT.vbuy)\n_vsell=(BYBIT:BTCUSDT-SPOT.vsell+BYBIT:BTCUSDC-SPOT.vsell+BYBIT:BTCUSD.vsell+BYBIT:BTCUSDT.vsell)\nline(cum(_vbuy-_vsell), title=BYBIT)", + "createdAt": 1716531496274, + "updatedAt": 1726323557557, + "options": { + "priceScaleId": "cvd-exchange-BYBIT", + "scaleMargins": { + "top": 0.29, + "bottom": 0.49 + }, + "color": "rgb(255,235,59)", + "visible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "lastValueVisible": true + }, + "optionsDefinitions": {}, + "series": [ + "cvd-exchange-BYBIT" + ], + "displayName": "CVD Exchange BYBIT", + "unsavedChanges": false + }, + "cvd-exchange-COINBASE": { + "id": "cvd-exchange-COINBASE", + "libraryId": "cvd-exchange-COINBASE", + "name": "cvd-exchange-COINBASE", + "script": "_vbuy=(COINBASE:BTC-USDT.vbuy+COINBASE:BTC-USD.vbuy)\n_vsell=(COINBASE:BTC-USDT.vsell+COINBASE:BTC-USD.vsell)\nline(cum(_vbuy-_vsell), title=COINBASE)", + "createdAt": 1716531496274, + "updatedAt": 1726323569625, + "options": { + "priceScaleId": "cvd-exchange-COINBASE", + "scaleMargins": { + "top": 0.54, + "bottom": 0.24 + }, + "color": "rgba(0,188,212,0.98)", + "visible": true, + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "cvd-exchange-COINBASE" + ], + "displayName": "CVD Exchange COINBASE", + "unsavedChanges": false + }, + "cvd-exchange-DERIBIT": { + "id": "cvd-exchange-DERIBIT", + "libraryId": "cvd-exchange-DERIBIT", + "name": "cvd-exchange-DERIBIT", + "script": "_vbuy=(DERIBIT:BTC-PERPETUAL.vbuy+DERIBIT:BTC_USDC-PERPETUAL.vbuy)\n_vsell=(DERIBIT:BTC-PERPETUAL.vsell+DERIBIT:BTC_USDC-PERPETUAL.vsell)\nline(cum(_vbuy-_vsell), title=DERIBIT)", + "createdAt": 1716531496274, + "updatedAt": 1718701896712, + "options": { + "priceScaleId": "cvd-exchange-DERIBIT", + "scaleMargins": { + "top": 0.29, + "bottom": 0.49 + }, + "color": "rgba(14,215,22,0.75)", + "visible": true + }, + "optionsDefinitions": {}, + "series": [ + "cvd-exchange-DERIBIT" + ], + "displayName": "CVD Exchange DERIBIT", + "unsavedChanges": false + }, + "_vrjve84cmmifpf2y": { + "id": "_vrjve84cmmifpf2y", + "libraryId": "bfs-cvd2", + "name": "BF.S CVD", + "script": "_vbuy = (BITFINEX:BTCUSD.vbuy+BITFINEX:BTCUST.vbuy)\n_vsell = (BITFINEX:BTCUSD.vsell+BITFINEX:BTCUST.vsell)\n\n\n\nline(cum(_vbuy - _vsell), title=bf.s )", + "createdAt": 1718763056740, + "updatedAt": 1726323584711, + "options": { + "priceScaleId": "_z516f0zwmtibbr76", + "scaleMargins": { + "top": 0.54, + "bottom": 0.24 + }, + "lastValueVisible": true, + "color": "rgb(41,98,255)", + "visible": true, + "priceLineVisible": false, + "baseLineVisible": false, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "bfs-cvd2" + ], + "displayName": "BF.S CVD", + "unsavedChanges": false + }, + "_tt3vmq14qq0q6zh3": { + "id": "_tt3vmq14qq0q6zh3", + "libraryId": "oks-cvd", + "name": "OK.S CVD", + "script": "_vbuy = (OKEX:BTC-USD.vbuy+OKEX:BTC-USDT.vbuy+OKEX:BTC-USDC.vbuy)\n_vsell = (OKEX:BTC-USD.vsell+OKEX:BTC-USDT.vsell+OKEX:BTC-USDC.vsell)\n\n\nline(cum(_vbuy - _vsell), title=ok.s )", + "createdAt": 1718762559127, + "updatedAt": 1726323595475, + "options": { + "priceScaleId": "_pshii8bunephrk55", + "scaleMargins": { + "top": 0.79, + "bottom": 0.01 + }, + "color": "rgb(91,156,246)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "oks-cvd" + ], + "displayName": "OK.S CVD", + "unsavedChanges": false + }, + "_a0uddpx1jizg6f4h": { + "id": "_a0uddpx1jizg6f4h", + "libraryId": "okp-cvd", + "name": "OK.P CVD", + "script": "_vbuy = (OKEX:BTC-USD-SWAP.vbuy+OKEX:BTC-USDT-SWAP.vbuy+OKEX:BTC-USDC-SWAP.vbuy)\n_vsell = (OKEX:BTC-USD-SWAP.vsell+OKEX:BTC-USDT-SWAP.vsell+OKEX:BTC-USDC-SWAP.vsell)\n\nline(cum(_vbuy - _vsell), title=ok.p )", + "createdAt": 1718762487919, + "updatedAt": 1726323856117, + "options": { + "priceScaleId": "_s99eqgk6zaglfjn7", + "scaleMargins": { + "top": 0.79, + "bottom": 0.01 + }, + "color": "rgb(152,111,223)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "okp-cvd" + ], + "displayName": "OK.P CVD", + "unsavedChanges": false + }, + "_zial46x5pdsip5ms": { + "id": "_zial46x5pdsip5ms", + "libraryId": "krakens-cvd", + "name": "KRAKEN.S CVD", + "script": "_vbuy = (KRAKEN:XBT/USD.vbuy)\n_vsell = (KRAKEN:XBT/USD.vsell)\n\n\n\nline(cum(_vbuy - _vsell), title=kraken.s )", + "createdAt": 1718763191990, + "updatedAt": 1726323606730, + "options": { + "priceScaleId": "_8d9u97zke39v6fz9", + "scaleMargins": { + "top": 0.53, + "bottom": 0.24 + }, + "color": "rgb(165,214,167)", + "visible": true, + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "krakens-cvd" + ], + "displayName": "KRAKEN.S CVD", + "unsavedChanges": false + }, + "_b8l0qkf2evpjbt1n": { + "id": "_b8l0qkf2evpjbt1n", + "libraryId": "cartel-fdusd", + "name": "Cartel FDUSD", + "script": "_vbuy=(BINANCE:btcfdusd.vbuy)\n_vsell=(BINANCE:btcfdusd.vsell)\nline(cum(_vbuy-_vsell), title='FDUSD')", + "createdAt": 1720292159337, + "updatedAt": 1726323368471, + "options": { + "priceScaleId": "cartel", + "scaleMargins": { + "top": 0.05, + "bottom": 0.72 + }, + "color": "rgb(255,8,0)", + "visible": true, + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "cartel-fdusd" + ], + "displayName": "Cartel FDUSD" + } + }, + "indicatorOrder": [ + "_yc5ewfpz6pjfe1ru", + "cvd-exchange-BINANCE", + "cvd-exchange-BINANCE_FUTURES", + "cvd-exchange-BYBIT", + "cvd-exchange-COINBASE", + "cvd-exchange-DERIBIT", + "_vrjve84cmmifpf2y", + "_tt3vmq14qq0q6zh3", + "_a0uddpx1jizg6f4h", + "_zial46x5pdsip5ms", + "_b8l0qkf2evpjbt1n" + ], + "priceScales": { + "right": { + "scaleMargins": { + "top": 0.03, + "bottom": 0.73 + }, + "indicators": [ + "Price" + ], + "priceFormat": { + "precision": 1, + "minMove": 0.1 + } + }, + "cvd": { + "scaleMargins": { + "top": 0.8, + "bottom": 0.02 + }, + "indicators": [ + "CVD" + ] + }, + "volume_liquidations": { + "scaleMargins": { + "top": 0.75, + "bottom": 0.17 + } + }, + "volume": { + "scaleMargins": { + "top": 0.84, + "bottom": 0 + } + }, + "cvdspot": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.2 + } + }, + "_n6t8sd7vxozc5h4r": { + "scaleMargins": { + "top": 0.29, + "bottom": 0.54 + }, + "indicators": [ + "CVD Spot Big" + ] + }, + "big-order-cvd": { + "scaleMargins": { + "top": 0.29, + "bottom": 0.54 + }, + "indicators": [ + "CVD Spot Big" + ] + }, + "_gyovgnu9maq1uwle": { + "scaleMargins": { + "top": 0.29, + "bottom": 0.54 + }, + "indicators": [ + "CVD Spot Mid" + ] + }, + "_ioxhx4vgropry53p": { + "scaleMargins": { + "top": 0.29, + "bottom": 0.54 + }, + "indicators": [ + "CVD Spot Small" + ] + }, + "small-order-cvd": { + "scaleMargins": { + "top": 0.29, + "bottom": 0.54 + }, + "indicators": [ + "CVD Spot Small" + ] + }, + "_7fc9z73qoetc9k0y": { + "scaleMargins": { + "top": 0.51, + "bottom": 0.32 + }, + "indicators": [ + "CVD Perp Big" + ] + }, + "_5x0wmryc4x516pxm": { + "scaleMargins": { + "top": 0.51, + "bottom": 0.32 + }, + "indicators": [ + "CVD Perp Mid" + ] + }, + "_zjxpbo8j4zw1foku": { + "scaleMargins": { + "top": 0.51, + "bottom": 0.32 + }, + "indicators": [ + "CVD Perp Small" + ] + }, + "cvd-exchange": { + "scaleMargins": { + "top": 0.79, + "bottom": 0.03 + }, + "indicators": [ + "CVD Exchange" + ] + }, + "cvd-exchange-BINANCE": { + "scaleMargins": { + "top": 0.04, + "bottom": 0.74 + }, + "indicators": [ + "cvd-exchange-BINANCE" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "cvd-exchange-BINANCE_FUTURES": { + "scaleMargins": { + "top": 0.05, + "bottom": 0.74 + }, + "indicators": [ + "cvd-exchange-BINANCE_FUTURES" + ] + }, + "cvd-exchange-BYBIT": { + "scaleMargins": { + "top": 0.29, + "bottom": 0.49 + }, + "indicators": [ + "cvd-exchange-BYBIT" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "cvd-exchange-COINBASE": { + "scaleMargins": { + "top": 0.54, + "bottom": 0.24 + }, + "indicators": [ + "cvd-exchange-COINBASE" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "cvd-exchange-DERIBIT": { + "scaleMargins": { + "top": 0.29, + "bottom": 0.49 + }, + "indicators": [ + "cvd-exchange-DERIBIT" + ] + }, + "_z516f0zwmtibbr76": { + "scaleMargins": { + "top": 0.54, + "bottom": 0.24 + }, + "indicators": [ + "BF.S CVD2" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "_pshii8bunephrk55": { + "scaleMargins": { + "top": 0.79, + "bottom": 0.01 + }, + "indicators": [ + "OK.S CVD" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "_s99eqgk6zaglfjn7": { + "scaleMargins": { + "top": 0.79, + "bottom": 0.01 + }, + "indicators": [ + "OK.P CVD" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "_8d9u97zke39v6fz9": { + "scaleMargins": { + "top": 0.53, + "bottom": 0.24 + }, + "indicators": [ + "KRAKEN.S CVD" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "cartel": { + "scaleMargins": { + "top": 0.05, + "bottom": 0.72 + }, + "indicators": [ + "Cartel FDUSD" + ] + } + }, + "layouting": false, + "showIndicators": true, + "timeframe": "900", + "refreshRate": 1000, + "showAlerts": true, + "showAlertsLabel": true, + "showLegend": true, + "fillGapsWithEmpty": true, + "showHorizontalGridlines": false, + "horizontalGridlinesColor": "rgba(255,255,255,.1)", + "showVerticalGridlines": false, + "verticalGridlinesColor": "rgba(255,255,255,.1)", + "showWatermark": false, + "watermarkColor": "rgba(255,255,255,.1)", + "showBorder": true, + "borderColor": null, + "textColor": null, + "showLeftScale": false, + "showRightScale": true, + "showTimeScale": true, + "hiddenMarkets": {}, + "barSpacing": 6.218359926594028, + "_id": "chart copy 1" + }, + "delta": { + "indicatorsErrors": {}, + "indicators": { + "_jrw7wrmtet6jdmth": { + "id": "_jrw7wrmtet6jdmth", + "name": "Delta Binance Spot", + "options": { + "priceScaleId": "delta-binance-spot", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0, + "bottom": 0.9 + } + }, + "script": "_vbuy = (\nBINANCE:btcfdusd.vbuy+\nBINANCE:btcusdc.vbuy+\nBINANCE:btcusdt.vbuy\n)\n\n_vsell = (\nBINANCE:btcfdusd.vsell+\nBINANCE:btcusdc.vsell+\nBINANCE:btcusdt.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" BN(S)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122536983, + "displayName": "Delta Binance Spot", + "description": null, + "author": null, + "preview": {}, + "libraryId": "delta-binance-spot3", + "optionsDefinitions": {}, + "series": [ + "delta-binance-spot3" + ], + "unsavedChanges": false + }, + "_ky9zklmwo7aud15n": { + "id": "_ky9zklmwo7aud15n", + "libraryId": "delta-okx-spot2", + "name": "Delta OKX Spot", + "script": "_vbuy = (\nOKEX:BTC-USDC.vbuy+\nOKEX:BTC-USDT.vbuy\n)\n\n_vsell = (\nOKEX:BTC-USDC.vsell+\nOKEX:BTC-USDT.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" OKX(S)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122547279, + "options": { + "priceScaleId": "_lcgvarzto6kaxhsi", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.1, + "bottom": 0.81 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-okx-spot2" + ], + "displayName": "Delta OKX Spot", + "unsavedChanges": false + }, + "_2w5s6578gp6z2of4": { + "id": "_2w5s6578gp6z2of4", + "libraryId": "delta-bybit-spot", + "name": "Delta ByBit Spot", + "script": "_vbuy = (\nBYBIT:BTCUSDC-SPOT.vbuy+\nBYBIT:BTCUSDT-SPOT.vbuy\n)\n\n_vsell = (\nBYBIT:BTCUSDC-SPOT.vsell+\nBYBIT:BTCUSDT-SPOT.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" ByB(S)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122558264, + "options": { + "priceScaleId": "_b4xic3uzz06rtv26", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.39, + "bottom": 0.52 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-bybit-spot" + ], + "displayName": "Delta ByBit Spot", + "unsavedChanges": false + }, + "_hcp8eo38prw5wqug": { + "id": "_hcp8eo38prw5wqug", + "libraryId": "delta-kraken-spot", + "name": "Delta KRAKEN Spot", + "script": "_vbuy = (\nKRAKEN:XBT/USD.vbuy+\nKRAKEN:XBT/USDC.vbuy+\nKRAKEN:XBT/USDT.vbuy\n)\n\n_vsell = (\nKRAKEN:XBT/USD.vsell+\nKRAKEN:XBT/USDC.vsell+\nKRAKEN:XBT/USDT.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" KRAKEN(S)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122573176, + "options": { + "priceScaleId": "_cn9x80j0ttweyqz6", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.19, + "bottom": 0.72 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-kraken-spot" + ], + "displayName": "Delta KRAKEN Spot", + "unsavedChanges": false + }, + "_r0rtgrchlud62lwx": { + "id": "_r0rtgrchlud62lwx", + "libraryId": "delta-coinbase-spot", + "name": "Delta CoinBASE Spot", + "script": "_vbuy = (\nCOINBASE:BTC-USD.vbuy+\nCOINBASE:BTC-USDT.vbuy\n)\n\n_vsell = (\nCOINBASE:BTC-USD.vsell+\nCOINBASE:BTC-USDT.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" CB(S)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122611545, + "options": { + "priceScaleId": "_zf4aekw47m3y53zx", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.27, + "bottom": 0.62 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-coinbase-spot" + ], + "displayName": "Delta CoinBASE Spot", + "unsavedChanges": false + }, + "_ag3apce9u97doq07": { + "id": "_ag3apce9u97doq07", + "libraryId": "delta-kraken-perp", + "name": "Delta KRAKEN Perp", + "script": "_vbuy = (\nKRAKEN:PI_XBTUSD.vbuy\n)\n\n_vsell = (\nKRAKEN:PI_XBTUSD.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" KRAKEN(P)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122718132, + "options": { + "priceScaleId": "_9zvs80vj4jeitgp5", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.78, + "bottom": 0.11 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-kraken-perp" + ], + "displayName": "Delta KRAKEN Perp", + "unsavedChanges": false + }, + "_sa3ysdyv7xplfmhc": { + "id": "_sa3ysdyv7xplfmhc", + "libraryId": "delta-bybit-perp", + "name": "Delta Bybit Perp", + "script": "_vbuy = (\nBYBIT:BTCUSD.vbuy+\nBYBIT:BTCUSDT.vbuy\n)\n\n_vsell = (\nBYBIT:BTCUSD.vsell+\nBYBIT:BTCUSDT.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" ByB(P)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122689369, + "options": { + "priceScaleId": "_r5cjw16ua6kobv5i", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.69, + "bottom": 0.25 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-bybit-perp" + ], + "displayName": "Delta Bybit Perp", + "unsavedChanges": false + }, + "_nttpc1117uhqv96p": { + "id": "_nttpc1117uhqv96p", + "libraryId": "delta-okx-perp", + "name": "Delta OKX Perp", + "script": "_vbuy = (\nOKEX:BTC-USD-SWAP.vbuy+\nOKEX:BTC-USDC-SWAP.vbuy+\nOKEX:BTC-USDT-SWAP.vbuy\n)\n\n_vsell = (\nOKEX:BTC-USD-SWAP.vsell+\nOKEX:BTC-USDC-SWAP.vsell+\nOKEX:BTC-USDT-SWAP.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" OKX(P)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122680162, + "options": { + "priceScaleId": "_y0s5gdub1mtsl5pv", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.58, + "bottom": 0.33 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-okx-perp" + ], + "displayName": "Delta OKX Perp", + "unsavedChanges": false + }, + "_hpzd9xihlf52lr85": { + "id": "_hpzd9xihlf52lr85", + "libraryId": "delta-binance-perp", + "name": "Delta Binance Perp", + "script": "_vbuy = (\nBINANCE_FUTURES:btcusd_perp.vbuy+\nBINANCE_FUTURES:btcusdt.vbuy\n)\n\n_vsell = (\nBINANCE_FUTURES:btcusd_perp.vsell+\nBINANCE_FUTURES:btcusdt.vsell\n)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\" BN(P)\")", + "createdAt": 1649330197719, + "updatedAt": 1719122668492, + "options": { + "priceScaleId": "_gvqnq2ca8tfq568n", + "upColor": "rgb(0,150,136)", + "downColor": "rgb(233,30,99)", + "visible": true, + "length": 7, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "lastValueVisible": true, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + }, + "scaleMargins": { + "top": 0.49, + "bottom": 0.43 + } + }, + "optionsDefinitions": {}, + "series": [ + "delta-binance-perp" + ], + "displayName": "Delta Binance Perp", + "unsavedChanges": false + }, + "_pdkc3oypu1l59j72": { + "id": "_pdkc3oypu1l59j72", + "libraryId": "bf2", + "name": "BF2", + "script": "_vbuy = (BITFINEX:BTCUSD.vbuy+BITFINEX:BTCUST.vbuy)\n_vsell = (BITFINEX:BTCUSD.vsell+BITFINEX:BTCUST.vsell)\n\nvolume = _vbuy+_vsell\na = sma(Math.pow(volume,2),options.length)\nb = Math.pow(sum(volume,options.length),2)/Math.pow(options.length,2)\nstdev = Math.sqrt(a - b)\nbasis = sma(volume, options.length)\ndev = 1 * stdev\ntreshold = basis + dev\n\ndelta = _vbuy - _vsell\n\nplothistogram({ time: time, value: (delta), color: delta > 0 ? ( volume > treshold ? options.upColorHighVol : options.upColorLowVol) : ( volume > treshold ? options.downColorHighVol : options.downColorLowVol)}, title=\"BF.S\")", + "createdAt": 1715187672296, + "updatedAt": 1720080344378, + "options": { + "priceScaleId": "_vcgrf1t9xv14mihs", + "scaleMargins": { + "top": 0.89, + "bottom": 0.02 + }, + "length": 14, + "upColorHighVol": "rgb(41,98,255)", + "upColorLowVol": "rgb(0,121,107)", + "downColorHighVol": "rgb(240,98,146)", + "downColorLowVol": "rgb(194,24,91)", + "visible": true, + "lastValueVisible": true, + "baseLineVisible": false, + "priceLineVisible": false, + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + }, + "optionsDefinitions": {}, + "series": [ + "bf2" + ], + "displayName": "BF2", + "unsavedChanges": false + } + }, + "indicatorOrder": [ + "_jrw7wrmtet6jdmth", + "_ky9zklmwo7aud15n", + "_2w5s6578gp6z2of4", + "_hcp8eo38prw5wqug", + "_r0rtgrchlud62lwx", + "_hpzd9xihlf52lr85", + "_nttpc1117uhqv96p", + "_sa3ysdyv7xplfmhc", + "_ag3apce9u97doq07", + "_pdkc3oypu1l59j72" + ], + "priceScales": { + "right": { + "scaleMargins": { + "top": 0.25, + "bottom": 0.19 + }, + "indicators": [ + "Price 🐶 GIGI" + ], + "mode": 0 + }, + "volume_liquidations": { + "scaleMargins": { + "top": 0.13, + "bottom": 0.8 + }, + "indicators": [ + "Liquidations" + ] + }, + "volume": { + "scaleMargins": { + "top": 0.93, + "bottom": 0 + }, + "indicators": [ + "Relative Volume" + ] + }, + "delta-binance-spot": { + "scaleMargins": { + "top": 0, + "bottom": 0.9 + }, + "indicators": [ + "Delta Binance Spot" + ], + "mode": 0 + }, + "cvd-coinbase": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.2 + } + }, + "delta-coinbase": { + "scaleMargins": { + "top": 0.11, + "bottom": 0.74 + }, + "indicators": [ + "Delta Coinbase" + ], + "mode": 0 + }, + "delta-bybit-perps": { + "scaleMargins": { + "top": 0.57, + "bottom": 0.3 + }, + "indicators": [ + "Delta Bybit Perps" + ], + "mode": 0 + }, + "delta-bitmex": { + "scaleMargins": { + "top": 0.82, + "bottom": 0.12 + }, + "indicators": [ + "Delta BitMex" + ], + "mode": 0 + }, + "delta-ftx-spot": { + "scaleMargins": { + "top": 0.18, + "bottom": 0.76 + }, + "indicators": [ + "Delta FTX Spot" + ], + "mode": 0 + }, + "delta-ftx-perps": { + "scaleMargins": { + "top": 0.88, + "bottom": 0.06 + }, + "indicators": [ + "Delta FTX Perps" + ], + "mode": 0 + }, + "delta-bitfinex-spot": { + "scaleMargins": { + "top": 0.12, + "bottom": 0.88 + }, + "indicators": [ + "Delta BitFinex Spot" + ], + "mode": 0 + }, + "delta-bitfinex-perps2": { + "scaleMargins": { + "top": 0.89, + "bottom": 0.05 + }, + "indicators": [ + "Delta BitFinex Perps" + ], + "mode": 0 + }, + "delta-binance-perps": { + "scaleMargins": { + "top": 0.7, + "bottom": 0.17 + }, + "indicators": [ + "Delta Binance Perps" + ], + "mode": 0 + }, + "okx-spot": { + "scaleMargins": { + "top": 0.09, + "bottom": 0.84 + }, + "indicators": [ + "OKX Spot" + ] + }, + "price": { + "scaleMargins": { + "top": 0.11, + "bottom": 0.43 + }, + "indicators": [ + "Price", + "Price" + ], + "mode": 0, + "priceFormat": { + "precision": 1, + "minMove": 0.1 + } + }, + "delta-okx-spot2": { + "scaleMargins": { + "top": 0.24, + "bottom": 0.61 + }, + "indicators": [ + "Delta OKX Spot" + ] + }, + "delta-okx-perps2": { + "scaleMargins": { + "top": 0.83, + "bottom": 0.03 + }, + "indicators": [ + "Delta OKX Perps" + ] + }, + "cvd-btc-spot": { + "scaleMargins": { + "top": 0.56, + "bottom": 0.36 + }, + "indicators": [ + "CVD ETH Spot" + ] + }, + "cvd-btc-perp": { + "scaleMargins": { + "top": 0.56, + "bottom": 0.36 + }, + "indicators": [ + "CVD ETH Perp" + ] + }, + "relative-volume copy 1": { + "scaleMargins": { + "top": 0.55, + "bottom": 0.43 + }, + "indicators": [ + "Relative Volume" + ] + }, + "single-bar-delta-divergence": { + "scaleMargins": { + "top": 0, + "bottom": 0.06 + }, + "indicators": [ + "Single Bar Delta Divergence" + ] + }, + "cvd-eth-perp2": { + "scaleMargins": { + "top": 0.43, + "bottom": 0.49 + }, + "indicators": [ + "CVD ETH Perp" + ] + }, + "cvd-eth-spot22": { + "scaleMargins": { + "top": 0.43, + "bottom": 0.49 + }, + "indicators": [ + "CVD ETH Spot" + ] + }, + "cvd-okx-s": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.2 + } + }, + "_lcgvarzto6kaxhsi": { + "scaleMargins": { + "top": 0.1, + "bottom": 0.81 + }, + "indicators": [ + "Delta OKX Spot" + ] + }, + "_b4xic3uzz06rtv26": { + "scaleMargins": { + "top": 0.39, + "bottom": 0.52 + }, + "indicators": [ + "Delta ByBit Spot" + ], + "mode": 0 + }, + "_cn9x80j0ttweyqz6": { + "scaleMargins": { + "top": 0.19, + "bottom": 0.72 + }, + "indicators": [ + "Delta KRAKEN Spot" + ] + }, + "_zf4aekw47m3y53zx": { + "scaleMargins": { + "top": 0.27, + "bottom": 0.62 + }, + "indicators": [ + "Delta CoinBASE Spot" + ] + }, + "_77ryrio4wutjl31c": { + "scaleMargins": { + "top": 0.4, + "bottom": 0.33 + }, + "indicators": [ + "CVD Spot" + ] + }, + "_s2ihajoqymk4gckd": { + "scaleMargins": { + "top": 0.41, + "bottom": 0.32 + }, + "indicators": [ + "CVD Perp" + ] + }, + "_9zvs80vj4jeitgp5": { + "scaleMargins": { + "top": 0.78, + "bottom": 0.11 + }, + "indicators": [ + "Delta KRAKEN Perp" + ] + }, + "_r5cjw16ua6kobv5i": { + "scaleMargins": { + "top": 0.69, + "bottom": 0.25 + }, + "indicators": [ + "Delta Bybit Perp" + ] + }, + "_y0s5gdub1mtsl5pv": { + "scaleMargins": { + "top": 0.58, + "bottom": 0.33 + }, + "indicators": [ + "Delta OKX Perp" + ] + }, + "_gvqnq2ca8tfq568n": { + "scaleMargins": { + "top": 0.49, + "bottom": 0.43 + }, + "indicators": [ + "Delta Binance Perp" + ] + }, + "_6ts9lvh8gqq5lx8q": { + "scaleMargins": { + "top": 0.24, + "bottom": 0.2 + }, + "indicators": [ + "Price 🐶 GIGI" + ], + "priceFormat": { + "precision": 1, + "minMove": 0.1 + } + }, + "cvd-spot": { + "scaleMargins": { + "top": 0.3, + "bottom": 0.45 + } + }, + "_vcgrf1t9xv14mihs": { + "scaleMargins": { + "top": 0.89, + "bottom": 0.02 + }, + "indicators": [ + "BF2" + ], + "priceFormat": { + "type": "volume", + "precision": 2, + "minMove": 0.01, + "auto": true + } + } + }, + "layouting": false, + "showIndicators": true, + "timeframe": "300", + "refreshRate": 200, + "showAlerts": true, + "showAlertsLabel": true, + "showLegend": true, + "fillGapsWithEmpty": true, + "showHorizontalGridlines": false, + "horizontalGridlinesColor": "rgba(255,255,255,.1)", + "showVerticalGridlines": false, + "verticalGridlinesColor": "rgba(255,255,255,.1)", + "showWatermark": false, + "watermarkColor": "rgba(255,255,255,.1)", + "showBorder": true, + "borderColor": null, + "textColor": null, + "showLeftScale": false, + "showRightScale": true, + "showTimeScale": true, + "hiddenMarkets": {}, + "barSpacing": 32.61306606314917, + "_id": "delta", + "forceNormalizePrice": false, + "navigationState": { + "tab": "options", + "optionsQuery": "", + "fontSizePx": 14 + } + }, + "chart": { + "indicatorsErrors": {}, + "indicators": { + "_pciob9dy9vlp6ksw": { + "id": "_pciob9dy9vlp6ksw", + "name": "CVD", + "options": { + "priceScaleId": "cvd", + "priceFormat": { + "type": "volume" + }, + "color": "#3BCA6D", + "scaleMargins": { + "top": 0.84, + "bottom": 0 + } + }, + "script": "line(cum(vbuy - vsell))", + "createdAt": 1749271162306, + "updatedAt": 1749271848162, + "description": "Cumulative Volume Delta", + "enabled": true, + "libraryId": "cvd", + "series": [ + "cvd" + ], + "optionsDefinitions": {} + }, + "_whcn9xf1lz3xx986": { + "enabled": true, + "name": "Liquidations", + "description": "Liquidations by side", + "script": "histogram(lbuy, color=options.upColor)\nhistogram(-lsell, color=options.downColor)", + "options": { + "priceFormat": { + "type": "volume" + }, + "priceScaleId": "volume_liquidations", + "upColor": "rgb(255,76,243)", + "downColor": "rgb(255,183,77)", + "scaleMargins": { + "top": 0.75, + "bottom": 0.17 + } + }, + "id": "_whcn9xf1lz3xx986", + "createdAt": 1749271162306, + "updatedAt": null, + "libraryId": "liquidations", + "series": [ + "liquidations", + "p3zlvktp" + ], + "optionsDefinitions": {} + }, + "_d2w8siglj8bhy2p3": { + "enabled": true, + "name": "Price", + "script": "var ohlc = options.useHeikinAshi ? \n avg_heikinashi(bar) : \n options.useGaps ? \n avg_ohlc_with_gaps(bar) : \n avg_ohlc(bar)\n\nplotcandlestick(ohlc)", + "options": { + "priceScaleId": "right", + "priceFormat": { + "auto": true, + "precision": 1, + "minMove": 0.1 + }, + "priceLineVisible": true, + "lastValueVisible": true, + "borderVisible": true, + "upColor": "rgb(59,202,109)", + "downColor": "rgb(214,40,40)", + "borderUpColor": "rgb(59,202,109)", + "borderDownColor": "rgb(239,67,82)", + "wickUpColor": "rgb(223,211,144)", + "wickDownColor": "rgb(239,67,82)", + "useGaps": false, + "useHeikinAshi": false, + "scaleMargins": { + "top": 0.04, + "bottom": 0.26 + } + }, + "id": "_d2w8siglj8bhy2p3", + "createdAt": 1749271162306, + "updatedAt": null, + "libraryId": "price", + "series": [ + "price" + ], + "optionsDefinitions": {}, + "unsavedChanges": true + }, + "_xmys71x0bt3xmrb1": { + "enabled": true, + "name": "Volume", + "description": "Volume + delta", + "script": "if (upColor === 0) {\n if (options.showDelta) {\n upColor = options.upBgColor\n downColor = options.downBgColor\n } else {\n upColor = options.upColor\n downColor = options.downColor\n }\n}\n\nif (options.showDelta) {\n histogram({ time: time, value: Math.abs(vbuy-vsell), color: vbuy - vsell > 0 ? options.upColor : options.downColor})\n}\n\nhistogram({ time: time, value: vbuy + vsell, color: vbuy > vsell ? upColor : downColor })", + "options": { + "priceFormat": { + "type": "volume" + }, + "upColor": "rgb(59,202,109)", + "downColor": "rgb(235,30,47)", + "priceScaleId": "volume", + "scaleMargins": { + "top": 0.84, + "bottom": 0 + }, + "showDelta": true, + "upBgColor": "rgba(59,202,109,0.5)", + "downBgColor": "rgba(235,30,47,0.5)" + }, + "id": "_xmys71x0bt3xmrb1", + "createdAt": 1749271162306, + "updatedAt": null, + "libraryId": "volume", + "series": [ + "volume", + "x4q2m953" + ], + "optionsDefinitions": {} + } + }, + "indicatorOrder": [ + "_pciob9dy9vlp6ksw", + "_whcn9xf1lz3xx986", + "_d2w8siglj8bhy2p3", + "_xmys71x0bt3xmrb1" + ], + "priceScales": { + "right": { + "scaleMargins": { + "top": 0.04, + "bottom": 0.26 + }, + "priceFormat": { + "precision": 1, + "minMove": 0.1 + } + }, + "cvd": { + "scaleMargins": { + "top": 0.84, + "bottom": 0 + } + }, + "volume_liquidations": { + "scaleMargins": { + "top": 0.75, + "bottom": 0.17 + } + }, + "volume": { + "scaleMargins": { + "top": 0.84, + "bottom": 0 + } + } + }, + "layouting": false, + "showIndicators": true, + "timeframe": 5, + "refreshRate": 1000, + "showAlerts": true, + "showAlertsLabel": true, + "showLegend": true, + "fillGapsWithEmpty": false, + "showHorizontalGridlines": false, + "horizontalGridlinesColor": "rgba(255,255,255,.1)", + "showVerticalGridlines": false, + "verticalGridlinesColor": "rgba(255,255,255,.1)", + "showWatermark": true, + "watermarkColor": "rgba(255,255,255,.033)", + "showBorder": true, + "borderColor": null, + "textColor": null, + "showLeftScale": false, + "showRightScale": true, + "showTimeScale": true, + "hiddenMarkets": {}, + "barSpacing": 4.6914145078209195, + "_id": "chart", + "navigationState": { + "tab": "script", + "optionsQuery": "", + "fontSizePx": 14 + }, + "forceNormalizePrice": false + } + } +} \ No newline at end of file diff --git a/python/valuecell/agents/common/trading/data/interfaces.py b/python/valuecell/agents/common/trading/data/interfaces.py index cbad57a47..c6710b850 100644 --- a/python/valuecell/agents/common/trading/data/interfaces.py +++ b/python/valuecell/agents/common/trading/data/interfaces.py @@ -3,7 +3,11 @@ from abc import ABC, abstractmethod from typing import List -from valuecell.agents.common.trading.models import Candle, MarketSnapShotType +from valuecell.agents.common.trading.models import ( + Candle, + DataSourceImage, + MarketSnapShotType, +) # Contracts for market data sources (module-local abstract interfaces). # These are plain ABCs (not Pydantic models) so implementations can be @@ -42,3 +46,18 @@ async def get_market_snapshot(self, symbols: List[str]) -> MarketSnapShotType: """ raise NotImplementedError + + +class BaseScreenshotDataSource(ABC): + """ + Abstract base class for screenshot data sources. + """ + + @abstractmethod + async def capture(self, *args, **kwargs) -> List[DataSourceImage]: + """ + Captures a screenshot and returns a list of `DataSourceImage` instances describing the + captured bytes and/or saved file path. Implementations should standardize + on `DataSourceImage.content` (bytes) and/or `DataSourceImage.filepath`. + """ + raise NotImplementedError diff --git a/python/valuecell/agents/common/trading/data/screenshot.py b/python/valuecell/agents/common/trading/data/screenshot.py new file mode 100644 index 000000000..24922e14a --- /dev/null +++ b/python/valuecell/agents/common/trading/data/screenshot.py @@ -0,0 +1,287 @@ +import asyncio +import os +from datetime import datetime +from typing import List, Optional + +import aiofiles +from loguru import logger +from playwright.async_api import Browser, Page, Playwright, async_playwright + +from valuecell.agents.common.trading.models import DataSourceImage +from valuecell.utils.path import get_screenshot_path + +from ..models import InstrumentRef +from ..utils import normalize_symbol_tradingview +from .interfaces import BaseScreenshotDataSource +from .utils import generate_tradingview_html + + +class PlaywrightScreenshotDataSource(BaseScreenshotDataSource): + """ + Concrete implementation using Playwright. + Implements Async Context Manager protocol for automatic setup and teardown. + """ + + def __init__( + self, + target_url: Optional[str] = None, + page_content: Optional[str] = None, + instrument: Optional[InstrumentRef] = None, + ): + """ + Initializes configuration. + """ + self.target_url = target_url + self.page_content = page_content + self.instrument = instrument + + self.playwright: Optional[Playwright] = None + self.browser: Optional[Browser] = None + self.page: Optional[Page] = None + + async def __aenter__(self): + """ + Magic method for 'async with'. + Starts the browser, navigates to the URL, and performs the setup automation. + """ + # Delegate to explicit open() so callers can avoid repeated __aenter__ overhead + return await self.open() + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """ + Magic method for 'async with'. + Handles cleanup of browser resources. + """ + if exc_type: + logger.error(f"Exiting session due to exception: {exc_val}") + + await self.close() + + async def _cleanup(self): + """ + Internal helper to close browser resources. + """ + if self.browser: + await self.browser.close() + if self.playwright: + await self.playwright.stop() + + async def _setup(self): + if self.target_url: + logger.info(f"Navigating to {self.target_url}") + await self.page.goto(self.target_url) + return + + if self.page_content: + logger.info("Setting page content from provided HTML.") + await self.page.set_content(self.page_content) + return + + async def open(self): + """Explicit initialization to support one-time setup. + + Returns: + self: the initialized data source (same as __aenter__ would). + """ + try: + logger.info("Initializing Playwright session...") + self.playwright = await async_playwright().start() + self.browser = await self.playwright.chromium.launch(headless=True) + + context = await self.browser.new_context( + viewport={"width": 1600, "height": 900} + ) + self.page = await context.new_page() + + await self._setup() + + return self + + except Exception as e: + logger.error(f"Error during initialization: {e}") + # Ensure cleanup happens if initialization fails mid-way + await self._cleanup() + raise e + + async def close(self): + """Explicit cleanup to support one-time teardown. + + Calls internal cleanup helpers and logs session close. + """ + await self._cleanup() + logger.info("Session closed.") + + async def capture(self, *args, **kwargs) -> List[DataSourceImage]: + """ + Captures the current state of the page. + """ + if not self.page: + logger.error("Page is not initialized. Cannot capture screenshot.") + return [] + + try: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + logger.info(f"Capturing screenshot at {timestamp}...") + + # Capture screenshot bytes + screenshot_bytes = await self.page.screenshot(full_page=True) + + # Persist bytes to screenshots directory asynchronously using aiofiles + fmt = "png" + full_path = os.path.join( + get_screenshot_path(), f"screenshot_{timestamp}.{fmt}" + ) + + async with aiofiles.open(full_path, "wb") as fh: + await fh.write(screenshot_bytes) + + # Build DataSourceImage with both bytes and file path + ds_image = DataSourceImage( + url=None, + filepath=full_path, + content=screenshot_bytes, + instrument=self.instrument, + ) + + logger.info(f"Screenshot captured and saved to {full_path}") + return [ds_image] + + except Exception as e: + logger.error(f"Failed to capture screenshot: {e}") + return [] + + +class AggrScreenshotDataSource(PlaywrightScreenshotDataSource): + """ + Composite data source that aggregates multiple screenshot data sources. + """ + + def __init__( + self, + target_url: str, + file_path: str, + instrument: Optional[InstrumentRef] = None, + ): + super().__init__(target_url, instrument=instrument) + self.file_path = file_path + + # Ensure dummy file exists if not present + if not os.path.exists(self.file_path): + logger.warning( + f"File {self.file_path} not found. Creating empty JSON file." + ) + with open(self.file_path, "w") as f: + f.write("{}") + + async def _setup(self): + logger.info(f"Navigating to {self.target_url}") + await self.page.goto(self.target_url) + + logger.info("Waiting for core UI elements...") + # Wait for the green menu button to ensure page load + menu_btn = self.page.locator("#menu .menu__button") + await menu_btn.wait_for(state="visible", timeout=60000) + + logger.info("Page loaded. Executing setup sequence.") + + # 1. Click Menu + await menu_btn.click() + + # 2. Click Settings + await self.page.get_by_text("Settings", exact=True).click() + + # 3. Click New + await self.page.locator("button").filter(has_text="New").click() + + # 4. Handle File Upload + logger.info("Uploading file...") + async with self.page.expect_file_chooser() as fc_info: + await self.page.get_by_text("Upload template file").click() + + file_chooser = await fc_info.value + await file_chooser.set_files(self.file_path) + + # Wait slightly for UI render + await asyncio.sleep(1) + + # 5. Click IMPORT + logger.info("Confirming import...") + import_btn = self.page.locator("button").filter(has_text="IMPORT") + await import_btn.wait_for(state="visible") + await import_btn.click() + + logger.info("Import successful. Waiting for modal to close...") + await asyncio.sleep(3) + + +class TradingViewScreenshotDataSource(PlaywrightScreenshotDataSource): + """Screenshot data source that renders a TradingView widget for a given symbol + + It generates an HTML page containing the TradingView advanced-chart widget, + waits for the iframe and a short stabilization period, then captures only + the widget container element to produce a focused screenshot. + """ + + def __init__( + self, + symbol: str, + instrument: Optional[InstrumentRef] = None, + ) -> None: + symbol = normalize_symbol_tradingview(symbol) + page_content = generate_tradingview_html(symbol) + super().__init__( + target_url=None, page_content=page_content, instrument=instrument + ) + + self.symbol = symbol + + async def _setup(self): + # Build full HTML wrapper and set as page content + await self.page.set_content(self.page_content) + logger.info("Loading TradingView widget for %s", self.symbol) + + # Wait for the iframe to attach + await self.page.wait_for_selector("iframe", timeout=15000) + + # Wait for visual stability (widgets often animate/fetch data) + await self.page.wait_for_timeout(3000) + + # Ensure the container is visible + widget_locator = self.page.locator(".tradingview-widget-container") + await widget_locator.wait_for(state="visible", timeout=10000) + + async def capture(self, *args, **kwargs) -> List[DataSourceImage]: + if not self.page: + logger.error("Page is not initialized. Cannot capture screenshot.") + return [] + + try: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + logger.info( + f"Capturing TradingView widget screenshot for {self.symbol} at {timestamp}..." + ) + + widget_locator = self.page.locator(".tradingview-widget-container") + # Capture only the widget element to reduce noise + png_bytes = await widget_locator.screenshot() + + fmt = "png" + full_path = os.path.join( + get_screenshot_path(), f"tradingview_{self.symbol}_{timestamp}.{fmt}" + ) + async with aiofiles.open(full_path, "wb") as fh: + await fh.write(png_bytes) + + ds_image = DataSourceImage( + url=None, + filepath=full_path, + content=png_bytes, + instrument=self.instrument, + ) + + logger.info(f"TradingView screenshot saved to {full_path}") + return [ds_image] + + except Exception as e: + logger.error(f"Failed to capture TradingView screenshot: {e}") + return [] diff --git a/python/valuecell/agents/common/trading/data/utils.py b/python/valuecell/agents/common/trading/data/utils.py new file mode 100644 index 000000000..f581f6166 --- /dev/null +++ b/python/valuecell/agents/common/trading/data/utils.py @@ -0,0 +1,45 @@ +from pydantic import BaseModel, Field + + +class TradingViewConfig(BaseModel): + symbol: str + allow_symbol_change: bool = False + calendar: bool = False + details: bool = False + hide_side_toolbar: bool = True + hide_top_toolbar: bool = True + hide_legend: bool = False + hide_volume: bool = False + hotlist: bool = False + interval: str = "D" + locale: str = "en" + save_image: bool = False + style: str = "1" + theme: str = "light" + timezone: str = "Etc/UTC" + backgroundColor: str = "#ffffff" + gridColor: str = "rgba(46, 46, 46, 0.06)" + watchlist: list[str] = Field(default_factory=list) + withdateranges: bool = False + compareSymbols: list[str] = Field(default_factory=list) + studies: list[str] = Field(default_factory=list) + autosize: bool = True + + +def generate_tradingview_html(symbol: str) -> str: + widget_config = TradingViewConfig(symbol=symbol) + config_json = widget_config.model_dump_json(indent=2) + html_content = f""" + + +
+
+ + +
+ + +""" + return html_content diff --git a/python/valuecell/agents/common/trading/features/image.py b/python/valuecell/agents/common/trading/features/image.py new file mode 100644 index 000000000..ab2aa8656 --- /dev/null +++ b/python/valuecell/agents/common/trading/features/image.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from agno.agent import Agent as AgnoAgent +from agno.media import Image as AgnoImage +from agno.models.base import Model as AgnoModel +from loguru import logger + +from valuecell.utils import model as model_utils +from valuecell.utils.ts import get_current_timestamp_ms + +from ..constants import ( + FEATURE_GROUP_BY_IMAGE_ANALYSIS, + FEATURE_GROUP_BY_KEY, +) +from ..models import ( + FeatureVector, + UserRequest, +) +from .interfaces import ( + ImageBasedFeatureComputer, +) + +if TYPE_CHECKING: + from valuecell.agents.common.trading.models import DataSourceImage + + +class MLLMImageFeatureComputer(ImageBasedFeatureComputer): + """Image feature computer using an MLLM (Gemini via agno Agent). + + Consumes dashboard screenshots and extracts structured trading insights, + returning a single `FeatureVector` with textual features. Instrument is + left unset (market-wide analysis). + """ + + def __init__(self, model: AgnoModel, prompt: str) -> None: + """Initialize with a pre-built `Agent` instance. + + The agent's `.model` and `.model.id` are inspected for provider/model + metadata. + """ + self._model = model + self._prompt = prompt + self._agent = AgnoAgent( + model=model, + instructions=[self._prompt], + markdown=True, + ) + + @classmethod + def from_request( + cls, request: UserRequest, prompt: str + ) -> "MLLMImageFeatureComputer": + """Create an instance from an `LLMModelConfig`. + + Builds a model via `model_utils.create_model_with_provider` and + constructs an `Agent` that will be reused for image analysis. + """ + llm_cfg = request.llm_model_config + created_model = model_utils.create_model_with_provider( + provider=llm_cfg.provider, + model_id=llm_cfg.model_id, + api_key=llm_cfg.api_key, + ) + + # Validate that the model declares image support in config + if not model_utils.supports_model_images(llm_cfg.provider, llm_cfg.model_id): + raise RuntimeError( + f"Model {llm_cfg.model_id} from provider {llm_cfg.provider} does not declare support for images" + ) + + return cls(created_model, prompt) + + async def compute_features( + self, + images: Optional[List["DataSourceImage"]] = None, + meta: Optional[Dict[str, Any]] = None, + ) -> List[FeatureVector]: + if not images: + logger.warning("No images provided for image feature computation") + return [] + + logger.info("Running MLLM analysis on provided images: {}", images) + # Convert DataSourceImage -> agno.media.Image for the agent + agno_images: List[AgnoImage] = [] + for ds in images: + try: + if content := ds.content: + agno_images.append(AgnoImage(content=content)) + continue + + if filepath := ds.filepath: + agno_images.append(AgnoImage(filepath=filepath)) + continue + + if url := ds.url: + agno_images.append(AgnoImage(url=url)) + + except Exception as e: + logger.warning("Failed to convert DataSourceImage to agno.Image: {}", e) + + resp = await self._agent.arun( + "analyze the trading dashboard configuration in the provided image and generate a brief report.", + images=agno_images, + ) + + content: str = getattr(resp, "content", "") or "" + logger.info("MLLM analysis complete: {}", content) + + # Store only the raw markdown report as requested. + values: Dict[str, Any] = {"report_markdown": content} + meta = meta or {} + # TODO: include image metadata such as filepath, url + fv_meta = { + FEATURE_GROUP_BY_KEY: FEATURE_GROUP_BY_IMAGE_ANALYSIS, + **meta, + } + instrument = images[0].instrument + fv = FeatureVector( + ts=get_current_timestamp_ms(), + instrument=instrument, + values=values, + meta=fv_meta, + ) + return [fv] diff --git a/python/valuecell/agents/common/trading/features/interfaces.py b/python/valuecell/agents/common/trading/features/interfaces.py index 20569fc5b..e8d08ba34 100644 --- a/python/valuecell/agents/common/trading/features/interfaces.py +++ b/python/valuecell/agents/common/trading/features/interfaces.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional from valuecell.agents.common.trading.models import ( Candle, @@ -9,6 +9,10 @@ FeatureVector, ) +if TYPE_CHECKING: + # Only for type hints to avoid hard dependency at runtime + from valuecell.agents.common.trading.models import DataSourceImage + # Contracts for feature computation (module-local abstract interfaces). # Plain ABCs (not Pydantic) to keep implementations lightweight. @@ -39,6 +43,31 @@ def compute_features( raise NotImplementedError +class ImageBasedFeatureComputer(ABC): + """Abstract base for image-based feature computers. + + Implementations consume one or more images (screenshots, dashboard panes) + and return domain FeatureVector objects. The concrete implementations may + call external vision/LLM services. + """ + + @abstractmethod + async def compute_features( + self, + images: Optional[List["DataSourceImage"]] = None, + meta: Optional[Dict[str, Any]] = None, + ) -> List[FeatureVector]: + """Build feature vectors from the provided images. + + Args: + images: list of `DataSourceImage` objects produced by data sources. + meta: optional metadata such as instrument or timestamps. + Returns: + A list of `FeatureVector` items. + """ + raise NotImplementedError + + class BaseFeaturesPipeline(ABC): """Abstract pipeline that produces feature vectors (including market features).""" @@ -51,3 +80,22 @@ async def build(self) -> FeaturesPipelineResult: into this call. """ raise NotImplementedError + + @abstractmethod + async def open(self) -> None: + """Optional one-time initialization for long-lived resources. + + Implementations may open network/browser sessions or warm caches here. + This method will be called by the runtime when available; callers may + ignore if not needed. + """ + pass + + @abstractmethod + async def close(self) -> None: + """Optional cleanup for resources allocated in `open()`. + + Called by the runtime during shutdown to release resources (e.g., close + browser, stop background tasks). Implementations should make this idempotent. + """ + pass diff --git a/python/valuecell/agents/common/trading/features/pipeline.py b/python/valuecell/agents/common/trading/features/pipeline.py index 547fd6ebd..1dadd5f7f 100644 --- a/python/valuecell/agents/common/trading/features/pipeline.py +++ b/python/valuecell/agents/common/trading/features/pipeline.py @@ -9,7 +9,7 @@ from __future__ import annotations import asyncio -import itertools +from pathlib import Path from typing import List, Optional from loguru import logger @@ -23,12 +23,16 @@ from ..data.interfaces import BaseMarketDataSource from ..data.market import SimpleMarketDataSource +from ..data.screenshot import AggrScreenshotDataSource, PlaywrightScreenshotDataSource +from ..models import InstrumentRef from .candle import SimpleCandleFeatureComputer +from .image import MLLMImageFeatureComputer from .interfaces import ( BaseFeaturesPipeline, CandleBasedFeatureComputer, ) from .market_snapshot import MarketSnapshotFeatureComputer +from .prompts import AGGR_PROMPT class DefaultFeaturesPipeline(BaseFeaturesPipeline): @@ -42,18 +46,26 @@ def __init__( candle_feature_computer: CandleBasedFeatureComputer, market_snapshot_computer: MarketSnapshotFeatureComputer, candle_configurations: Optional[List[CandleConfig]] = None, + screenshot_data_source: Optional[PlaywrightScreenshotDataSource] = None, + image_feature_computer: Optional[MLLMImageFeatureComputer] = None, ) -> None: self._request = request self._market_data_source = market_data_source self._candle_feature_computer = candle_feature_computer self._symbols = list(dict.fromkeys(request.trading_config.symbols)) self._market_snapshot_computer = market_snapshot_computer - self._candle_configurations = candle_configurations + self._screenshot_data_source = screenshot_data_source + self._image_feature_computer = image_feature_computer self._candle_configurations = candle_configurations or [ - CandleConfig(interval="1s", lookback=60 * 3), CandleConfig(interval="1m", lookback=60 * 4), ] + async def open(self) -> None: + """Open any long-lived resources needed by the pipeline.""" + + if self._screenshot_data_source is not None: + await self._screenshot_data_source.open() + async def build(self) -> FeaturesPipelineResult: """ Fetch candles and market snapshot, compute feature vectors concurrently, @@ -80,27 +92,66 @@ async def _fetch_market_features() -> List[FeatureVector]: logger.info( f"Starting concurrent data fetching for {len(self._candle_configurations)} candle sets and markets snapshot..." ) - tasks = [ - _fetch_candles(config.interval, config.lookback) - for config in self._candle_configurations - ] - tasks.append(_fetch_market_features()) - # results = [ [candle_features_1], [candle_features_2], ..., [market_features] ] - results = await asyncio.gather(*tasks) + # Create named tasks so we don't depend on result ordering. + tasks_map: dict[str, asyncio.Task] = {} + + for idx, config in enumerate(self._candle_configurations): + name = f"candles:{config.interval}:{idx}" + coro = _fetch_candles(config.interval, config.lookback) + tasks_map[name] = asyncio.create_task(coro) + + # market snapshot task + tasks_map["market"] = asyncio.create_task(_fetch_market_features()) + + # Optionally fetch and compute image-based features if providers are set + if ( + self._screenshot_data_source is not None + and self._image_feature_computer is not None + ): + + async def _fetch_image_features() -> List[FeatureVector]: + try: + images = await self._screenshot_data_source.capture() + return await self._image_feature_computer.compute_features( + images=images + ) + except Exception as e: + logger.error(f"Failed to capture screenshot: {e}") + return [] + + tasks_map["image"] = asyncio.create_task(_fetch_image_features()) + + # Await all tasks and then collect results by name + await asyncio.gather(*tasks_map.values()) logger.info("Concurrent data fetching complete.") - market_features: List[FeatureVector] = results.pop() + results_map: dict[str, List[FeatureVector]] = { + name: task.result() for name, task in tasks_map.items() + } - # Flatten the list of lists of candle features - candle_features: List[FeatureVector] = list( - itertools.chain.from_iterable(results) - ) + # Flatten candle features from all candle tasks + candle_features: List[FeatureVector] = [] + for name, feats in results_map.items(): + if name.startswith("candles:"): + candle_features.extend(feats) + + # Append market features if available + market_features: List[FeatureVector] = results_map.get("market", []) + + # Append image-derived features if available + image_features: List[FeatureVector] = results_map.get("image", []) candle_features.extend(market_features) + candle_features.extend(image_features) return FeaturesPipelineResult(features=candle_features) + async def close(self) -> None: + """Close any long-lived resources created by the pipeline.""" + if self._screenshot_data_source is not None: + await self._screenshot_data_source.close() + @classmethod def from_request(cls, request: UserRequest) -> DefaultFeaturesPipeline: """Factory creating the default pipeline from a user request.""" @@ -109,9 +160,35 @@ def from_request(cls, request: UserRequest) -> DefaultFeaturesPipeline: ) candle_feature_computer = SimpleCandleFeatureComputer() market_snapshot_computer = MarketSnapshotFeatureComputer() + + try: + image_feature_computer = MLLMImageFeatureComputer.from_request( + request, prompt=AGGR_PROMPT + ) + charts_json = ( + Path(__file__).parent.parent + / "data" + / "configs" + / "aggr" + / "charts.json" + ) + screenshot_data_source = AggrScreenshotDataSource( + target_url="https://aggr.trade", + file_path=str(charts_json), + instrument=InstrumentRef(symbol="BTCUSD"), + ) + except Exception as e: + logger.warning( + f"Image feature computer could not be initialized: {e}. Proceeding without image features." + ) + image_feature_computer = None + screenshot_data_source = None + return cls( request=request, market_data_source=market_data_source, candle_feature_computer=candle_feature_computer, market_snapshot_computer=market_snapshot_computer, + image_feature_computer=image_feature_computer, + screenshot_data_source=screenshot_data_source, ) diff --git a/python/valuecell/agents/common/trading/features/prompts.py b/python/valuecell/agents/common/trading/features/prompts.py new file mode 100644 index 000000000..8ae3ad233 --- /dev/null +++ b/python/valuecell/agents/common/trading/features/prompts.py @@ -0,0 +1,75 @@ +AGGR_PROMPT: str = """ +# Role +You are an expert High-Frequency Trader (HFT) and Order Flow Analyst specializing in crypto market microstructure. You are analyzing a dashboard from Aggr.trade. + +# Visual Context +The image displays three vertical panes: +1. **Top (Price & Global CVD):** 5s candles, Aggregate Volume, Liquidations (bright bars), and Global CVD line. +2. **Middle (Delta Grid):** Net Delta per exchange/pair (5m timeframe). Key: Spot (S) vs. Perps (P). +3. **Bottom (Exchange CVDs):** Cumulative Volume Delta lines for individual exchanges (15m timeframe). + * *Legend Assumption:* Cyan/Blue = Coinbase (Spot); Yellow/Red = Binance (Spot/Perps). + +# Analysis Objectives +Please analyze the order flow dynamics and provide a scalping strategy based on the following: + +1. **Spot vs. Perp Dynamics:** + * Is the price action driven by Spot demand (e.g., Coinbase buying) or Perp speculation? + * Identify any **"Spot Premium"** or **"Perp Discount"** behavior. + +2. **Absorption & Divergences (CRITICAL):** + * Look for **"Passive Absorption"**: Are we seeing aggressive selling (Red Delta/CVD) resulting in stable or rising prices? + * Look for **"CVD Divergences"**: Is Price making Higher Highs while Global/Binance CVD makes Lower Highs? + +3. **Exchange Specific Flows:** + * Compare **Coinbase Spot (Smart Money)** vs. **Binance Perps (Retail/Speculative)**. Are they correlated or fighting each other? + +# Output Format +Provide a concise professional report: +* **Market State:** (e.g., Spot-Led Grind, Short Squeeze, Liquidation Cascade) +* **Key Observation:** (One sentence on the most critical anomaly, e.g., "Coinbase bidding while Binance dumps.") +* **Trade Setup:** + * **Bias:** [LONG / SHORT / NEUTRAL] + * **Entry Trigger:** (e.g., "Enter on retest of VWAP with absorption.") + * **Invalidation:** (Where does the thesis fail?) +""" + +TRADINGVIEW_WIDGET_PROMPT: str = """ +# Role +You are an expert Technical Analyst and Swing Trader specializing in Price Action and Market Structure. You are analyzing a chart screenshot from a TradingView Widget. + +# Visual Context +The image displays a standard candlestick chart (likely Crypto or Stock asset). +1. **Price Action:** Candlesticks showing Open, High, Low, Close data. +2. **Volume:** Vertical bars at the bottom representing trading activity. +3. **Timeframe:** Identify the timeframe from the top left (e.g., 1D, 4H, 15m). +4. **Asset:** Identify the ticker symbol (e.g., DOGE/USDT, BTC/USD). + +# Analysis Objectives +Please analyze the chart structure and provide a trading plan based on the following pillars: + +1. **Market Structure & Trend Identification:** + * Determine the **Current Trend**: Is the market in an Uptrend (Higher Highs/Higher Lows), Downtrend (Lower Highs/Lower Lows), or Consolidation (Ranging)? + * Identify the **Market Phase**: Accumulation, Markup, Distribution, or Decline? + +2. **Key Levels (Support & Resistance):** + * Identify major **Horizontal Levels**: Where has price historically bounced or rejected? + * Identify **Supply/Demand Zones**: Look for areas of explosive moves that price is revisiting. + * *Optional:* Note any visible trendlines or chart patterns (e.g., Head & Shoulders, Flags, Wedges). + +3. **Volume & Candlestick Analysis (VSA):** + * **Volume Anomalies:** Is there high volume at lows (Stopping Volume) or high volume at highs (Selling Pressure)? + * **Candlestick Signals:** Identify specific patterns on the most recent candles (e.g., Pin Bar, Engulfing, Doji) that suggest reversal or continuation. + * **Momentum:** Are the candles getting larger (increasing momentum) or smaller (loss of momentum)? + +# Output Format +Provide a concise, actionable trading report: + +* **Market Context:** (e.g., "DOGE is consolidating at daily support after a 30% correction.") +* **Technical Signal:** (One sentence on the most important technical factor, e.g., "Bullish Engulfing candle formed on high volume at the 0.14 support level.") +* **Trade Setup:** + * **Bias:** [BULLISH / BEARISH / NEUTRAL] + * **Key Level:** (The price level that matters most right now). + * **Action Plan:** (e.g., "Wait for a daily close above 0.16 to confirm reversal," or "Short on rejection of 0.18.") + * **Invalidation:** (A price level that proves this thesis wrong, e.g., "Close below 0.13".) + +""" diff --git a/python/valuecell/agents/common/trading/models.py b/python/valuecell/agents/common/trading/models.py index 983123674..bfc7b4ef4 100644 --- a/python/valuecell/agents/common/trading/models.py +++ b/python/valuecell/agents/common/trading/models.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from enum import Enum +from pathlib import Path from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field, field_validator, model_validator @@ -384,7 +385,7 @@ class FeatureVector(BaseModel): ..., description="Feature vector timestamp in ms", ) - instrument: InstrumentRef + instrument: Optional[InstrumentRef] values: Dict[CommonKeyType, CommonValueType | List[CommonValueType]] = Field( default_factory=dict, description="Feature name to numeric value" ) @@ -947,3 +948,15 @@ class DecisionCycleResult: history_records: List[HistoryRecord] digest: TradeDigest portfolio_view: PortfolioView + + +@dataclass +class DataSourceImage: + url: Optional[str] = None # Remote location + filepath: Optional[Path | str] = None # Local file path + content: Optional[bytes] = None # Raw image bytes (standardized to bytes) + + instrument: Optional[InstrumentRef] = None # Associated instrument, if any + + def __repr__(self): + return f"DataSourceImage(url={self.url}, filepath={self.filepath}, content={'' if self.content else None}, instrument={self.instrument})" diff --git a/python/valuecell/agents/common/trading/utils.py b/python/valuecell/agents/common/trading/utils.py index 6742cead2..f45c74d89 100644 --- a/python/valuecell/agents/common/trading/utils.py +++ b/python/valuecell/agents/common/trading/utils.py @@ -182,6 +182,40 @@ def normalize_symbol(symbol: str) -> str: return base_symbol +def normalize_symbol_tradingview(symbol: str) -> str: + """Normalize symbol format for TradingView widgets. + + Rules implemented: + - If the input contains a colon ("A/B:B" style), take the portion + to the left of the first colon, remove separators (`-` and `/`), + convert to upper-case and append the suffix `.P`. + Example: `BTC-USDT:USDT` or `BTC/USDT:PERP` -> `BTCUSDT.P`. + - Otherwise remove separators (`-` and `/`) and convert to upper-case. + Examples: `BTC-USD` -> `BTCUSD`, `ETH/USDT` -> `ETHUSDT`. + + Args: + symbol: Input symbol string (e.g., 'BTC-USD', 'BTC/USDT', 'BTC-USDT:USDT', 'BTC/USDT:PERP'). + + Returns: + Normalized TradingView symbol string in upper-case. The `.P` suffix + is used for inputs that include a colon to indicate the right-hand + side (perpetual/venue) was present and was stripped from the output. + """ + if not symbol: + return symbol + + s = str(symbol).upper() + + # If a colon is present, keep the left side, strip separators, and append .P + if ":" in s: + left, _ = s.split(":", 1) + left_clean = left.replace("-", "").replace("/", "") + return f"{left_clean}.P" + + # Otherwise remove common separators and return uppercase + return s.replace("-", "").replace("/", "") + + def get_exchange_cls(exchange_id: str): """Get CCXT exchange class by exchange ID.""" diff --git a/python/valuecell/utils/model.py b/python/valuecell/utils/model.py index 4e9e19bd5..a24a7c651 100644 --- a/python/valuecell/utils/model.py +++ b/python/valuecell/utils/model.py @@ -327,6 +327,44 @@ def create_model_with_provider( return provider_instance.create_model(model_id, **kwargs) +def supports_model_images(provider: str, model_id: Optional[str]) -> bool: + """Return True if the given provider+model_id supports image inputs. + + This inspects the provider YAML loaded by `ConfigManager` and looks for a + `supported_inputs` entry on the matching model definition. If `model_id` is + None the provider's `default_model` is used. The check is case-insensitive + and tolerant of missing fields. + """ + try: + from valuecell.config.manager import get_config_manager + + cfg_mgr = get_config_manager() + prov = cfg_mgr.get_provider_config(provider) + if not prov: + return False + + models = prov.models or [] + target_id = model_id or prov.default_model + + for m in models: + mid = m.get("id") or m.get("name") or "" + if not mid: + continue + if target_id and target_id != "" and mid != target_id: + # continue searching for exact match + continue + inputs = m.get("supported_inputs") or [] + # normalize to lower-case + if any(str(i).lower() == "images" for i in inputs): + return True + return False + except Exception: + logger.debug( + "supports_model_images: failed to read provider config, assuming False" + ) + return False + + # ============================================ # Embedding Functions # ============================================ diff --git a/python/valuecell/utils/path.py b/python/valuecell/utils/path.py index 26a6cb045..7b90d5f44 100644 --- a/python/valuecell/utils/path.py +++ b/python/valuecell/utils/path.py @@ -88,3 +88,15 @@ def get_knowledge_path() -> str: pass return str(new_path) + + +def get_screenshot_path() -> str: + """ + Returns the path to the screenshots directory located in the system application directory. + + Returns: + str: Absolute path of the screenshots directory + """ + screenshot_path = Path(get_system_env_dir()) / "screenshots" + screenshot_path.mkdir(parents=True, exist_ok=True) + return str(screenshot_path)