opentradex 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/README.md +3 -1
- package/gossip/__pycache__/polymarket.cpython-314.pyc +0 -0
- package/main.py +38 -7
- package/package.json +1 -1
- package/src/catalog.mjs +76 -4
- package/src/cli.mjs +44 -18
- package/src/index.mjs +433 -19
- package/web/next-env.d.ts +1 -1
- package/web/src/app/api/workspace/route.ts +12 -0
- package/web/src/app/globals.css +28 -0
- package/web/src/app/guide/page.tsx +262 -0
- package/web/src/app/layout.tsx +2 -1
- package/web/src/app/page.tsx +15 -0
- package/web/src/components/DashboardApp.tsx +192 -88
- package/web/src/components/HarnessBootPanel.tsx +160 -0
- package/web/src/components/LiveStream.tsx +632 -255
- package/web/src/components/TopBar.tsx +135 -82
- package/web/src/lib/demo-data.ts +25 -0
- package/web/src/lib/trading-guide-content.ts +337 -0
- package/web/src/lib/types.ts +30 -0
- package/web/src/lib/workspace.ts +117 -0
package/src/index.mjs
CHANGED
|
@@ -6,10 +6,14 @@ import process from "node:process";
|
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import {
|
|
9
|
+
CHANNEL_OPTIONS,
|
|
10
|
+
DASHBOARD_SURFACE_OPTIONS,
|
|
9
11
|
INTEGRATION_OPTIONS,
|
|
12
|
+
MCP_TRANSPORT_OPTIONS,
|
|
10
13
|
MARKET_OPTIONS,
|
|
11
14
|
PACKAGE_MANAGER_OPTIONS,
|
|
12
15
|
RUNTIME_OPTIONS,
|
|
16
|
+
TRADINGVIEW_CONNECTOR_OPTIONS,
|
|
13
17
|
formatOptionLines,
|
|
14
18
|
getOptionById,
|
|
15
19
|
keepKnownIds,
|
|
@@ -49,6 +53,8 @@ const DEFAULT_ENV = {
|
|
|
49
53
|
OPENTRADEX_ENABLED_MARKETS: "kalshi",
|
|
50
54
|
OPENTRADEX_ENABLED_INTEGRATIONS: "apify,rss",
|
|
51
55
|
OPENTRADEX_LIVE_EXECUTION_MARKET: "kalshi",
|
|
56
|
+
OPENTRADEX_DASHBOARD_SURFACE: "chat",
|
|
57
|
+
OPENTRADEX_CHANNELS: "command,markets,feeds,risk,execution",
|
|
52
58
|
KALSHI_API_KEY_ID: "",
|
|
53
59
|
KALSHI_PRIVATE_KEY_PATH: "",
|
|
54
60
|
KALSHI_USE_DEMO: "true",
|
|
@@ -58,6 +64,12 @@ const DEFAULT_ENV = {
|
|
|
58
64
|
TRADINGVIEW_USERNAME: "",
|
|
59
65
|
TRADINGVIEW_PASSWORD: "",
|
|
60
66
|
TRADINGVIEW_WATCHLIST: "SPY,QQQ,BTCUSD,NQ1!",
|
|
67
|
+
TRADINGVIEW_CONNECTOR_MODE: "watchlist",
|
|
68
|
+
TRADINGVIEW_MCP_ENABLED: "false",
|
|
69
|
+
TRADINGVIEW_MCP_TRANSPORT: "stdio",
|
|
70
|
+
TRADINGVIEW_MCP_COMMAND: "",
|
|
71
|
+
TRADINGVIEW_MCP_ARGS: "",
|
|
72
|
+
TRADINGVIEW_MCP_URL: "",
|
|
61
73
|
ROBINHOOD_USERNAME: "",
|
|
62
74
|
ROBINHOOD_PASSWORD: "",
|
|
63
75
|
ROBINHOOD_MFA_CODE: "",
|
|
@@ -111,6 +123,11 @@ export function writeWorkspaceProfile(workspace, profile) {
|
|
|
111
123
|
primaryMarket: profile.primaryMarket,
|
|
112
124
|
enabledMarkets: uniqueIds(profile.enabledMarkets),
|
|
113
125
|
integrations: uniqueIds(profile.integrations),
|
|
126
|
+
dashboardSurface: profile.dashboardSurface,
|
|
127
|
+
channels: uniqueIds(profile.channels),
|
|
128
|
+
tradingviewConnectorMode: profile.tradingviewConnectorMode,
|
|
129
|
+
tradingviewMcpEnabled: Boolean(profile.tradingviewMcpEnabled),
|
|
130
|
+
tradingviewMcpTransport: profile.tradingviewMcpTransport,
|
|
114
131
|
bankroll: Number(profile.bankroll).toFixed(2),
|
|
115
132
|
interval: Number(profile.interval),
|
|
116
133
|
installDeps: Boolean(profile.installDeps),
|
|
@@ -179,6 +196,8 @@ export function writeEnvFile(workspace, overrides = {}) {
|
|
|
179
196
|
"OPENTRADEX_ENABLED_MARKETS",
|
|
180
197
|
"OPENTRADEX_ENABLED_INTEGRATIONS",
|
|
181
198
|
"OPENTRADEX_LIVE_EXECUTION_MARKET",
|
|
199
|
+
"OPENTRADEX_DASHBOARD_SURFACE",
|
|
200
|
+
"OPENTRADEX_CHANNELS",
|
|
182
201
|
"NEWS_PROVIDER",
|
|
183
202
|
],
|
|
184
203
|
},
|
|
@@ -214,6 +233,12 @@ export function writeEnvFile(workspace, overrides = {}) {
|
|
|
214
233
|
"TRADINGVIEW_USERNAME",
|
|
215
234
|
"TRADINGVIEW_PASSWORD",
|
|
216
235
|
"TRADINGVIEW_WATCHLIST",
|
|
236
|
+
"TRADINGVIEW_CONNECTOR_MODE",
|
|
237
|
+
"TRADINGVIEW_MCP_ENABLED",
|
|
238
|
+
"TRADINGVIEW_MCP_TRANSPORT",
|
|
239
|
+
"TRADINGVIEW_MCP_COMMAND",
|
|
240
|
+
"TRADINGVIEW_MCP_ARGS",
|
|
241
|
+
"TRADINGVIEW_MCP_URL",
|
|
217
242
|
],
|
|
218
243
|
},
|
|
219
244
|
{
|
|
@@ -293,6 +318,11 @@ export function collectDoctor(workspaceArg) {
|
|
|
293
318
|
const runtime = config.runtime || env.OPENTRADEX_RUNTIME || "claude-code";
|
|
294
319
|
const packageManager = config.packageManager || env.OPENTRADEX_PACKAGE_MANAGER || "npm";
|
|
295
320
|
const primaryMarket = config.primaryMarket || env.OPENTRADEX_PRIMARY_MARKET || "kalshi";
|
|
321
|
+
const dashboardSurface = config.dashboardSurface || env.OPENTRADEX_DASHBOARD_SURFACE || DEFAULT_ENV.OPENTRADEX_DASHBOARD_SURFACE;
|
|
322
|
+
const channels = keepKnownIds(
|
|
323
|
+
CHANNEL_OPTIONS,
|
|
324
|
+
config.channels?.length ? config.channels : env.OPENTRADEX_CHANNELS || DEFAULT_ENV.OPENTRADEX_CHANNELS
|
|
325
|
+
);
|
|
296
326
|
const enabledMarkets = keepKnownIds(
|
|
297
327
|
MARKET_OPTIONS,
|
|
298
328
|
config.enabledMarkets?.length ? config.enabledMarkets : env.OPENTRADEX_ENABLED_MARKETS || primaryMarket
|
|
@@ -301,6 +331,16 @@ export function collectDoctor(workspaceArg) {
|
|
|
301
331
|
INTEGRATION_OPTIONS,
|
|
302
332
|
config.integrations?.length ? config.integrations : env.OPENTRADEX_ENABLED_INTEGRATIONS || "apify,rss"
|
|
303
333
|
);
|
|
334
|
+
const tradingviewConnectorMode =
|
|
335
|
+
String(config.tradingviewConnectorMode || env.TRADINGVIEW_CONNECTOR_MODE || DEFAULT_ENV.TRADINGVIEW_CONNECTOR_MODE).toLowerCase() === "mcp"
|
|
336
|
+
? "mcp"
|
|
337
|
+
: "watchlist";
|
|
338
|
+
const tradingviewMcpEnabled =
|
|
339
|
+
String(config.tradingviewMcpEnabled ?? env.TRADINGVIEW_MCP_ENABLED ?? DEFAULT_ENV.TRADINGVIEW_MCP_ENABLED).toLowerCase() === "true";
|
|
340
|
+
const tradingviewMcpTransport =
|
|
341
|
+
String(config.tradingviewMcpTransport || env.TRADINGVIEW_MCP_TRANSPORT || DEFAULT_ENV.TRADINGVIEW_MCP_TRANSPORT).toLowerCase() === "http"
|
|
342
|
+
? "http"
|
|
343
|
+
: "stdio";
|
|
304
344
|
|
|
305
345
|
const checks = [
|
|
306
346
|
check("Node.js", commandWorks("node", ["-v"]), "Install Node.js 22+."),
|
|
@@ -354,6 +394,17 @@ export function collectDoctor(workspaceArg) {
|
|
|
354
394
|
"Add TRADINGVIEW_WATCHLIST so the agent has symbols to monitor."
|
|
355
395
|
)
|
|
356
396
|
);
|
|
397
|
+
if (tradingviewConnectorMode === "mcp" || tradingviewMcpEnabled) {
|
|
398
|
+
checks.push(
|
|
399
|
+
check(
|
|
400
|
+
"TradingView MCP",
|
|
401
|
+
tradingviewMcpTransport === "http" ? Boolean(env.TRADINGVIEW_MCP_URL) : Boolean(env.TRADINGVIEW_MCP_COMMAND),
|
|
402
|
+
tradingviewMcpTransport === "http"
|
|
403
|
+
? "Set TRADINGVIEW_MCP_URL to your TradingView MCP endpoint."
|
|
404
|
+
: "Set TRADINGVIEW_MCP_COMMAND and optional TRADINGVIEW_MCP_ARGS for your local TradingView MCP server."
|
|
405
|
+
)
|
|
406
|
+
);
|
|
407
|
+
}
|
|
357
408
|
}
|
|
358
409
|
|
|
359
410
|
if (integrations.includes("apify") || integrations.includes("twitter") || integrations.includes("tiktok")) {
|
|
@@ -365,13 +416,28 @@ export function collectDoctor(workspaceArg) {
|
|
|
365
416
|
profile: {
|
|
366
417
|
runtime,
|
|
367
418
|
packageManager,
|
|
419
|
+
dashboardSurface,
|
|
420
|
+
channels,
|
|
368
421
|
mode: liveTrading ? "live" : config.mode || env.OPENTRADEX_EXECUTION_MODE || "paper",
|
|
369
422
|
primaryMarket,
|
|
370
423
|
enabledMarkets,
|
|
371
424
|
integrations,
|
|
425
|
+
tradingviewConnectorMode,
|
|
426
|
+
tradingviewMcpEnabled,
|
|
427
|
+
tradingviewMcpTransport,
|
|
372
428
|
},
|
|
373
429
|
checks,
|
|
374
|
-
notes: buildDoctorNotes({
|
|
430
|
+
notes: buildDoctorNotes({
|
|
431
|
+
runtime,
|
|
432
|
+
primaryMarket,
|
|
433
|
+
enabledMarkets,
|
|
434
|
+
liveTrading,
|
|
435
|
+
dashboardSurface,
|
|
436
|
+
channels,
|
|
437
|
+
tradingviewConnectorMode,
|
|
438
|
+
tradingviewMcpEnabled,
|
|
439
|
+
tradingviewMcpTransport,
|
|
440
|
+
}),
|
|
375
441
|
};
|
|
376
442
|
}
|
|
377
443
|
|
|
@@ -384,6 +450,15 @@ export async function onboard(options = {}) {
|
|
|
384
450
|
try {
|
|
385
451
|
printWelcomeBanner();
|
|
386
452
|
|
|
453
|
+
if (interactive) {
|
|
454
|
+
printSecurityNotice();
|
|
455
|
+
const accepted = await confirm(rl, "I understand this is powerful and inherently risky. Continue?", false);
|
|
456
|
+
if (!accepted) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
printSection("1/7", "workspace", "Choose where OpenTradex should create or reuse the local harness.");
|
|
460
|
+
}
|
|
461
|
+
|
|
387
462
|
const workspace = path.resolve(
|
|
388
463
|
options.workspace ||
|
|
389
464
|
(interactive
|
|
@@ -396,11 +471,20 @@ export async function onboard(options = {}) {
|
|
|
396
471
|
...saved,
|
|
397
472
|
...readWorkspaceProfile(workspace),
|
|
398
473
|
});
|
|
474
|
+
const workspaceExists = existsSync(path.join(workspace, "main.py"));
|
|
475
|
+
|
|
476
|
+
if (interactive && workspaceExists && !(await confirm(rl, `Reuse existing workspace at ${workspace}`, true))) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (interactive) {
|
|
481
|
+
printSection("2/7", "runtime", "Pick the operator runtime and package flow for this machine.");
|
|
482
|
+
}
|
|
399
483
|
|
|
400
484
|
const runtime = interactive
|
|
401
485
|
? await chooseOption(
|
|
402
486
|
rl,
|
|
403
|
-
"
|
|
487
|
+
"Select operator runtime",
|
|
404
488
|
RUNTIME_OPTIONS,
|
|
405
489
|
options.llm || options.runtime || savedProfile.runtime || currentEnv.OPENTRADEX_RUNTIME || "claude-code"
|
|
406
490
|
)
|
|
@@ -409,7 +493,7 @@ export async function onboard(options = {}) {
|
|
|
409
493
|
const packageManager = interactive
|
|
410
494
|
? await chooseOption(
|
|
411
495
|
rl,
|
|
412
|
-
"
|
|
496
|
+
"Select package manager",
|
|
413
497
|
PACKAGE_MANAGER_OPTIONS,
|
|
414
498
|
options.packageManager || savedProfile.packageManager || currentEnv.OPENTRADEX_PACKAGE_MANAGER || "npm"
|
|
415
499
|
)
|
|
@@ -419,10 +503,14 @@ export async function onboard(options = {}) {
|
|
|
419
503
|
"npm"
|
|
420
504
|
).id;
|
|
421
505
|
|
|
506
|
+
if (interactive) {
|
|
507
|
+
printSection("3/7", "market rails", "Pick the execution rail first, then enable any additional discovery or watchlist rails.");
|
|
508
|
+
}
|
|
509
|
+
|
|
422
510
|
const primaryMarket = interactive
|
|
423
511
|
? await chooseOption(
|
|
424
512
|
rl,
|
|
425
|
-
"
|
|
513
|
+
"Select primary market",
|
|
426
514
|
MARKET_OPTIONS,
|
|
427
515
|
options.primaryMarket || options.market || savedProfile.primaryMarket || currentEnv.OPENTRADEX_PRIMARY_MARKET || "kalshi"
|
|
428
516
|
)
|
|
@@ -442,7 +530,7 @@ export async function onboard(options = {}) {
|
|
|
442
530
|
const extraMarkets = interactive
|
|
443
531
|
? await chooseMany(
|
|
444
532
|
rl,
|
|
445
|
-
"Enable
|
|
533
|
+
"Enable additional market rails",
|
|
446
534
|
MARKET_OPTIONS.filter((item) => item.id !== primaryMarket),
|
|
447
535
|
defaultMarkets
|
|
448
536
|
)
|
|
@@ -450,15 +538,53 @@ export async function onboard(options = {}) {
|
|
|
450
538
|
|
|
451
539
|
const enabledMarkets = uniqueIds([primaryMarket, ...extraMarkets]);
|
|
452
540
|
|
|
541
|
+
const availableChannels = enabledMarkets.includes("tradingview")
|
|
542
|
+
? CHANNEL_OPTIONS
|
|
543
|
+
: CHANNEL_OPTIONS.filter((item) => item.id !== "tradingview");
|
|
544
|
+
|
|
545
|
+
if (interactive) {
|
|
546
|
+
printSection("4/7", "operator channels", "Choose the dashboard surface and the messaging lanes your operator cockpit should keep online.");
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const dashboardSurface = interactive
|
|
550
|
+
? await chooseOption(
|
|
551
|
+
rl,
|
|
552
|
+
"Select dashboard surface",
|
|
553
|
+
DASHBOARD_SURFACE_OPTIONS,
|
|
554
|
+
options.dashboardSurface || savedProfile.dashboardSurface || currentEnv.OPENTRADEX_DASHBOARD_SURFACE || DEFAULT_ENV.OPENTRADEX_DASHBOARD_SURFACE
|
|
555
|
+
)
|
|
556
|
+
: getOptionById(
|
|
557
|
+
DASHBOARD_SURFACE_OPTIONS,
|
|
558
|
+
options.dashboardSurface || savedProfile.dashboardSurface || currentEnv.OPENTRADEX_DASHBOARD_SURFACE,
|
|
559
|
+
DEFAULT_ENV.OPENTRADEX_DASHBOARD_SURFACE
|
|
560
|
+
).id;
|
|
561
|
+
|
|
562
|
+
const defaultChannels = keepKnownIds(
|
|
563
|
+
availableChannels,
|
|
564
|
+
options.channels || savedProfile.channels || currentEnv.OPENTRADEX_CHANNELS || DEFAULT_ENV.OPENTRADEX_CHANNELS
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
const channels = interactive
|
|
568
|
+
? await chooseMany(rl, "Enable operator channels", availableChannels, defaultChannels)
|
|
569
|
+
: defaultChannels;
|
|
570
|
+
|
|
453
571
|
const defaultIntegrations = keepKnownIds(
|
|
454
572
|
INTEGRATION_OPTIONS,
|
|
455
573
|
options.integrations || savedProfile.integrations || currentEnv.OPENTRADEX_ENABLED_INTEGRATIONS || "apify,rss"
|
|
456
574
|
);
|
|
457
575
|
|
|
576
|
+
if (interactive) {
|
|
577
|
+
printSection("5/7", "data feeds", "Enable only the context feeds you want. Optional integrations stay optional.");
|
|
578
|
+
}
|
|
579
|
+
|
|
458
580
|
const integrations = interactive
|
|
459
581
|
? await chooseMany(rl, "Enable optional data integrations", INTEGRATION_OPTIONS, defaultIntegrations)
|
|
460
582
|
: defaultIntegrations;
|
|
461
583
|
|
|
584
|
+
if (interactive) {
|
|
585
|
+
printSection("6/7", "guardrails", "Set trading mode, bankroll, and loop interval before anything gets close to execution.");
|
|
586
|
+
}
|
|
587
|
+
|
|
462
588
|
const mode =
|
|
463
589
|
options.live ? "live" : options.paper ? "paper" : interactive ? await chooseMode(rl, savedProfile.mode || "paper") : savedProfile.mode || "paper";
|
|
464
590
|
|
|
@@ -472,8 +598,9 @@ export async function onboard(options = {}) {
|
|
|
472
598
|
? await ask(rl, "Loop interval in seconds", String(savedProfile.interval || DEFAULT_ENV.CYCLE_INTERVAL))
|
|
473
599
|
: savedProfile.interval || DEFAULT_ENV.CYCLE_INTERVAL);
|
|
474
600
|
|
|
475
|
-
|
|
476
|
-
|
|
601
|
+
if (interactive) {
|
|
602
|
+
printSection("7/7", "credentials", "Enter only the keys and watchlists you need. Blank values stay blank unless you enable live Kalshi.");
|
|
603
|
+
}
|
|
477
604
|
|
|
478
605
|
const apifyToken =
|
|
479
606
|
options.apifyToken ??
|
|
@@ -519,6 +646,80 @@ export async function onboard(options = {}) {
|
|
|
519
646
|
: currentEnv.TRADINGVIEW_WATCHLIST || DEFAULT_ENV.TRADINGVIEW_WATCHLIST)
|
|
520
647
|
: currentEnv.TRADINGVIEW_WATCHLIST || DEFAULT_ENV.TRADINGVIEW_WATCHLIST;
|
|
521
648
|
|
|
649
|
+
const tradingviewConnectorMode =
|
|
650
|
+
enabledMarkets.includes("tradingview")
|
|
651
|
+
? interactive
|
|
652
|
+
? await chooseOption(
|
|
653
|
+
rl,
|
|
654
|
+
"TradingView connector mode",
|
|
655
|
+
TRADINGVIEW_CONNECTOR_OPTIONS,
|
|
656
|
+
options.tradingviewConnectorMode ||
|
|
657
|
+
savedProfile.tradingviewConnectorMode ||
|
|
658
|
+
currentEnv.TRADINGVIEW_CONNECTOR_MODE ||
|
|
659
|
+
DEFAULT_ENV.TRADINGVIEW_CONNECTOR_MODE
|
|
660
|
+
)
|
|
661
|
+
: getOptionById(
|
|
662
|
+
TRADINGVIEW_CONNECTOR_OPTIONS,
|
|
663
|
+
options.tradingviewConnectorMode ||
|
|
664
|
+
savedProfile.tradingviewConnectorMode ||
|
|
665
|
+
currentEnv.TRADINGVIEW_CONNECTOR_MODE,
|
|
666
|
+
DEFAULT_ENV.TRADINGVIEW_CONNECTOR_MODE
|
|
667
|
+
).id
|
|
668
|
+
: currentEnv.TRADINGVIEW_CONNECTOR_MODE || DEFAULT_ENV.TRADINGVIEW_CONNECTOR_MODE;
|
|
669
|
+
|
|
670
|
+
const tradingviewMcpTransport =
|
|
671
|
+
enabledMarkets.includes("tradingview") && tradingviewConnectorMode === "mcp"
|
|
672
|
+
? interactive
|
|
673
|
+
? await chooseOption(
|
|
674
|
+
rl,
|
|
675
|
+
"TradingView MCP transport",
|
|
676
|
+
MCP_TRANSPORT_OPTIONS,
|
|
677
|
+
options.tradingviewMcpTransport ||
|
|
678
|
+
savedProfile.tradingviewMcpTransport ||
|
|
679
|
+
currentEnv.TRADINGVIEW_MCP_TRANSPORT ||
|
|
680
|
+
DEFAULT_ENV.TRADINGVIEW_MCP_TRANSPORT
|
|
681
|
+
)
|
|
682
|
+
: getOptionById(
|
|
683
|
+
MCP_TRANSPORT_OPTIONS,
|
|
684
|
+
options.tradingviewMcpTransport ||
|
|
685
|
+
savedProfile.tradingviewMcpTransport ||
|
|
686
|
+
currentEnv.TRADINGVIEW_MCP_TRANSPORT,
|
|
687
|
+
DEFAULT_ENV.TRADINGVIEW_MCP_TRANSPORT
|
|
688
|
+
).id
|
|
689
|
+
: currentEnv.TRADINGVIEW_MCP_TRANSPORT || DEFAULT_ENV.TRADINGVIEW_MCP_TRANSPORT;
|
|
690
|
+
|
|
691
|
+
const tradingviewMcpEnabled =
|
|
692
|
+
enabledMarkets.includes("tradingview") && tradingviewConnectorMode === "mcp"
|
|
693
|
+
? options.tradingviewMcpEnabled ??
|
|
694
|
+
(interactive
|
|
695
|
+
? await confirm(rl, "Enable TradingView MCP for this workspace", true)
|
|
696
|
+
: String(savedProfile.tradingviewMcpEnabled ?? currentEnv.TRADINGVIEW_MCP_ENABLED ?? "true").toLowerCase() === "true")
|
|
697
|
+
: false;
|
|
698
|
+
|
|
699
|
+
const tradingviewMcpCommand =
|
|
700
|
+
tradingviewMcpEnabled && tradingviewMcpTransport === "stdio"
|
|
701
|
+
? options.tradingviewMcpCommand ??
|
|
702
|
+
(interactive
|
|
703
|
+
? await ask(rl, "TradingView MCP command", currentEnv.TRADINGVIEW_MCP_COMMAND || "")
|
|
704
|
+
: currentEnv.TRADINGVIEW_MCP_COMMAND || "")
|
|
705
|
+
: currentEnv.TRADINGVIEW_MCP_COMMAND || "";
|
|
706
|
+
|
|
707
|
+
const tradingviewMcpArgs =
|
|
708
|
+
tradingviewMcpEnabled && tradingviewMcpTransport === "stdio"
|
|
709
|
+
? options.tradingviewMcpArgs ??
|
|
710
|
+
(interactive
|
|
711
|
+
? await ask(rl, "TradingView MCP args (optional)", currentEnv.TRADINGVIEW_MCP_ARGS || "")
|
|
712
|
+
: currentEnv.TRADINGVIEW_MCP_ARGS || "")
|
|
713
|
+
: currentEnv.TRADINGVIEW_MCP_ARGS || "";
|
|
714
|
+
|
|
715
|
+
const tradingviewMcpUrl =
|
|
716
|
+
tradingviewMcpEnabled && tradingviewMcpTransport === "http"
|
|
717
|
+
? options.tradingviewMcpUrl ??
|
|
718
|
+
(interactive
|
|
719
|
+
? await ask(rl, "TradingView MCP URL", currentEnv.TRADINGVIEW_MCP_URL || "")
|
|
720
|
+
: currentEnv.TRADINGVIEW_MCP_URL || "")
|
|
721
|
+
: currentEnv.TRADINGVIEW_MCP_URL || "";
|
|
722
|
+
|
|
522
723
|
const robinhoodUsername =
|
|
523
724
|
enabledMarkets.includes("robinhood")
|
|
524
725
|
? options.robinhoodUsername ??
|
|
@@ -543,16 +744,29 @@ export async function onboard(options = {}) {
|
|
|
543
744
|
: currentEnv.GROWW_ACCESS_TOKEN || "")
|
|
544
745
|
: currentEnv.GROWW_ACCESS_TOKEN || "";
|
|
545
746
|
|
|
546
|
-
const workspaceExists = existsSync(path.join(workspace, "main.py"));
|
|
547
|
-
if (interactive && workspaceExists && !(await confirm(rl, `Reuse existing workspace at ${workspace}`, true))) {
|
|
548
|
-
throw new Error("Onboarding cancelled.");
|
|
549
|
-
}
|
|
550
|
-
|
|
551
747
|
if (mode === "live" && primaryMarket !== "kalshi") {
|
|
552
748
|
notes.push("Live execution is currently wired through Kalshi. This workspace has been kept in paper mode while the extra rails stay enabled.");
|
|
553
749
|
}
|
|
554
750
|
|
|
751
|
+
if (dashboardSurface === "chat") {
|
|
752
|
+
notes.push("Dashboard chat cockpit is enabled with operator channels for command, markets, feeds, risk, and execution.");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (enabledMarkets.includes("tradingview") && tradingviewConnectorMode !== "mcp") {
|
|
756
|
+
notes.push("TradingView is configured in watchlist mode. Add an MCP connector later if you want richer chart and symbol context.");
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (tradingviewMcpEnabled && tradingviewMcpTransport === "stdio" && !tradingviewMcpCommand) {
|
|
760
|
+
notes.push("TradingView MCP was enabled without a command. The dashboard will show the connector as incomplete until you fill it in.");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (tradingviewMcpEnabled && tradingviewMcpTransport === "http" && !tradingviewMcpUrl) {
|
|
764
|
+
notes.push("TradingView MCP was enabled without a URL. The dashboard will show the connector as incomplete until you fill it in.");
|
|
765
|
+
}
|
|
766
|
+
|
|
555
767
|
const effectiveMode = mode === "live" && primaryMarket === "kalshi" ? "live" : "paper";
|
|
768
|
+
const installDeps =
|
|
769
|
+
options.install ?? (options.skipInstall ? false : interactive ? await confirm(rl, "Install Python and web dependencies now", true) : true);
|
|
556
770
|
|
|
557
771
|
ensureWorkspaceFiles(workspace, { force: Boolean(options.force) });
|
|
558
772
|
|
|
@@ -564,6 +778,8 @@ export async function onboard(options = {}) {
|
|
|
564
778
|
OPENTRADEX_ENABLED_MARKETS: enabledMarkets.join(","),
|
|
565
779
|
OPENTRADEX_ENABLED_INTEGRATIONS: integrations.join(","),
|
|
566
780
|
OPENTRADEX_LIVE_EXECUTION_MARKET: "kalshi",
|
|
781
|
+
OPENTRADEX_DASHBOARD_SURFACE: dashboardSurface,
|
|
782
|
+
OPENTRADEX_CHANNELS: channels.join(","),
|
|
567
783
|
NEWS_PROVIDER: integrations.join(","),
|
|
568
784
|
APIFY_API_TOKEN: apifyToken,
|
|
569
785
|
BANKROLL: Number(bankroll).toFixed(2),
|
|
@@ -575,6 +791,12 @@ export async function onboard(options = {}) {
|
|
|
575
791
|
POLYMARKET_WALLET_ADDRESS: polymarketWalletAddress,
|
|
576
792
|
POLYMARKET_PRIVATE_KEY: polymarketPrivateKey,
|
|
577
793
|
TRADINGVIEW_WATCHLIST: tradingviewWatchlist,
|
|
794
|
+
TRADINGVIEW_CONNECTOR_MODE: tradingviewConnectorMode,
|
|
795
|
+
TRADINGVIEW_MCP_ENABLED: tradingviewMcpEnabled ? "true" : "false",
|
|
796
|
+
TRADINGVIEW_MCP_TRANSPORT: tradingviewMcpTransport,
|
|
797
|
+
TRADINGVIEW_MCP_COMMAND: tradingviewMcpCommand,
|
|
798
|
+
TRADINGVIEW_MCP_ARGS: tradingviewMcpArgs,
|
|
799
|
+
TRADINGVIEW_MCP_URL: tradingviewMcpUrl,
|
|
578
800
|
ROBINHOOD_USERNAME: robinhoodUsername,
|
|
579
801
|
ROBINHOOD_PASSWORD: robinhoodPassword,
|
|
580
802
|
GROWW_ACCESS_TOKEN: growwAccessToken,
|
|
@@ -588,6 +810,11 @@ export async function onboard(options = {}) {
|
|
|
588
810
|
primaryMarket,
|
|
589
811
|
enabledMarkets,
|
|
590
812
|
integrations,
|
|
813
|
+
dashboardSurface,
|
|
814
|
+
channels,
|
|
815
|
+
tradingviewConnectorMode,
|
|
816
|
+
tradingviewMcpEnabled,
|
|
817
|
+
tradingviewMcpTransport,
|
|
591
818
|
bankroll,
|
|
592
819
|
interval,
|
|
593
820
|
installDeps,
|
|
@@ -603,14 +830,25 @@ export async function onboard(options = {}) {
|
|
|
603
830
|
primaryMarket,
|
|
604
831
|
enabledMarkets,
|
|
605
832
|
integrations,
|
|
833
|
+
dashboardSurface,
|
|
834
|
+
channels,
|
|
835
|
+
tradingviewConnectorMode,
|
|
836
|
+
tradingviewMcpEnabled,
|
|
837
|
+
tradingviewMcpTransport,
|
|
606
838
|
bankroll: Number(bankroll).toFixed(2),
|
|
607
839
|
interval: Number(interval),
|
|
608
840
|
installedAt: new Date().toISOString(),
|
|
609
841
|
});
|
|
610
842
|
|
|
611
843
|
if (installDeps) {
|
|
844
|
+
if (interactive) {
|
|
845
|
+
printSection("BOOT", "local launch", "Installing the requested Python and dashboard dependencies.");
|
|
846
|
+
}
|
|
612
847
|
await runCommand("python", ["-m", "pip", "install", "-r", "requirements.txt"], { cwd: workspace });
|
|
613
848
|
await runPackageInstall(packageManager, path.join(workspace, "web"));
|
|
849
|
+
notes.push("Local dependencies were installed for Python and the dashboard.");
|
|
850
|
+
} else {
|
|
851
|
+
notes.push("Dependency install was skipped. You can still boot later with `pip install -r requirements.txt` and a dashboard package install.");
|
|
614
852
|
}
|
|
615
853
|
|
|
616
854
|
return {
|
|
@@ -671,6 +909,8 @@ export async function runDashboard(options = {}) {
|
|
|
671
909
|
await runPackageInstall(packageManager, webDir);
|
|
672
910
|
}
|
|
673
911
|
|
|
912
|
+
printDashboardBoot({ workspace, packageManager, profile });
|
|
913
|
+
|
|
674
914
|
await runPackageScript(packageManager, "dev", webDir);
|
|
675
915
|
}
|
|
676
916
|
|
|
@@ -680,6 +920,8 @@ export function formatDoctorReport(report) {
|
|
|
680
920
|
`workspace: ${report.workspace}`,
|
|
681
921
|
`runtime: ${report.profile.runtime}`,
|
|
682
922
|
`package manager: ${report.profile.packageManager}`,
|
|
923
|
+
`dashboard: ${report.profile.dashboardSurface}`,
|
|
924
|
+
`channels: ${labelsForIds(CHANNEL_OPTIONS, report.profile.channels).join(", ") || "none"}`,
|
|
683
925
|
`mode: ${report.profile.mode}`,
|
|
684
926
|
`primary market: ${labelsForIds(MARKET_OPTIONS, [report.profile.primaryMarket]).join(", ")}`,
|
|
685
927
|
`market rails: ${labelsForIds(MARKET_OPTIONS, report.profile.enabledMarkets).join(", ") || "none"}`,
|
|
@@ -711,12 +953,18 @@ export function formatProviderMatrix() {
|
|
|
711
953
|
"Agent runtimes:",
|
|
712
954
|
...formatOptionLines(RUNTIME_OPTIONS).map((line) => ` ${line}`),
|
|
713
955
|
"",
|
|
956
|
+
"Dashboard surfaces:",
|
|
957
|
+
...formatOptionLines(DASHBOARD_SURFACE_OPTIONS).map((line) => ` ${line}`),
|
|
958
|
+
"",
|
|
714
959
|
"Market rails:",
|
|
715
960
|
...formatOptionLines(MARKET_OPTIONS).map((line) => ` ${line}`),
|
|
716
961
|
"",
|
|
717
962
|
"Data integrations:",
|
|
718
963
|
...formatOptionLines(INTEGRATION_OPTIONS).map((line) => ` ${line}`),
|
|
719
964
|
"",
|
|
965
|
+
"Operator channels:",
|
|
966
|
+
...formatOptionLines(CHANNEL_OPTIONS).map((line) => ` ${line}`),
|
|
967
|
+
"",
|
|
720
968
|
"Package managers:",
|
|
721
969
|
...formatOptionLines(PACKAGE_MANAGER_OPTIONS).map((line) => ` ${line}`),
|
|
722
970
|
];
|
|
@@ -817,12 +1065,12 @@ async function chooseMode(rl, fallback) {
|
|
|
817
1065
|
|
|
818
1066
|
async function chooseOption(rl, label, options, fallbackId) {
|
|
819
1067
|
const fallback = getOptionById(options, fallbackId, options[0].id);
|
|
820
|
-
|
|
1068
|
+
printOptionList(label);
|
|
821
1069
|
for (const line of formatOptionLines(options)) {
|
|
822
1070
|
console.log(` ${line}`);
|
|
823
1071
|
}
|
|
824
1072
|
|
|
825
|
-
const answer = (await rl.question(`Enter number or id [${fallback.id}]: `)).trim().toLowerCase();
|
|
1073
|
+
const answer = (await rl.question(` Enter number or id [${fallback.id}]: `)).trim().toLowerCase();
|
|
826
1074
|
if (!answer) {
|
|
827
1075
|
return fallback.id;
|
|
828
1076
|
}
|
|
@@ -837,13 +1085,13 @@ async function chooseOption(rl, label, options, fallbackId) {
|
|
|
837
1085
|
|
|
838
1086
|
async function chooseMany(rl, label, options, fallbackIds = []) {
|
|
839
1087
|
const fallback = keepKnownIds(options, fallbackIds);
|
|
840
|
-
|
|
1088
|
+
printOptionList(label);
|
|
841
1089
|
for (const line of formatOptionLines(options)) {
|
|
842
1090
|
console.log(` ${line}`);
|
|
843
1091
|
}
|
|
844
1092
|
|
|
845
1093
|
const fallbackText = fallback.join(",") || "none";
|
|
846
|
-
const answer = (await rl.question(`Enter comma-separated ids or numbers [${fallbackText}]: `)).trim().toLowerCase();
|
|
1094
|
+
const answer = (await rl.question(` Enter comma-separated ids or numbers [${fallbackText}]: `)).trim().toLowerCase();
|
|
847
1095
|
if (!answer) {
|
|
848
1096
|
return fallback;
|
|
849
1097
|
}
|
|
@@ -866,28 +1114,180 @@ async function chooseMany(rl, label, options, fallbackIds = []) {
|
|
|
866
1114
|
function printWelcomeBanner() {
|
|
867
1115
|
console.log([
|
|
868
1116
|
"",
|
|
869
|
-
"
|
|
870
|
-
"
|
|
1117
|
+
" ___ ____ _____ _ _ _____ ____ _ ____ _______ __",
|
|
1118
|
+
" / _ \\| _ \\| ____| \\ | |_ _| _ \\ / \\ | _ \\| ____\\ \\/ /",
|
|
1119
|
+
"| | | | |_) | _| | \\| | | | | |_) |/ _ \\ | | | | _| \\ / ",
|
|
1120
|
+
"| |_| | __/| |___| |\\ | | | | _ </ ___ \\| |_| | |___ / \\ ",
|
|
1121
|
+
" \\___/|_| |_____|_| \\_| |_| |_| \\_\\_/ \\_\\____/|_____/_/\\_\\",
|
|
1122
|
+
"",
|
|
1123
|
+
" OpenTradex onboarding",
|
|
1124
|
+
" Our implementation. Your strategy.",
|
|
1125
|
+
"",
|
|
1126
|
+
].join("\n"));
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function printSecurityNotice() {
|
|
1130
|
+
printPanel("Security", [
|
|
1131
|
+
"OpenTradex can read local files, install dependencies, and route trading workflows.",
|
|
1132
|
+
"Treat it like a capable local operator. Use least-privilege credentials and keep secrets out of the repo.",
|
|
1133
|
+
"Do not enable live trading until your paper workflow is stable and your risk limits are set.",
|
|
1134
|
+
"Live execution is currently supported on Kalshi. Other rails stay in research, discovery, watchlist, or paper mode.",
|
|
1135
|
+
"",
|
|
1136
|
+
"Recommended routine:",
|
|
1137
|
+
"opentradex doctor",
|
|
1138
|
+
"opentradex providers",
|
|
1139
|
+
]);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function printSection(step, title, description) {
|
|
1143
|
+
console.log([
|
|
1144
|
+
"",
|
|
1145
|
+
`[${step}] ${title.toUpperCase()}`,
|
|
1146
|
+
"-".repeat(72),
|
|
1147
|
+
description,
|
|
1148
|
+
"",
|
|
1149
|
+
].join("\n"));
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function printOptionList(label) {
|
|
1153
|
+
console.log([
|
|
871
1154
|
"",
|
|
1155
|
+
label,
|
|
1156
|
+
"-".repeat(72),
|
|
872
1157
|
].join("\n"));
|
|
873
1158
|
}
|
|
874
1159
|
|
|
1160
|
+
function printDashboardBoot({ workspace, packageManager, profile }) {
|
|
1161
|
+
printPanel("Local harness", [
|
|
1162
|
+
`workspace: ${workspace}`,
|
|
1163
|
+
`runtime: ${profile.runtime}`,
|
|
1164
|
+
`package flow: ${packageManager}`,
|
|
1165
|
+
`dashboard: ${profile.dashboardSurface || DEFAULT_ENV.OPENTRADEX_DASHBOARD_SURFACE}`,
|
|
1166
|
+
`primary rail: ${profile.primaryMarket}`,
|
|
1167
|
+
`additional rails: ${profile.enabledMarkets.join(", ") || "none"}`,
|
|
1168
|
+
`channels: ${profile.channels?.join(", ") || "none"}`,
|
|
1169
|
+
"",
|
|
1170
|
+
"When the dev server is ready, open the dashboard and warm boot the local harness.",
|
|
1171
|
+
"Suggested local URL: http://localhost:3000/dashboard",
|
|
1172
|
+
]);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function printPanel(title, lines, width = 76) {
|
|
1176
|
+
const innerWidth = Math.max(32, width - 4);
|
|
1177
|
+
const border = `+${"-".repeat(innerWidth + 2)}+`;
|
|
1178
|
+
const output = [border];
|
|
1179
|
+
|
|
1180
|
+
for (const line of wrapText(title, innerWidth)) {
|
|
1181
|
+
output.push(`| ${padRight(line, innerWidth)} |`);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
output.push(`| ${"-".repeat(innerWidth)} |`);
|
|
1185
|
+
|
|
1186
|
+
for (const sourceLine of lines) {
|
|
1187
|
+
if (!sourceLine) {
|
|
1188
|
+
output.push(`| ${" ".repeat(innerWidth)} |`);
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
for (const line of wrapText(sourceLine, innerWidth)) {
|
|
1193
|
+
output.push(`| ${padRight(line, innerWidth)} |`);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
output.push(border);
|
|
1198
|
+
console.log(`\n${output.join("\n")}\n`);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function wrapText(text, width) {
|
|
1202
|
+
const normalized = String(text);
|
|
1203
|
+
if (normalized.length <= width) {
|
|
1204
|
+
return [normalized];
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const words = normalized.split(/\s+/).filter(Boolean);
|
|
1208
|
+
if (words.length === 0) {
|
|
1209
|
+
return [""];
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const lines = [];
|
|
1213
|
+
let current = "";
|
|
1214
|
+
|
|
1215
|
+
const flush = () => {
|
|
1216
|
+
if (current) {
|
|
1217
|
+
lines.push(current);
|
|
1218
|
+
current = "";
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
for (const word of words) {
|
|
1223
|
+
if (word.length > width) {
|
|
1224
|
+
flush();
|
|
1225
|
+
for (let index = 0; index < word.length; index += width) {
|
|
1226
|
+
lines.push(word.slice(index, index + width));
|
|
1227
|
+
}
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (!current) {
|
|
1232
|
+
current = word;
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
if (`${current} ${word}`.length <= width) {
|
|
1237
|
+
current = `${current} ${word}`;
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
flush();
|
|
1242
|
+
current = word;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
flush();
|
|
1246
|
+
return lines;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function padRight(value, width) {
|
|
1250
|
+
return String(value).padEnd(width, " ");
|
|
1251
|
+
}
|
|
1252
|
+
|
|
875
1253
|
function normalizeConfig(config = {}) {
|
|
876
1254
|
return {
|
|
877
1255
|
workspace: config.workspace,
|
|
878
1256
|
runtime: config.runtime || config.profile?.runtime || DEFAULT_ENV.OPENTRADEX_RUNTIME,
|
|
879
1257
|
packageManager: config.packageManager || config.profile?.packageManager || DEFAULT_ENV.OPENTRADEX_PACKAGE_MANAGER,
|
|
1258
|
+
dashboardSurface: config.dashboardSurface || config.profile?.dashboardSurface || DEFAULT_ENV.OPENTRADEX_DASHBOARD_SURFACE,
|
|
1259
|
+
channels: keepKnownIds(CHANNEL_OPTIONS, config.channels || config.profile?.channels || DEFAULT_ENV.OPENTRADEX_CHANNELS),
|
|
880
1260
|
mode: config.mode || config.profile?.mode || DEFAULT_ENV.OPENTRADEX_EXECUTION_MODE,
|
|
881
1261
|
primaryMarket: config.primaryMarket || config.profile?.primaryMarket || DEFAULT_ENV.OPENTRADEX_PRIMARY_MARKET,
|
|
882
1262
|
enabledMarkets: keepKnownIds(MARKET_OPTIONS, config.enabledMarkets || config.profile?.enabledMarkets || DEFAULT_ENV.OPENTRADEX_ENABLED_MARKETS),
|
|
883
1263
|
integrations: keepKnownIds(INTEGRATION_OPTIONS, config.integrations || config.profile?.integrations || DEFAULT_ENV.OPENTRADEX_ENABLED_INTEGRATIONS),
|
|
1264
|
+
tradingviewConnectorMode:
|
|
1265
|
+
String(config.tradingviewConnectorMode || config.profile?.tradingviewConnectorMode || DEFAULT_ENV.TRADINGVIEW_CONNECTOR_MODE).toLowerCase() === "mcp"
|
|
1266
|
+
? "mcp"
|
|
1267
|
+
: "watchlist",
|
|
1268
|
+
tradingviewMcpEnabled:
|
|
1269
|
+
String(config.tradingviewMcpEnabled ?? config.profile?.tradingviewMcpEnabled ?? DEFAULT_ENV.TRADINGVIEW_MCP_ENABLED).toLowerCase() === "true",
|
|
1270
|
+
tradingviewMcpTransport:
|
|
1271
|
+
String(config.tradingviewMcpTransport || config.profile?.tradingviewMcpTransport || DEFAULT_ENV.TRADINGVIEW_MCP_TRANSPORT).toLowerCase() === "http"
|
|
1272
|
+
? "http"
|
|
1273
|
+
: "stdio",
|
|
884
1274
|
bankroll: config.bankroll || config.profile?.bankroll || DEFAULT_ENV.BANKROLL,
|
|
885
1275
|
interval: config.interval || config.profile?.interval || DEFAULT_ENV.CYCLE_INTERVAL,
|
|
886
1276
|
installDeps: config.installDeps ?? config.profile?.installDeps ?? false,
|
|
887
1277
|
};
|
|
888
1278
|
}
|
|
889
1279
|
|
|
890
|
-
function buildDoctorNotes({
|
|
1280
|
+
function buildDoctorNotes({
|
|
1281
|
+
runtime,
|
|
1282
|
+
primaryMarket,
|
|
1283
|
+
enabledMarkets,
|
|
1284
|
+
liveTrading,
|
|
1285
|
+
dashboardSurface,
|
|
1286
|
+
channels,
|
|
1287
|
+
tradingviewConnectorMode,
|
|
1288
|
+
tradingviewMcpEnabled,
|
|
1289
|
+
tradingviewMcpTransport,
|
|
1290
|
+
}) {
|
|
891
1291
|
const notes = [];
|
|
892
1292
|
|
|
893
1293
|
if (runtime !== "claude-code") {
|
|
@@ -906,6 +1306,20 @@ function buildDoctorNotes({ runtime, primaryMarket, enabledMarkets, liveTrading
|
|
|
906
1306
|
notes.push("TradingView, Robinhood, and Groww are configured as watchlist/profile rails so you can add credentials only when you really need them.");
|
|
907
1307
|
}
|
|
908
1308
|
|
|
1309
|
+
if (dashboardSurface === "chat") {
|
|
1310
|
+
notes.push(`Dashboard chat cockpit is enabled with channels: ${channels.join(", ") || "none"}.`);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
if (enabledMarkets.includes("tradingview") && tradingviewConnectorMode === "mcp") {
|
|
1314
|
+
notes.push(`TradingView is set to MCP mode over ${tradingviewMcpTransport}. The dashboard will surface connector status, but Claude Code still needs the MCP server available locally.`);
|
|
1315
|
+
} else if (enabledMarkets.includes("tradingview")) {
|
|
1316
|
+
notes.push("TradingView is currently a watchlist/context rail. Enable MCP later if you want richer chart tooling.");
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (tradingviewMcpEnabled && tradingviewConnectorMode === "mcp") {
|
|
1320
|
+
notes.push("TradingView MCP is marked enabled in the workspace profile. Fill in the command or URL if the doctor still shows it as incomplete.");
|
|
1321
|
+
}
|
|
1322
|
+
|
|
909
1323
|
return notes;
|
|
910
1324
|
}
|
|
911
1325
|
|