> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whitebit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Positions

> Subscribe to real-time collateral position updates for a WhiteBIT account via WebSocket.

export const RelatedResources = ({children}) => {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const el = ref.current;
    if (el.parentElement) {
      el.parentElement.appendChild(el);
    }
    setVisible(true);
  }, []);
  return <div ref={ref} className="related-resources" style={{
    marginTop: "2.5rem",
    paddingTop: "1.5rem",
    borderTop: "1px solid var(--border-color, #e5e7eb)",
    opacity: visible ? 1 : 0,
    transition: "opacity 0.15s ease-in"
  }}>
      <h2 style={{
    marginTop: 0
  }}>Related resources</h2>
      {children}
    </div>;
};

export const exPositionsUnsubscribe = {
  "id": 17,
  "method": "positionsMargin_unsubscribe",
  "params": []
};

export const exPositionsUpdate = {
  "method": "positionsMargin_update",
  "params": {
    "total": 1,
    "records": [{
      "id": 2,
      "market": "BTC_USDT",
      "ctime": 1704067200,
      "mtime": 1704067200,
      "amount": "-0.01",
      "amount_in_money": "118.762",
      "base_price": "60000",
      "pnl": "-0.47",
      "liq_price": "65000",
      "liq_stage": null,
      "unrealized_funding": "0",
      "funding": "0",
      "margin": "23.8",
      "free_margin": "999932.92",
      "realized_pnl": "0",
      "position_side": "LONG"
    }]
  },
  "id": null
};

export const exPositionsSubscribeResponse = {
  "id": 16,
  "result": {
    "status": "success"
  },
  "error": null
};

export const exPositionsSubscribe = {
  "id": 16,
  "method": "positionsMargin_subscribe",
  "params": []
};

export const positionRecord = [{
  name: "id",
  type: "integer",
  description: "Position ID"
}, {
  name: "market",
  type: "string",
  description: "Market name"
}, {
  name: "ctime",
  type: "number",
  description: "Date of position opening in Unix time"
}, {
  name: "mtime",
  type: "number",
  description: "Date of position modification (the date of the current event) in Unix time"
}, {
  name: "amount",
  type: "string",
  description: "Position amount (negative = short)"
}, {
  name: "amount_in_money",
  type: "string",
  description: "Position amount in money"
}, {
  name: "base_price",
  type: "string",
  description: "Base price of position"
}, {
  name: "pnl",
  type: "string",
  description: "Unrealized PnL in **money**"
}, {
  name: "liq_price",
  type: "string",
  description: "Liquidation price according to current state of position"
}, {
  name: "liq_stage",
  type: "string",
  enum: [null, "margin_call"],
  description: "Liquidation state. Possible values: null, margin_call"
}, {
  name: "unrealized_funding",
  type: "string",
  description: "Funding to be paid on the next position stage change (order, liquidation, etc)"
}, {
  name: "funding",
  type: "string",
  description: "Funding already disbursed"
}, {
  name: "margin",
  type: "string",
  description: "Own funds amount in open position in **money**"
}, {
  name: "free_margin",
  type: "string",
  description: "Free funds for trading"
}, {
  name: "realized_pnl",
  type: "string",
  description: "Realized PnL in **money**"
}, {
  name: "position_side",
  type: "string",
  enum: ["LONG", "SHORT", "BOTH"],
  description: "Position side - LONG or SHORT or BOTH"
}];

export const channelMeta = {
  "authRequired": true,
  "rateLimits": {
    "connectionsPerMinute": 1000,
    "requestsPerMinute": 200
  },
  "errorCodes": "standard"
};

export const channelOperations = [{
  name: "Subscribe",
  send: "positionsMargin_subscribe",
  receive: "Confirmation (status: success)",
  push: "positionsMargin_update — real-time positions update"
}, {
  name: "Unsubscribe",
  send: "positionsMargin_unsubscribe",
  receive: "Confirmation (status: success)",
  push: null
}];

export const WsErrorCodes = ({errorCodes}) => {
  if (Array.isArray(errorCodes)) {
    return <div>
        <table>
          <thead>
            <tr>
              <th>Code</th>
              <th>Message</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            {errorCodes.map((err, i) => <tr key={i}>
                <td><code>{err.code}</code></td>
                <td>{err.message}</td>
                <td>{err.description}</td>
              </tr>)}
          </tbody>
        </table>
        <p>
          Standard WebSocket error codes apply. See <a href="/websocket/overview">WebSocket overview</a> for error code reference.
        </p>
      </div>;
  }
  return <p>
      Standard WebSocket error codes apply. See <a href="/websocket/overview">WebSocket overview</a> for error code reference.
    </p>;
};

export const WsRateLimits = ({connectionsPerMinute, requestsPerMinute}) => {
  return <p>
      Standard connection-level rate limits apply. See{' '}
      <a href="/websocket/rate-limits">WebSocket Rate Limits</a> for details.
    </p>;
};

export const WsAuthBadge = ({required}) => {
  if (!required) {
    return <p><strong>Authentication:</strong> None. No authentication required.</p>;
  }
  return <div>
      <Warning>
        This is a private channel.{' '}
        <a href="/websocket/authentication">Authorize</a> the WebSocket
        connection before subscribing.
      </Warning>
      <p>
        <strong>Authentication:</strong> Required.{' '}
        <a href="/websocket/authentication">Authorize</a> the WebSocket
        connection before subscribing.
      </p>
    </div>;
};

export const WsSubscribeSteps = ({subscribe, confirmation, update, unsubscribe, subscribeNote, updateNote, updateExtra, unsubscribeNote}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const TOKEN_COLORS = isDark ? {
    key: '#79c0ff',
    string: '#a5d6ff',
    number: '#e3b341',
    boolean: '#ff7b72',
    null: '#8b949e',
    punctuation: '#c9d1d9',
    ws: '#c9d1d9'
  } : {
    key: '#0550ae',
    string: '#0a3069',
    number: '#953800',
    boolean: '#cf222e',
    null: '#6e7781',
    punctuation: '#24292f',
    ws: '#24292f'
  };
  const tokenize = json => {
    const tokens = [];
    let i = 0;
    while (i < json.length) {
      if ((/\s/).test(json[i])) {
        let ws = '';
        while (i < json.length && (/\s/).test(json[i])) ws += json[i++];
        tokens.push({
          type: 'ws',
          value: ws
        });
        continue;
      }
      if (json[i] === '"') {
        let str = '"';
        i++;
        while (i < json.length) {
          if (json[i] === '\\') {
            str += json[i] + json[i + 1];
            i += 2;
          } else if (json[i] === '"') {
            str += '"';
            i++;
            break;
          } else str += json[i++];
        }
        let j = i;
        while (j < json.length && (/[ \t]/).test(json[j])) j++;
        tokens.push({
          type: json[j] === ':' ? 'key' : 'string',
          value: str
        });
        continue;
      }
      if (json[i] === '-' || (/\d/).test(json[i])) {
        let num = '';
        if (json[i] === '-') num += json[i++];
        while (i < json.length && (/[\d.eE+\-]/).test(json[i])) num += json[i++];
        tokens.push({
          type: 'number',
          value: num
        });
        continue;
      }
      if (json.slice(i, i + 4) === 'true') {
        tokens.push({
          type: 'boolean',
          value: 'true'
        });
        i += 4;
        continue;
      }
      if (json.slice(i, i + 5) === 'false') {
        tokens.push({
          type: 'boolean',
          value: 'false'
        });
        i += 5;
        continue;
      }
      if (json.slice(i, i + 4) === 'null') {
        tokens.push({
          type: 'null',
          value: 'null'
        });
        i += 4;
        continue;
      }
      tokens.push({
        type: 'punctuation',
        value: json[i++]
      });
    }
    return tokens;
  };
  const JsonBlock = ({data}) => {
    const json = JSON.stringify(data, null, 2);
    const tokens = tokenize(json);
    return <div className="border border-gray-200 dark:border-[#30363d]" style={{
      borderRadius: '0.5rem',
      overflow: 'hidden',
      marginBottom: '0.75rem'
    }}>
        <div className="bg-gray-50 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d] text-gray-500 dark:text-[#8b949e]" style={{
      padding: '0.25rem 1rem',
      fontSize: '0.7rem',
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
    }}>
          json
        </div>
        <div className="bg-white dark:bg-[#0d1117]" style={{
      margin: 0,
      padding: '0.875rem 1.125rem',
      overflowX: 'auto',
      fontSize: '0.8125rem',
      lineHeight: '1.65',
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
      whiteSpace: 'pre'
    }}>
          {tokens.map((t, idx) => <span key={idx} style={{
      color: TOKEN_COLORS[t.type]
    }}>{t.value}</span>)}
        </div>
      </div>;
  };
  const CIRCLE = 32;
  const WsStep = ({number, title, isLast, children}) => <div style={{
    display: 'flex',
    gap: '1rem'
  }}>
      <div style={{
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    flexShrink: 0,
    width: CIRCLE
  }}>
        <div className="bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100" style={{
    width: CIRCLE,
    height: CIRCLE,
    borderRadius: '50%',
    border: '1.5px solid',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: '0.8125rem',
    fontWeight: 600,
    flexShrink: 0
  }}>
          {number}
        </div>
        {!isLast && <div className="bg-gray-200 dark:bg-gray-700" style={{
    width: 2,
    flex: 1,
    minHeight: '1.5rem'
  }} />}
      </div>
      <div style={{
    flex: 1,
    paddingBottom: isLast ? '0.5rem' : '1.75rem',
    paddingTop: '4px',
    minWidth: 0
  }}>
        <p style={{
    fontWeight: 600,
    fontSize: '1rem',
    margin: '0 0 0.75rem 0',
    lineHeight: `${CIRCLE}px`
  }}>
          {title}
        </p>
        {children}
      </div>
    </div>;
  return <div style={{
    margin: '1.25rem 0'
  }}>
      <WsStep number={1} title="Send subscription request">
        {subscribeNote && <p style={{
    margin: '0 0 0.75rem 0'
  }}>{subscribeNote}</p>}
        <JsonBlock data={subscribe} />
      </WsStep>

      <WsStep number={2} title="Receive confirmation">
        <JsonBlock data={confirmation} />
      </WsStep>

      <WsStep number={3} title="Receive real-time updates">
        {updateNote && <p style={{
    margin: '0 0 0.75rem 0'
  }}>{updateNote}</p>}
        <JsonBlock data={update} />
        {updateExtra}
      </WsStep>

      <WsStep number={4} title="Unsubscribe" isLast>
        {unsubscribeNote && <p style={{
    margin: '0 0 0.75rem 0'
  }}>{unsubscribeNote}</p>}
        <JsonBlock data={unsubscribe} />
      </WsStep>
    </div>;
};

export const WsMessageExample = ({data, title}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const COLORS = isDark ? {
    key: '#79c0ff',
    string: '#a5d6ff',
    number: '#e3b341',
    boolean: '#ff7b72',
    null: '#8b949e',
    punctuation: '#c9d1d9',
    ws: '#c9d1d9'
  } : {
    key: '#0550ae',
    string: '#0a3069',
    number: '#953800',
    boolean: '#cf222e',
    null: '#6e7781',
    punctuation: '#24292f',
    ws: '#24292f'
  };
  const tokenize = json => {
    const tokens = [];
    let i = 0;
    while (i < json.length) {
      if ((/\s/).test(json[i])) {
        let ws = '';
        while (i < json.length && (/\s/).test(json[i])) ws += json[i++];
        tokens.push({
          type: 'ws',
          value: ws
        });
        continue;
      }
      if (json[i] === '"') {
        let str = '"';
        i++;
        while (i < json.length) {
          if (json[i] === '\\') {
            str += json[i] + json[i + 1];
            i += 2;
          } else if (json[i] === '"') {
            str += '"';
            i++;
            break;
          } else {
            str += json[i++];
          }
        }
        let j = i;
        while (j < json.length && (/[ \t]/).test(json[j])) j++;
        const isKey = json[j] === ':';
        tokens.push({
          type: isKey ? 'key' : 'string',
          value: str
        });
        continue;
      }
      if (json[i] === '-' || (/\d/).test(json[i])) {
        let num = '';
        if (json[i] === '-') num += json[i++];
        while (i < json.length && (/[\d.eE+\-]/).test(json[i])) num += json[i++];
        tokens.push({
          type: 'number',
          value: num
        });
        continue;
      }
      if (json.slice(i, i + 4) === 'true') {
        tokens.push({
          type: 'boolean',
          value: 'true'
        });
        i += 4;
        continue;
      }
      if (json.slice(i, i + 5) === 'false') {
        tokens.push({
          type: 'boolean',
          value: 'false'
        });
        i += 5;
        continue;
      }
      if (json.slice(i, i + 4) === 'null') {
        tokens.push({
          type: 'null',
          value: 'null'
        });
        i += 4;
        continue;
      }
      tokens.push({
        type: 'punctuation',
        value: json[i++]
      });
    }
    return tokens;
  };
  const json = JSON.stringify(data, null, 2);
  const tokens = tokenize(json);
  return <div style={{
    marginBottom: '1rem'
  }}>
      {title && <p style={{
    fontWeight: 600,
    fontSize: '0.875rem',
    marginTop: '1rem',
    marginBottom: '0.5rem'
  }}>
          {title}
        </p>}
      <div className="border border-gray-200 dark:border-[#30363d]" style={{
    position: 'relative',
    borderRadius: '0.5rem',
    overflow: 'hidden'
  }}>
        <div className="bg-gray-50 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]" style={{
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: '0.375rem 1rem'
  }}>
          <span className="text-gray-500 dark:text-[#8b949e]" style={{
    fontSize: '0.75rem',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
  }}>
            json
          </span>
        </div>
        <div className="bg-white dark:bg-[#0d1117]" style={{
    margin: 0,
    padding: '1rem 1.25rem',
    overflowX: 'auto',
    fontSize: '0.8125rem',
    lineHeight: '1.7',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
    whiteSpace: 'pre'
  }}>
          {tokens.map((t, idx) => <span key={idx} style={{
    color: COLORS[t.type]
  }}>{t.value}</span>)}
        </div>
      </div>
    </div>;
};

export const WsSchemaTable = ({title, fields}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const T = isDark ? {
    border: '#374151',
    borderSubtle: '#1f2937',
    headerBg: '#1f2937',
    headerText: '#9ca3af',
    titleBg: '#1f2937',
    titleText: '#d1d5db',
    fieldBg: '#374151',
    fieldText: '#e5e7eb',
    reqYes: '#f3f4f6',
    reqNo: '#4b5563',
    descText: '#9ca3af',
    emptyText: '#4b5563',
    enumBg: 'rgb(120 53 15 / 0.3)',
    enumText: '#fbbf24'
  } : {
    border: '#e5e7eb',
    borderSubtle: '#f3f4f6',
    headerBg: '#f9fafb',
    headerText: '#6b7280',
    titleBg: '#f9fafb',
    titleText: '#374151',
    fieldBg: '#f3f4f6',
    fieldText: '#1f2937',
    reqYes: '#111827',
    reqNo: '#d1d5db',
    descText: '#6b7280',
    emptyText: '#d1d5db',
    enumBg: '#fffbeb',
    enumText: '#92400e'
  };
  const TYPE_STYLE = isDark ? {
    string: {
      backgroundColor: 'rgb(88 28 135 / 0.25)',
      color: '#c084fc'
    },
    integer: {
      backgroundColor: 'rgb(154 52 18 / 0.25)',
      color: '#fb923c'
    },
    number: {
      backgroundColor: 'rgb(154 52 18 / 0.25)',
      color: '#fb923c'
    },
    boolean: {
      backgroundColor: 'rgb(30 64 175 / 0.25)',
      color: '#60a5fa'
    },
    array: {
      backgroundColor: 'rgb(17 94 89 / 0.25)',
      color: '#2dd4bf'
    },
    object: {
      backgroundColor: 'rgb(55 65 81 / 0.4)',
      color: '#9ca3af'
    },
    null: {
      backgroundColor: 'rgb(55 65 81 / 0.4)',
      color: '#9ca3af'
    }
  } : {
    string: {
      backgroundColor: '#faf5ff',
      color: '#6b21a8'
    },
    integer: {
      backgroundColor: '#fff7ed',
      color: '#9a3412'
    },
    number: {
      backgroundColor: '#fff7ed',
      color: '#9a3412'
    },
    boolean: {
      backgroundColor: '#eff6ff',
      color: '#1e40af'
    },
    array: {
      backgroundColor: '#f0fdfa',
      color: '#115e59'
    },
    object: {
      backgroundColor: '#f3f4f6',
      color: '#374151'
    },
    null: {
      backgroundColor: '#f3f4f6',
      color: '#374151'
    }
  };
  const MONO = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
  const HEADER = {
    padding: '0.5rem 1rem',
    fontSize: '0.6875rem',
    fontWeight: 600,
    textTransform: 'uppercase',
    letterSpacing: '0.05em',
    whiteSpace: 'nowrap',
    color: T.headerText,
    backgroundColor: T.headerBg,
    borderBottom: `1px solid ${T.border}`
  };
  const CELL = {
    padding: '0.625rem 1rem',
    fontSize: '0.8125rem',
    display: 'flex',
    alignItems: 'center',
    minWidth: 0
  };
  const hasRequired = fields.some(f => f.required === true);
  const hasEnum = fields.some(f => f.enum && f.enum.length > 0);
  const gridTemplateColumns = ['minmax(120px, auto)', 'minmax(70px, auto)', ...hasRequired ? ['minmax(70px, auto)'] : [], ...hasEnum ? ['minmax(120px, auto)'] : [], '1fr'].join(' ');
  return <div style={{
    margin: '1.25rem 0',
    borderRadius: '0.5rem',
    border: `1px solid ${T.border}`,
    overflow: 'hidden',
    fontSize: '0.8125rem'
  }}>
      {title && <div style={{
    padding: '0.5rem 1rem',
    fontSize: '0.75rem',
    fontWeight: 600,
    letterSpacing: '0.02em',
    backgroundColor: T.titleBg,
    borderBottom: `1px solid ${T.border}`,
    color: T.titleText
  }}>
          {title}
        </div>}
      <div style={{
    display: 'grid',
    gridTemplateColumns,
    width: '100%',
    overflowX: 'auto'
  }}>

        <div style={HEADER}>Field</div>
        <div style={HEADER}>Type</div>
        {hasRequired && <div style={HEADER}>Required</div>}
        {hasEnum && <div style={HEADER}>Values</div>}
        <div style={HEADER}>Description</div>

        {fields.map((f, i) => {
    const borderTop = `1px solid ${i === 0 ? T.border : T.borderSubtle}`;
    const typeStyle = TYPE_STYLE[f.type] || TYPE_STYLE.object;
    return [<div key={`${i}-n`} style={{
      ...CELL,
      whiteSpace: 'nowrap',
      borderTop
    }}>
              <span style={{
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem',
      fontSize: '0.75rem',
      fontFamily: MONO,
      backgroundColor: T.fieldBg,
      color: T.fieldText
    }}>
                {f.name}
              </span>
            </div>, <div key={`${i}-t`} style={{
      ...CELL,
      whiteSpace: 'nowrap',
      borderTop
    }}>
              <span style={{
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem',
      fontSize: '0.75rem',
      whiteSpace: 'nowrap',
      fontFamily: MONO,
      ...typeStyle
    }}>
                {f.type}
              </span>
            </div>, ...hasRequired ? [<div key={`${i}-r`} style={{
      ...CELL,
      borderTop,
      fontWeight: f.required ? 500 : 400,
      color: f.required ? T.reqYes : T.reqNo
    }}>
                {f.required ? 'Yes' : '—'}
              </div>] : [], ...hasEnum ? [<div key={`${i}-e`} style={{
      ...CELL,
      borderTop,
      flexWrap: 'wrap',
      gap: '0.25rem'
    }}>
                {f.enum && f.enum.length > 0 ? f.enum.map((v, j) => <span key={j} style={{
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem',
      fontSize: '0.75rem',
      whiteSpace: 'nowrap',
      fontFamily: MONO,
      backgroundColor: T.enumBg,
      color: T.enumText
    }}>
                        {String(v)}
                      </span>) : <span style={{
      color: T.emptyText
    }}>—</span>}
              </div>] : [], <div key={`${i}-d`} style={{
      ...CELL,
      borderTop,
      color: T.descText
    }}>
              {f.description}
            </div>];
  })}

      </div>
    </div>;
};

export const WsChannelOverview = ({operations}) => {
  const hasPush = operations.some(op => op.push);
  const Th = ({children}) => <th className="text-left text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700" style={{
    padding: "0.5rem 1rem",
    fontSize: "0.6875rem",
    fontWeight: 600,
    textTransform: "uppercase",
    letterSpacing: "0.05em",
    whiteSpace: "nowrap"
  }}>
      {children}
    </th>;
  const PushBadge = ({value}) => {
    const dashIdx = value.indexOf(" — ");
    const method = dashIdx !== -1 ? value.slice(0, dashIdx) : value;
    const desc = dashIdx !== -1 ? value.slice(dashIdx + 3) : null;
    return <span style={{
      display: "inline-flex",
      alignItems: "center",
      gap: "0.375rem",
      flexWrap: "wrap"
    }}>
        <code className="bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-400" style={{
      padding: "0.125rem 0.375rem",
      borderRadius: "0.25rem",
      fontSize: "0.75rem",
      whiteSpace: "nowrap"
    }}>
          {method}
        </code>
        {desc && <span className="text-gray-500 dark:text-gray-400" style={{
      fontSize: "0.75rem"
    }}>
            — {desc}
          </span>}
      </span>;
  };
  return <div style={{
    margin: "1.25rem 0",
    borderRadius: "0.5rem",
    border: "1px solid",
    overflow: "hidden",
    fontSize: "0.8125rem"
  }} className="border-gray-200 dark:border-gray-700">
      <table className="w-full" style={{
    margin: 0,
    borderCollapse: "collapse",
    tableLayout: "auto"
  }}>
        <thead>
          <tr className="bg-gray-50 dark:bg-gray-800/60 border-b border-gray-200 dark:border-gray-700">
            {Th({
    children: "Operation"
  })}
            {Th({
    children: <span><span style={{
      color: "#16a34a"
    }}>→</span> You send</span>
  })}
            {Th({
    children: "← Server responds"
  })}
            {hasPush && Th({
    children: <span><span style={{
      color: "#2563eb"
    }}>⟵</span> Server pushes</span>
  })}
          </tr>
        </thead>
        <tbody>
          {operations.map((op, i) => <tr key={i} className="border-b border-gray-100 dark:border-gray-800" style={{
    borderBottom: i === operations.length - 1 ? "none" : undefined
  }}>
              {}
              <td className="text-gray-900 dark:text-gray-100" style={{
    padding: "0.625rem 1rem",
    fontWeight: 500,
    whiteSpace: "nowrap"
  }}>
                {op.name}
              </td>

              {}
              <td style={{
    padding: "0.625rem 1rem",
    whiteSpace: "nowrap"
  }}>
                <code className="bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-400" style={{
    padding: "0.125rem 0.375rem",
    borderRadius: "0.25rem",
    fontSize: "0.75rem"
  }}>
                  {op.send}
                </code>
              </td>

              {}
              <td className="text-gray-500 dark:text-gray-400" style={{
    padding: "0.625rem 1rem"
  }}>
                {op.receive}
              </td>

              {}
              {hasPush && <td style={{
    padding: "0.625rem 1rem"
  }}>
                  {op.push ? PushBadge({
    value: op.push
  }) : <span className="text-gray-300 dark:text-gray-600">—</span>}
                </td>}
            </tr>)}
        </tbody>
      </table>
    </div>;
};

export const RegionBaseUrl = ({className = "", showBaseUrl = true}) => {
  const [region, setRegionState] = useState(() => {
    if (typeof window !== 'undefined') {
      return localStorage.getItem("api-region-preference") || "com";
    }
    return "com";
  });
  const [mounted, setMounted] = useState(false);
  const observerRef = useRef(null);
  const isSyncingRef = useRef(false);
  const updateAllContentOnPage = targetRegion => {
    try {
      const domainFrom = targetRegion === "eu" ? "whitebit.com" : "whitebit.eu";
      const domainTo = targetRegion === "eu" ? "whitebit.eu" : "whitebit.com";
      const links = document.querySelectorAll('a');
      links.forEach(link => {
        let href = link.getAttribute('href');
        if (href && href.includes(domainFrom)) {
          link.setAttribute('href', href.replace(domainFrom, domainTo));
        }
      });
      const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
        acceptNode: node => {
          if (node.parentElement?.closest('.region-toggle-component')) {
            return NodeFilter.FILTER_REJECT;
          }
          return node.textContent.includes(domainFrom) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
      });
      let currentNode;
      while (currentNode = walker.nextNode()) {
        currentNode.textContent = currentNode.textContent.replace(new RegExp(domainFrom, 'g'), domainTo);
      }
      console.log(`[RegionSync] Global content updated to ${domainTo}`);
    } catch (e) {
      console.error("[RegionSync] Error updating content:", e);
    }
  };
  const updateRegion = (newRegion, source) => {
    if (region === newRegion) return;
    console.log(`[RegionBaseUrl] Updating to "${newRegion}" (Source: ${source})`);
    if (source === 'observer') {
      isSyncingRef.current = true;
      setTimeout(() => isSyncingRef.current = false, 1000);
    }
    setRegionState(newRegion);
    localStorage.setItem("api-region-preference", newRegion);
    updateAllContentOnPage(newRegion);
    if (source === 'user-click') {
      window.dispatchEvent(new CustomEvent("regionChange", {
        detail: newRegion
      }));
      attemptToUpdateNativeDropdown(newRegion, 0);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 1), 500);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 2), 1500);
    }
  };
  const attemptToUpdateNativeDropdown = (targetRegion, attempt) => {
    if (isSyncingRef.current) return;
    try {
      const targetUrl = targetRegion === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
      const targetDesc = targetRegion === "eu" ? "EU Server" : "Production Server";
      const selects = document.querySelectorAll('select');
      for (const select of selects) {
        if (select.innerHTML.includes('whitebit.com') || select.innerHTML.includes('whitebit.eu')) {
          select.value = targetUrl;
          select.dispatchEvent(new Event('change', {
            bubbles: true
          }));
          return;
        }
      }
      const buttons = Array.from(document.querySelectorAll('button, [role="combobox"]'));
      const serverSelector = buttons.find(btn => {
        if (btn.closest('a') || btn.closest('[class*="card"]') || btn.closest('nav')) {
          return false;
        }
        const txt = btn.textContent || "";
        const isServerDropdown = (txt.includes('Production Server') || txt.includes('EU Server') || txt.includes('WhiteBIT Global Server') || txt.includes('WhiteBIT EU Server')) && !txt.includes('Run') && !txt.includes('Send') || btn.getAttribute('role') === 'combobox';
        return isServerDropdown;
      });
      if (serverSelector) {
        const currentText = serverSelector.textContent || "";
        if (currentText.includes(targetDesc)) return;
        serverSelector.click();
        setTimeout(() => {
          const options = document.querySelectorAll('[role="option"], li, button');
          for (const opt of options) {
            const optText = opt.textContent || "";
            if (optText.includes(targetDesc) || optText.includes(targetUrl)) {
              opt.click();
              return;
            }
          }
        }, 100);
      }
    } catch (e) {
      console.error("[Sync] Error:", e);
    }
  };
  useEffect(() => {
    setMounted(true);
    updateAllContentOnPage(region);
    const handleStorageChange = e => {
      if (e.key === "api-region-preference" && e.newValue) {
        updateRegion(e.newValue, 'storage');
      }
    };
    const handleRegionChange = e => {
      if (e.detail !== region) {
        updateRegion(e.detail, 'event');
      }
    };
    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("regionChange", handleRegionChange);
    observerRef.current = new MutationObserver(mutations => {
      if (isSyncingRef.current) return;
      updateAllContentOnPage(region);
      for (const mutation of mutations) {
        if (mutation.type !== 'childList' && mutation.type !== 'characterData') continue;
        const target = mutation.target;
        const el = target.nodeType === Node.TEXT_NODE ? target.parentElement : target;
        if (el && (el.getAttribute('role') === 'option' || el.closest('[role="listbox"]'))) continue;
        const text = target.textContent || "";
        if (text.includes('WhiteBIT EU Server') || text.includes('https://whitebit.eu') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'eu') updateRegion('eu', 'observer');
          }
        } else if (text.includes('WhiteBIT Global Server') || text.includes('https://whitebit.com') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'com') updateRegion('com', 'observer');
          }
        }
      }
    });
    observerRef.current.observe(document.body, {
      childList: true,
      subtree: true,
      characterData: true
    });
    if (typeof window !== 'undefined') {
      const current = localStorage.getItem("api-region-preference");
      if (current) attemptToUpdateNativeDropdown(current, 'init');
    }
    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("regionChange", handleRegionChange);
      if (observerRef.current) observerRef.current.disconnect();
    };
  }, [region]);
  const apiBaseUrl = region === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
  if (!mounted) return null;
  return <div className={`flex items-center gap-2 flex-wrap my-4 region-toggle-component ${className}`}>
            <span className="text-sm text-gray-500 dark:text-gray-400 font-mono">
                Base URL
            </span>
            <span className="text-sm text-gray-400">(</span>
            <div className="inline-flex bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5 border border-gray-200 dark:border-gray-700">
                <button onClick={() => updateRegion("com", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "com" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .com
                </button>
                <button onClick={() => updateRegion("eu", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "eu" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .eu
                </button>
            </div>
            <span className="text-sm text-gray-400">)</span>
            {showBaseUrl && <>
                    <span className="text-sm text-gray-400">:</span>
                    <a href={apiBaseUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-primary dark:text-primary-light hover:underline">
                        {apiBaseUrl}
                    </a>
                </>}
        </div>;
};

<RegionBaseUrl />

<WsAuthBadge required={channelMeta.authRequired} />

Subscribe to collateral position updates including P\&L, liquidation price, margin, and position side. **Update interval:** 1 second.

<Note>
  This channel does **NOT** support query operations — only subscribe/update/unsubscribe.
</Note>

<WsChannelOverview operations={channelOperations} />

<Note>Connect to `wss://api.whitebit.com/ws` — see [WebSocket Overview](/websocket/overview) for protocol details and keepalive requirements.</Note>

## Rate limits

<WsRateLimits {...channelMeta.rateLimits} />

## Subscribe to positions

<WsSubscribeSteps subscribe={exPositionsSubscribe} subscribeNote="params is always an empty array — subscribes to all open positions." confirmation={exPositionsSubscribeResponse} update={exPositionsUpdate} updateNote="The server pushes positionsMargin_update with a full snapshot of all open positions every second. amount — position size (negative = short). pnl — unrealized profit and loss. liq_price — liquidation price. liq_stage — null (healthy) or margin_call. position_side — LONG, SHORT, or BOTH." unsubscribe={exPositionsUnsubscribe} />

<Tip>
  Each update is a full snapshot of all open positions — local state auto-recovers within 1 second of subscribing. No gap-filling is needed after reconnection.
</Tip>

## Update frequency

The server pushes updates every **1 second** when position data changes.

## Position object

<WsSchemaTable fields={positionRecord} />

## Error codes

<WsErrorCodes errorCodes={channelMeta.errorCodes} />

<RelatedResources>
  * [Margin Positions Events](/websocket/account-streams/margin-positions-events) — real-time margin call and liquidation events for collateral positions.
</RelatedResources>
