SignalR · WebSocket

Socket Bağlantıları

Emir, portföy ve ham TradingView sinyalleri için gerçek zamanlı hub metotları.

3 hub metodu JWT kimlik doğrulama Reconnect yeniden abonelik
Hub URL https://api.tradewize.com.tr/hubs/positions
Bağlantı Resmî SignalR istemcisi negotiate ve WebSocket yükseltmesini yönetir. JWT access token ile bağlanın. Emir ve portföy aboneliklerini Heartbeat() ile canlı tutun; yeniden bağlantı kurulduğunda ilgili hub metodunu tekrar çağırın. Ham TradingView sinyallerindeki bağlantı boşluğunu REST catch-up ile kapatın.

TradingView Ham Sinyal Akışı

TradingView webhook’u Gateway tarafından kalıcı olarak kabul edilir edilmez Gateway → RabbitMQ → Redis → Developer API yoluyla yayınlanır. Robot.Api bu teslimat yolunda değildir; olay, robotun sinyali onayladığı veya emir verdiği anlamına gelmez. Webhook URL oluşturma ya da yenileme yanıtının data zarfında data.webhookUrl ile birlikte dönen data.signalStreamId, hem canlı abonelikte hem catch-up isteğinde kullanılır.

1 metot
HUB /hubs/positions read:tradingview-signals

SubscribeToTradingViewSignals - ham sinyallere abone ol

Webhook akışına signalStreamId ile abone olur ve bool sonuç döndürür. Canlı olaylar ReceiveTradingViewSignal eventiyle { cursor, signal } biçiminde gelir. Önce canlı aboneliği kurun, ardından REST catch-up okuyun; iki kanaldaki örtüşmeyi signal.notificationId ile tekilleştirin.

Abone ol, sonra catch-up yap Bağlantı kurulduğunda ve her reconnect sonrasında önce SubscribeToTradingViewSignals çağırın, sonra son saklanan cursor’dan REST catch-up okuyun. Arada iki kanala da düşen aynı olayı signal.notificationId ile tekilleştirin.
Bağlantı ve istemci sınırları Bir bağlantıda en fazla 32 farklı stream’e abone olunabilir; limit aşımında metot false döner ve Error eventi gelir. Recovery işlemlerini seri çalıştırın, tekilleştirme kümesini ve canlı tamponu sınırlayın; tampon taşarsa son kalıcı cursor’dan catch-up’ı yeniden başlatın. Cursor yalnız handler başarısından sonra monoton ilerlemelidir.
Teslimat anlamı ReceiveTradingViewSignal, Gateway’in webhook’u kalıcı olarak kabul ettiğini bildirir. Robot.Api bu akışta değildir; robot sinyali reddedebilir, hiç çalışmayabilir veya emir üretmeyebilir.
Alanların bulunabilirliği symbol, signal, price, quantity, signalName, interval ve sourceEventId nullable olabilir. parseStatus partial veya invalid olduğunda olay yine geçerlidir; istemci yalnız mevcut alanları göstermelidir.

Hub metodu argümanları

SubscribeToTradingViewSignals - ham sinyallere abone ol — Hub metodu argümanları
Argüman Tip Açıklama
signalStreamId zorunlu string Webhook URL oluşturma/yenileme yanıtının data.signalStreamId alanındaki gizli, 64 karakterli stream kimliği.
Bağlantı ve abonelikJavaScript
import * as signalR from '@microsoft/signalr';

const signalStreamId = '<signalStreamId-from-webhook-response>';
const seenNotificationIds = new Set();
const seenOrder = [];
const maxSeenNotificationIds = 20_000;
const maxPendingLive = 1_000;
let pendingLive = [];
let catchingUp = true;
let pendingOverflowed = false;
let recovery = Promise.resolve();

const connection = new signalR.HubConnectionBuilder()
  .withUrl('https://api.tradewize.com.tr/hubs/positions', {
    accessTokenFactory: () => accessToken,
  })
  .withAutomaticReconnect()
  .build();

function rememberOnce(id) {
  if (seenNotificationIds.has(id)) return false;
  seenNotificationIds.add(id);
  seenOrder.push(id);
  if (seenOrder.length > maxSeenNotificationIds) {
    seenNotificationIds.delete(seenOrder.shift());
  }
  return true;
}

function deliver(item) {
  const id = item.signal.notificationId;
  if (!rememberOnce(id)) return;
  handleTradingViewSignal(item.signal);
  saveCursorMonotonic(item.cursor);
}

connection.on('ReceiveTradingViewSignal', (item) => {
  if (catchingUp && pendingLive.length < maxPendingLive) pendingLive.push(item);
  else if (catchingUp) pendingOverflowed = true;
  else deliver(item);
});

async function subscribeThenCatchUp() {
  catchingUp = true;
  pendingLive = [];
  pendingOverflowed = false;
  const pageSize = 100;

  const subscribed = await connection.invoke(
    'SubscribeToTradingViewSignals',
    signalStreamId,
  );
  if (!subscribed) throw new Error('TradingView signal subscription failed');

  while (true) {
    let cursor = loadCursor() || '0-0';
    while (true) {
      const url =
        'https://api.tradewize.com.tr/api/v1/tradingview-signals/' +
        encodeURIComponent(signalStreamId) +
        '?after=' + encodeURIComponent(cursor) + '&count=' + pageSize;
      const response = await fetch(url, {
        headers: { Authorization: 'Bearer ' + accessToken },
      });
      if (!response.ok) throw new Error('TradingView signal catch-up failed');

      const history = await response.json();
      history.events.forEach(deliver);
      if (history.events.length < pageSize) break;
      if (history.nextCursor === cursor) {
        throw new Error('TradingView signal cursor did not advance');
      }
      cursor = history.nextCursor;
    }

    if (!pendingOverflowed) break;
    // Canlı olaylar Redis Stream’de de bulunduğu için son kalıcı cursor’dan tekrar okuyun.
    pendingLive = [];
    pendingOverflowed = false;
  }

  pendingLive.forEach(deliver);
  pendingLive = [];
  catchingUp = false;
}

function queueRecovery() {
  recovery = recovery.catch(() => {}).then(subscribeThenCatchUp);
  return recovery;
}

connection.onreconnected(() => {
  void queueRecovery().catch(reportRecoveryError);
});
await connection.start();
await queueRecovery();

// Kapatırken:
// await connection.invoke('UnsubscribeFromTradingViewSignals', signalStreamId);
Event · ReceiveTradingViewSignalJSON
{
  "cursor": "1793364001000-0",
  "signal": {
    "schemaVersion": "1.0",
    "notificationId": "gws-example-42",
    "deliveryId": "tvd-example-42",
    "correlationId": "2f846fb1-81ac-4134-9b8e-d4c86f9291d5",
    "streamId": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    "brokerageCode": "MarbasMenkul",
    "symbol": "THYAO",
    "signal": "buy",
    "price": 321.45,
    "quantity": 2.5,
    "signalTimestampUtc": "2026-10-30T09:59:59.123Z",
    "gatewayReceivedAtUtc": "2026-10-30T10:00:00Z",
    "signalName": "Kırılım v3",
    "interval": "15",
    "sourceEventId": "tv-42",
    "parseStatus": "parsed"
  }
}

Gerçek Zamanlı (SignalR / WebSocket)

Emir değişiklikleri ve portföy güncellemeleri tek SignalR hub üzerinden, iki ayrı hesap aboneliği metoduyla yayınlanır. Hub adresi wss://api.tradewize.com.tr/hubs/positions şeklindedir; resmi SignalR istemcisi negotiate ve WebSocket yükseltmesini otomatik yönetir. Bağlantıda JWT access token gönderilmeli, hesap abonelikleri Heartbeat() çağrısıyla canlı tutulmalıdır. TradingView ham webhook akışı aynı hub üzerinde ayrı stream kimliğiyle çalışır; üstteki bölüme bakın.

2 metot
HUB /hubs/positions JWT

SubscribeToPositions - emir değişikliklerine abone ol

Belirtilen aracı kurum hesabının emir değişiklikleri akışına abone olur ve bool sonuç döndürür. Metot adı geriye dönük uyumluluk nedeniyle SubscribeToPositions olarak kalmıştır; bu abonelik portföy değil, ReceiveOrderChanges eventindeki emir değişikliklerini yayınlar.

Gerekli yetki JWT zorunludur ve API anahtarında read:orders kapsamı bulunmalıdır. Yalnızca JWT sahibine ait hesaplara abone olunabilir.
Metot adı ve event eşleşmesi SubscribeToPositions çağrısından sonra ReceiveOrderChanges dinlenmelidir. Eski ReceivePositions eventi kullanılmaz.
Abonelik yaşam döngüsü Aboneliği UnsubscribeFromPositions ile kapatın. Bağlantı kesildiğinde sunucu abonelik kaydını temizler; uzun yaşayan bağlantılarda Heartbeat() çağırın.

Hub metodu argümanları

SubscribeToPositions - emir değişikliklerine abone ol — Hub metodu argümanları
Argüman Tip Açıklama
brokerageCode zorunlu string Aracı kurum kodu (örn. MarbasMenkul).
accountNumber zorunlu string Abone olunacak ve JWT sahibine ait olması gereken hesap numarası.
Bağlantı ve abonelikJavaScript
import * as signalR from '@microsoft/signalr';

const connection = new signalR.HubConnectionBuilder()
  .withUrl('https://api.tradewize.com.tr/hubs/positions', {
    accessTokenFactory: () => accessToken,
  })
  .withAutomaticReconnect()
  .build();

connection.on('ReceiveOrderChanges', (changes) => {
  console.log('Emir değişiklikleri', changes);
});
connection.on('Error', (message) => console.error(message));

await connection.start();
const subscribed = await connection.invoke(
  'SubscribeToPositions',
  'MarbasMenkul',
  '77777',
);

// Aktif aboneliklerin TTL süresini yenileyin.
setInterval(() => connection.invoke('Heartbeat'), 60_000);

// Aboneliği kapatmak için:
// await connection.invoke('UnsubscribeFromPositions', 'MarbasMenkul', '77777');
Event · ReceiveOrderChangesJSON
[
  {
    "orderId": "305344701",
    "symbol": "THYAO",
    "side": "BUY",
    "quantity": 100,
    "remainingQuantity": 0,
    "visibleQuantity": 100,
    "orderType": 2,
    "status": "Completed",
    "statusDescription": "Gerçekleşti",
    "accountNumber": "77777",
    "changeType": "StatusChanged",
    "previousStatus": "Pending",
    "changedAt": "2026-08-26T10:35:22Z"
  }
]
HUB /hubs/positions JWT

SubscribeToPortfolio - portföy güncellemelerine abone ol

Belirtilen aracı kurum hesabının portföy akışına abone olur ve bool sonuç döndürür. Başarılı abonelikten sonra güncel portföy nesneleri ReceivePortfolio eventiyle yayınlanır.

Gerekli yetki JWT zorunludur ve API anahtarında read:positions kapsamı bulunmalıdır. Yalnızca JWT sahibine ait hesaplara abone olunabilir.
Abonelik yaşam döngüsü Aboneliği UnsubscribeFromPortfolio ile kapatın. Bağlantı kesildiğinde sunucu abonelik kaydını temizler; uzun yaşayan bağlantılarda Heartbeat() çağırın.

Hub metodu argümanları

SubscribeToPortfolio - portföy güncellemelerine abone ol — Hub metodu argümanları
Argüman Tip Açıklama
brokerageCode zorunlu string Aracı kurum kodu (örn. MarbasMenkul).
accountNumber zorunlu string Abone olunacak ve JWT sahibine ait olması gereken hesap numarası.
Bağlantı ve abonelikJavaScript
import * as signalR from '@microsoft/signalr';

const connection = new signalR.HubConnectionBuilder()
  .withUrl('https://api.tradewize.com.tr/hubs/positions', {
    accessTokenFactory: () => accessToken,
  })
  .withAutomaticReconnect()
  .build();

connection.on('ReceivePortfolio', (portfolio) => {
  console.log('Portföy güncellemesi', portfolio);
});
connection.on('Error', (message) => console.error(message));

await connection.start();
const subscribed = await connection.invoke(
  'SubscribeToPortfolio',
  'MarbasMenkul',
  '77777',
);

// Aktif aboneliklerin TTL süresini yenileyin.
setInterval(() => connection.invoke('Heartbeat'), 60_000);

// Aboneliği kapatmak için:
// await connection.invoke('UnsubscribeFromPortfolio', 'MarbasMenkul', '77777');
Event · ReceivePortfolioJSON
{
  "accountNumber": "77777",
  "dateTime": "2026-08-26T10:35:22Z",
  "accountBalance": 152340.50,
  "cashAmount": 12500.00,
  "t1NetCashAmount": 12500.00,
  "t2NetCashAmount": 12500.00,
  "overall": 152340.50,
  "stockOverall": 139840.50,
  "totalProfit": 8420.75,
  "positions": [
    {
      "symbol": "THYAO",
      "lot": 100,
      "sellable": 100,
      "profitRate": 6.02
    }
  ]
}