Historique des prix des tokens de stratégie

Lecture signée des prix de marché USD disponibles pour les allocations actuelles.

Historique des prix de marché

GET /api/v1/b2b/strategies?include=token_price_history&from=YYYY-MM-DD&to=YYYY-MM-DD&resolution=auto ajoute un historique de prix de marché USD aux allocations actuelles Fundamental, Dynamic et Innovation. Les dates UTC from et to sont obligatoires et inclusives. resolution accepte auto (par défaut), daily ou weekly. Sans paramètre, la réponse de composition existante reste identique.

Utilisez une clé d'équipe active avec l'accès API de l'équipe activé. Créer une clé n'active pas cet accès. La signature HMAC porte sur la méthode, le chemin, tous les paramètres de requête, le hash SHA-256 du corps vide et l'horodatage. Les règles de révocation, fenêtre de rejeu de 300 secondes, quotas et Cache-Control: no-store s'appliquent. Chaque lecture consomme une requête. Pour les autres routes et l'activation, consultez le guide External Portfolio Read API.

Le champ frère token_price_history contient requested, effective_to, currency: "USD", resolution, interval_minutes, cadence_tolerance_seconds et tokens. Ce dernier est indexé par l'asset_uid des allocations, avec une seule entrée par token actuel, y compris les allocations à zéro. La lecture ne dépend pas des positions d'un client et ne reconstitue pas les compositions historiques. Les correspondances entre UUID et catalogues sont persistées et vérifiées ; un nouveau token non référencé reste visible avec le statut indisponible.

Nombre de dates UTC inclusivesIntervalle automatique
Jusqu'à 7240 minutes
Jusqu'à 30720 minutes
Jusqu'à 366, année normale ou bissextile incluse4320 minutes
Au-delà10080 minutes, une semaine exacte

daily utilise 1440 minutes et weekly 10080 minutes. La grille commence à minuit UTC au début demandé et inclut la borne finale effective. Sans observation admissible antérieure à ce début, le premier instant observé dans la période est inclus comme ouverture et les points précédents sont omis. Si un prix antérieur sert d'ancre d'ouverture, les points suivent la grille et la borne finale : la première observation dans la période n'est pas automatiquement ajoutée comme point supplémentaire. Les observations admissibles entre deux points restent comptées dans la couverture. Pour aujourd'hui, la fin effective s'arrête à l'heure de la requête. Une date de fin future est refusée.

Chaque point contient price_usd sous forme de chaîne décimale exacte, at, observed_at, carried_forward, source (custody ou cefi), source_asset_id, snapshot_uid et lineage (legacy ou cutover). Ne convertissez pas les prix en nombres flottants. La dernière observation admissible antérieure ou égale au point est utilisée ; à instant strictement égal, custody précède CeFi. observed_at est l'instant effectif du snapshot persisté, pas l'heure native d'un tick de marché. Il n'y a ni interpolation, ni ajout de zéro, ni utilisation d'une observation future. Les observations reportées sont explicitement marquées.

Chaque token comporte status, reasons, coverage et points. La couverture expose les première/dernière observations, observation_count (nombre d'instants effectifs distincts, ancre d'ouverture incluse), max_gap_seconds, première/dernière dates tracées et carried_forward_points. Un intervalle grossier ou des points reportés ne prouvent pas une année complète d'observations.

StatutSignification
availableDonnées vérifiées satisfaisant les critères d'ouverture, de fraîcheur et d'écart ; des points peuvent être reportés.
partialDonnées utilisables avec late_opening, stale_final et/ou sparse_observations.
emptyCatalogues vérifiés sans observation admissible jusqu'à la fin effective ; motif no_observations.
unavailablemapping_missing, mapping_conflict, ambiguous_observations, invalid_observation, source_work_limit ou source_unavailable ; aucun point inventé.

late_opening signale une première observation postérieure au début demandé. stale_final signale une dernière observation précédant la fin effective de plus de 172800 secondes, et sparse_observations un écart interne supérieur à cette valeur. Cette tolérance de qualité de 48 heures n'est pas une garantie de fréquence du fournisseur. L'historique peut commencer tard et présenter des écarts importants ; aucune année entièrement observée n'est promise. Une lecture de source en échec produit unavailable, distinct d'une lecture vérifiée sans résultat (empty).

La limite de l'équipe est de 366 dates inclusives par défaut, au maximum 3660. Une date mal formée produit 400 invalid_query ; une période inversée ou une fin future produit 422 invalid_date_range avant toute erreur de dépassement de durée. Une période passée valide dépassant la limite produit 422 history_range_too_large. Une réponse comporte au maximum 128 tokens, 12000 points potentiels et 4 000 000 octets sérialisés. 422 history_point_limit_exceeded ou 422 response_item_too_large demande une résolution hebdomadaire ou des périodes adjacentes plus courtes : par exemple janvier–juin jusqu'au 2025-06-30, puis à partir du 2025-07-01. Signez chaque URL séparément et comparez les UUID d'allocation avant de combiner les résultats : chaque requête lit la composition actuelle indépendamment. Cette route n'accepte ni curseur ni pagination ; un paramètre non reconnu produit 400 invalid_query.

Exemples signés : courte période, mois, année

Enregistrez le code suivant dans token-price-history.cjs. Configurez EXTERNAL_PORTFOLIO_BASE_URL (origine de l'application), EXTERNAL_PORTFOLIO_API_KEY et EXTERNAL_PORTFOLIO_HMAC_SECRET dans l'environnement sécurisé, puis exécutez node token-price-history.cjs. Il signe trois lectures fraîches et affiche les résultats réellement renvoyés, sans données de marché fabriquées. Pour une résolution imposée, passez resolution: 'daily' ou 'weekly' à signedTokenHistoryRequest et signez la nouvelle URL.

const { createHash, createHmac } = require('node:crypto');

function signedTokenHistoryRequest({
  baseUrl,
  apiKey,
  secret,
  from,
  to,
  resolution = 'auto',
}) {
  const url = new URL('/api/v1/b2b/strategies', baseUrl);
  url.search = new URLSearchParams({
    include: 'token_price_history',
    from,
    to,
    resolution,
  }).toString();
  const canonicalQuery = [...url.searchParams.entries()]
    .sort(([a, av], [b, bv]) => a.localeCompare(b) || av.localeCompare(bv))
    .map(
      ([key, value]) =>
        `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
    )
    .join('&');
  const timestamp = String(Math.floor(Date.now() / 1000));
  const stringToSign = [
    'GET',
    url.pathname,
    canonicalQuery,
    createHash('sha256').update('').digest('hex'),
    timestamp,
  ].join('\n');
  return {
    url: url.href,
    method: 'GET',
    headers: {
      'x-iceblock-key': apiKey,
      'x-timestamp': timestamp,
      'x-signature': `sha256=${createHmac('sha256', secret).update(stringToSign).digest('base64')}`,
    },
  };
}

async function readTokenHistories() {
  const baseUrl = process.env.EXTERNAL_PORTFOLIO_BASE_URL;
  const apiKey = process.env.EXTERNAL_PORTFOLIO_API_KEY;
  const secret = process.env.EXTERNAL_PORTFOLIO_HMAC_SECRET;
  if (!baseUrl || !apiKey || !secret)
    throw new Error('Secure API configuration is required');
  const origin = new URL(baseUrl);
  if (
    origin.protocol !== 'https:' &&
    !['localhost', '127.0.0.1'].includes(origin.hostname)
  ) {
    throw new Error('HTTPS is required');
  }
  const today = new Date();
  const to = today.toISOString().slice(0, 10);
  const midnight = Date.parse(`${to}T00:00:00.000Z`);
  // Inclusive UTC dates: 7, 30, and 366 days. No fabricated market observations.
  for (const [label, daysBack] of [
    ['short', 6],
    ['month', 29],
    ['year', 365],
  ]) {
    const from = new Date(midnight - daysBack * 86400000)
      .toISOString()
      .slice(0, 10);
    const request = signedTokenHistoryRequest({
      baseUrl,
      apiKey,
      secret,
      from,
      to,
    });
    const response = await fetch(request.url, {
      method: request.method,
      headers: request.headers,
    });
    if (!response.ok)
      throw new Error(
        `HTTP ${response.status}; request id ${response.headers.get('x-request-id') ?? 'unavailable'}`,
      );
    const body = await response.json();
    console.log(
      JSON.stringify({
        window: label,
        token_price_history: body.token_price_history,
      }),
    );
  }
}

module.exports = { signedTokenHistoryRequest, readTokenHistories };

if (require.main === module) {
  readTokenHistories().catch((error) => {
    console.error(error.message);
    process.exitCode = 1;
  });
}