Skip to content

Commit 1e1fc42

Browse files
committed
tradingview linking
1 parent 2b45ebd commit 1e1fc42

File tree

7 files changed

+159
-39
lines changed

7 files changed

+159
-39
lines changed

src/controller/orders_controller.ts

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BaseController, TemplateHelpers } from './base_controller';
22
import { ProfileService } from '../profile/profile_service';
33
import { ProfilePairService } from '../modules/profile_pair_service';
44
import { OrderParams } from '../profile/types';
5+
import { buildTradingViewSymbol } from '../utils/tradingview_util';
56
import express from 'express';
67

78
export class OrdersController extends BaseController {
@@ -65,7 +66,7 @@ export class OrdersController extends BaseController {
6566
allPairs,
6667
pairErrors,
6768
ticker,
68-
tradingview: this.buildTradingViewSymbol(profile.exchange, pair),
69+
tradingview: buildTradingViewSymbol(profile.exchange, pair),
6970
form: {
7071
price: ticker ? ticker.bid : undefined,
7172
type: 'limit'
@@ -150,7 +151,7 @@ export class OrdersController extends BaseController {
150151
allPairs,
151152
pairErrors,
152153
ticker,
153-
tradingview: this.buildTradingViewSymbol(profile.exchange, pair),
154+
tradingview: buildTradingViewSymbol(profile.exchange, pair),
154155
form,
155156
asset,
156157
currency,
@@ -202,41 +203,4 @@ export class OrdersController extends BaseController {
202203
currency: parts[1] || ''
203204
};
204205
}
205-
206-
/**
207-
* Build TradingView symbol from exchange and pair
208-
*/
209-
private buildTradingViewSymbol(exchange: string, pair: string): string {
210-
let symbol = pair.replace('/', '');
211-
212-
// Exchange-specific adjustments
213-
if (exchange === 'binance') {
214-
// For futures, append PERP
215-
if (pair.includes(':USDT')) {
216-
symbol = symbol.replace(':USDT', 'PERP');
217-
}
218-
}
219-
220-
if (exchange === 'bybit') {
221-
if (pair.endsWith(':USDT')) {
222-
symbol = symbol.replace(':USDT', '.P');
223-
} else if (pair.endsWith(':USDC')) {
224-
symbol = symbol.replace(':USDC', '.P');
225-
}
226-
}
227-
228-
// Map exchange names to TradingView format
229-
const exchangeMap: Record<string, string> = {
230-
'coinbasepro': 'coinbase',
231-
'coinbase': 'coinbase',
232-
'binance': 'binance',
233-
'bybit': 'bybit',
234-
'kraken': 'kraken',
235-
'bitfinex': 'bitfinex',
236-
};
237-
238-
const tvExchange = exchangeMap[exchange.toLowerCase()] || exchange.toLowerCase();
239-
240-
return `${tvExchange.toUpperCase()}:${symbol.toUpperCase()}`;
241-
}
242206
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { BaseController, TemplateHelpers } from './base_controller';
2+
import { buildTradingViewSymbol, parseExchangeSymbol } from '../utils/tradingview_util';
3+
import express from 'express';
4+
5+
export class TradingViewController extends BaseController {
6+
constructor(templateHelpers: TemplateHelpers) {
7+
super(templateHelpers);
8+
}
9+
10+
registerRoutes(router: express.Router): void {
11+
// TradingView chart page for a specific exchange:symbol
12+
// e.g., /tradingview/bybit:BTC/USDT:USDT
13+
router.get('/tradingview/:exchangeSymbol', (req: any, res: any) => {
14+
const { exchangeSymbol } = req.params;
15+
16+
const parsed = parseExchangeSymbol(exchangeSymbol);
17+
if (!parsed) {
18+
return res.status(400).render('error', {
19+
activePage: 'tradingview',
20+
title: 'Invalid Symbol | Crypto Bot',
21+
message: `Invalid symbol format: ${exchangeSymbol}. Expected format: exchange:symbol (e.g., bybit:BTC/USDT:USDT)`
22+
});
23+
}
24+
25+
const { exchange, pair } = parsed;
26+
const tradingviewSymbol = buildTradingViewSymbol(exchange, pair);
27+
28+
res.render('tradingview_chart', {
29+
activePage: 'tradingview',
30+
title: `${exchange}:${pair} | Crypto Bot`,
31+
exchange,
32+
pair,
33+
tradingviewSymbol,
34+
layout: 'layout'
35+
});
36+
});
37+
}
38+
}

src/modules/http.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export class Http {
155155
this.services.getCcxtExchangesController(this.templateHelpers).registerRoutes(mainRouter);
156156
this.services.getProfileController(this.templateHelpers).registerRoutes(mainRouter);
157157
this.services.getSettingsController(this.templateHelpers).registerRoutes(mainRouter);
158+
this.services.getTradingViewController(this.templateHelpers).registerRoutes(mainRouter);
158159

159160
// Mount the main router at root
160161
app.use('/', mainRouter);

src/modules/services.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { CcxtExchangesController } from '../controller';
5353
import { DashboardSettingsController } from '../controller';
5454
import { ProfileController } from '../controller';
5555
import { SettingsController } from '../controller';
56+
import { TradingViewController } from '../controller/tradingview_controller';
5657

5758
// V2 Strategies
5859
import { DcaDipper } from '../strategy/strategies/dca_dipper/dca_dipper';
@@ -185,6 +186,7 @@ export interface Services {
185186
getCcxtExchangesController(templateHelpers: any): CcxtExchangesController;
186187
getProfileController(templateHelpers: any): ProfileController;
187188
getSettingsController(templateHelpers: any): SettingsController;
189+
getTradingViewController(templateHelpers: any): TradingViewController;
188190
getExchangeInstanceService(): ExchangeInstanceService;
189191
getProfileService(): ProfileService;
190192
getProfilePairService(): ProfilePairService;
@@ -525,6 +527,10 @@ const services: Services = {
525527
return new SettingsController(templateHelpers, this.getSystemUtil());
526528
},
527529

530+
getTradingViewController: function (templateHelpers: any): TradingViewController {
531+
return new TradingViewController(templateHelpers);
532+
},
533+
528534
getExchangeInstanceService: function (): ExchangeInstanceService {
529535
if (exchangeInstanceService) {
530536
return exchangeInstanceService;

src/utils/tradingview_util.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Build TradingView symbol from exchange and pair
3+
* Converts bot's internal format (e.g., "bybit:BTC/USDT:USDT") to TradingView format (e.g., "BYBIT:BTCUSDT.P")
4+
*/
5+
export function buildTradingViewSymbol(exchange: string, pair: string): string {
6+
let symbol = pair.replace('/', '');
7+
8+
// Exchange-specific adjustments
9+
if (exchange === 'binance') {
10+
// For futures, append PERP
11+
if (pair.includes(':USDT')) {
12+
symbol = symbol.replace(':USDT', 'PERP');
13+
}
14+
}
15+
16+
if (exchange === 'bybit') {
17+
if (pair.endsWith(':USDT')) {
18+
symbol = symbol.replace(':USDT', '.P');
19+
} else if (pair.endsWith(':USDC')) {
20+
symbol = symbol.replace(':USDC', '.P');
21+
}
22+
}
23+
24+
// Map exchange names to TradingView format
25+
const exchangeMap: Record<string, string> = {
26+
'coinbasepro': 'coinbase',
27+
'coinbase': 'coinbase',
28+
'binance': 'binance',
29+
'bybit': 'bybit',
30+
'kraken': 'kraken',
31+
'bitfinex': 'bitfinex',
32+
};
33+
34+
const tvExchange = exchangeMap[exchange.toLowerCase()] || exchange.toLowerCase();
35+
36+
return `${tvExchange.toUpperCase()}:${symbol.toUpperCase()}`;
37+
}
38+
39+
/**
40+
* Parse exchange:symbol format from URL parameter
41+
* e.g., "bybit:BTC/USDT:USDT" -> { exchange: "bybit", pair: "BTC/USDT:USDT" }
42+
*/
43+
export function parseExchangeSymbol(param: string): { exchange: string; pair: string } | null {
44+
// Find the first colon which separates exchange from pair
45+
const colonIndex = param.indexOf(':');
46+
if (colonIndex === -1) {
47+
return null;
48+
}
49+
50+
const exchange = param.substring(0, colonIndex);
51+
const pair = decodeURIComponent(param.substring(colonIndex + 1));
52+
53+
return { exchange, pair };
54+
}

views/error.ejs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!-- Content -->
2+
<div class="p-4">
3+
<div class="max-w-[1800px] mx-auto">
4+
<div class="bg-white border border-gray-200 rounded-lg shadow-sm">
5+
<div class="p-6 text-center">
6+
<div class="mb-4">
7+
<i class="fas fa-exclamation-triangle text-6xl text-red-500"></i>
8+
</div>
9+
<h1 class="text-2xl font-bold text-gray-800 mb-2"><%= title %></h1>
10+
<p class="text-gray-600 mb-6"><%= message %></p>
11+
<a href="/" class="inline-block px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
12+
<i class="fas fa-home mr-2"></i>Go to Dashboard
13+
</a>
14+
</div>
15+
</div>
16+
</div>
17+
</div>

views/tradingview_chart.ejs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!-- Content -->
2+
<div class="p-4">
3+
<div class="max-w-[1800px] mx-auto">
4+
<div class="flex items-center justify-between mb-4">
5+
<h1 class="text-2xl font-semibold">
6+
<i class="fas fa-chart-line mr-2 text-blue-600"></i>
7+
<%= exchange %>:<%= pair %>
8+
</h1>
9+
<nav class="text-sm text-gray-500">
10+
<a href="/" class="text-blue-600 hover:underline">Dashboard</a> / TradingView
11+
</nav>
12+
</div>
13+
14+
<div class="bg-white border border-gray-200 rounded-lg shadow-sm">
15+
<div class="p-4">
16+
<div id="tradingview_chart" style="width: 100%; height: 700px"></div>
17+
</div>
18+
</div>
19+
</div>
20+
</div>
21+
22+
<script src="https://s3.tradingview.com/tv.js"></script>
23+
<script type="text/javascript">
24+
new TradingView.widget({
25+
"autosize": true,
26+
"symbol": "<%= tradingviewSymbol %>",
27+
"interval": "15",
28+
"theme": "Light",
29+
"style": "1",
30+
"locale": "en",
31+
"toolbar_bg": "#f1f3f6",
32+
"enable_publishing": false,
33+
"allow_symbol_change": true,
34+
"container_id": "tradingview_chart",
35+
"timezone": Intl.DateTimeFormat().resolvedOptions().timeZone,
36+
"studies": [
37+
"RSI@tv-basicstudies"
38+
]
39+
});
40+
</script>

0 commit comments

Comments
 (0)