Documentation

Bursium simule l'exécution d'ordres sur les cours réels. Deux façons d'automatiser : un script JavaScript hébergé et exécuté par la plateforme, ou votre propre programme connecté à l'API REST.

Clubs et compétitions

  • Un club (club de finance, classe, promotion) a son espace à ses couleurs : logo, bannière, couleur d'accent. Son propriétaire nomme des administrateurs.
  • On rejoint un club avec un code, un lien ou un code QR d'invitation (expiration et nombre d'utilisations fixés par le club), sur la page Rejoindre un club. Chaque invitation porte son code QR : les administrateurs peuvent le projeter pour que toute une classe scanne depuis sa place, ou l'enregistrer en image pour une diapositive ou une feuille imprimée.
  • Les administrateurs lancent des compétitions : dates, capital de départ, frais, classes d'actifs ou liste de symboles autorisés, bots et API autorisés ou non. Les règles se verrouillent au départ.
  • Chaque inscrit reçoit un portefeuille dédié. Les ordres hors règles (avant le départ, après la fin, actif non autorisé, bot ou API interdits) sont refusés. À la fin, les ordres en attente expirent et le classement est figé.
  • Selon les réglages de la compétition, le classement peut être caché jusqu'à la fin, et les positions de chaque participant visibles par les administrateurs (suivi pédagogique, export CSV).
  • Votre bac à sable personnel reste privé et n'apparaît dans aucun classement.

Règles de simulation

  • Chaque compte reçoit un portefeuille de 100 000 $. Vous pouvez en créer d'autres (10 maximum, bots compris).
  • Tout est valorisé en USD. Les actifs cotés dans une autre devise (EUR, GBP, CHF…) sont convertis au taux de change du moment. Les paires crypto en USDT sont assimilées au dollar.
  • Ordres au marché : exécutés immédiatement au meilleur prix disponible (ask à l'achat, bid à la vente, quand la source les publie), avec un slippage défavorable (5 pb par défaut) et des frais (10 pb par défaut) réglables par portefeuille.
  • Ordres en attente — limite, stop, stop-limite et stop suiveur (écart en % ou en devise) : vérifiés à chaque tick planifié, sur le dernier cours et sur les mèches des bougies écoulées depuis la dernière vérification. En cas d'ouverture en écart (gap), un stop s'exécute au cours d'ouverture, jamais à son seuil théorique. Aucune liquidité n'est bloquée : la faisabilité est revérifiée à l'exécution.
  • Take-profit / stop-loss (bracket, OCO) : posés avec l'ordre d'entrée, activés dès son exécution ; le départ de l'un annule l'autre. L'option réduction seulement interdit d'ouvrir ou d'inverser une position. Un ordre en attente peut être modifié (quantité, prix, échéance) : si l'ordre a changé entre-temps, la modification est refusée plutôt qu'appliquée à l'aveugle.
  • Vente à découvert et marge, si la compétition (ou votre portefeuille perso) les active : une quantité négative représente une position vendue à découvert, et l'exposition brute peut atteindre levier × capitaux propres (×1 à ×4). Sous la marge de maintenance, un appel de marge est notifié ; s'il n'est pas résolu au passage suivant, les plus grosses positions sont liquidées au marché. Chaque jour, les positions vendues à découvert paient des frais d'emprunt et la trésorerie négative des intérêts. À ×1 sans vente à découvert, la règle revient exactement à « ne jamais dépasser ses liquidités ».
  • Marché actions fermé : au choix du portefeuille ou de la compétition, l'ordre au marché est exécuté au dernier cours connu, refusé, ou mis en file jusqu'à l'ouverture. La crypto cote en continu.
  • Dividendes et divisions d'actions : appliqués automatiquement chaque jour aux positions détenues. Un dividende crédite le porteur et débite le vendeur à découvert ; une division multiplie la quantité, divise le prix de revient — la valeur de la position est inchangée — et ajuste vos ordres en attente pour qu'un stop ne se déclenche pas à tort. Le reliquat fractionnaire est réglé en trésorerie.
  • Taxe de bourse et courtage plancher, si la compétition les active : le barème réel de la place s'applique aux actions (France 0,30 % à l'achat, Royaume-Uni 0,50 %, Italie 0,10 %), les ETF, indices et cryptos en sont exonérés. Un courtage minimum par ordre peut être exigé, ce qui rend les micro-ordres coûteux, comme dans la réalité.
  • Une valeur qui n'a plus échangé depuis le délai fixé par la compétition n'est plus négociable : certaines paires crypto restent affichées « ouvertes » alors qu'elles dorment depuis des jours.
  • Mesures de performance : au-delà du rendement brut, votre tableau de bord calcule le Sharpe et le Sortino (rendement rapporté au risque pris), la perte maximale depuis un sommet et sa durée, le ratio de Calmar, et reconstitue vos allers-retours (taux de réussite, gain moyen, perte moyenne, facteur de profit). Vous pouvez vous comparer à un indice — S&P 500, CAC 40 ou Bitcoin — avec bêta, alpha et écart de suivi. Une compétition peut classer au rendement ou au Sharpe : classer au seul rendement récompense mécaniquement la prise de risque maximale. En dessous de cinq relevés quotidiens, les ratios sont signalés comme peu fiables plutôt que présentés comme des vérités.
  • Les exécutions d'ordres en attente, les appels de marge, les liquidations et les événements sur titres arrivent dans le centre de notifications (cloche), et dans le navigateur si vous l'autorisez.
  • Crypto : Binance, temps réel. Sur la page d'un actif crypto, le cours, le carnet d'ordres et les dernières transactions arrivent en direct du flux public Binance, reçu par votre navigateur. Actions, ETF et indices : Yahoo Finance, différés d'environ 15 minutes.

Bots hébergés (JavaScript)

Un bot est un script qui définit onTick(ctx). La plateforme l'appelle à la fréquence choisie (au plus toutes les 15 minutes), après avoir préparé les cours, les bougies et l'état de son portefeuille. Les ordres demandés sont exécutés après la fin de la fonction.

bot.js
function onTick(ctx) {
  const rsi = ta.last(ta.rsi(ctx.closes("BTCUSDT"), 14));
  ctx.log("RSI =", rsi.toFixed(1));

  if (rsi < 30 && ctx.position("BTCUSDT") === 0) {
    ctx.buyNotional("BTCUSDT", 2000);            // achat pour 2 000 $ (frais inclus)
  } else if (rsi > 70) {
    ctx.closePosition("BTCUSDT");                // vend toute la position
  }
  ctx.state.lastRsi = rsi;                        // persistant entre exécutions
}

Objet ctx

MembreDescription
ctx.nowDate de l'exécution (ISO 8601).
ctx.symbolsSymboles déclarés dans la configuration (10 max). Seuls ceux-ci sont accessibles.
ctx.paramsParamètres JSON de la configuration (lecture seule).
ctx.stateObjet persistant d'une exécution à l'autre (64 Ko max). Modifiez ses propriétés.
ctx.portfolio{ cash, equity, positions: { SYMBOLE: { qty, avgCost, value, unrealizedPnl } } } en USD.
ctx.openOrdersOrdres en attente : { id, symbol, side, type, quantity, limitPrice, stopPrice }.
ctx.price(s)Dernier cours en USD.
ctx.quote(s){ price, priceUsd, currency, changePct24h, marketState }.
ctx.candles(s)Jusqu'à 200 bougies { t, o, h, l, c, v } en USD, de la plus ancienne à la plus récente.
ctx.closes(s)Raccourci : cours de clôture des bougies.
ctx.position(s)Quantité détenue : positive si longue, négative si vendue à découvert, 0 sinon.
ctx.buy(s, qty, opts?)Achat d'une quantité. opts = { type: 'market' | 'limit' | 'stop' | 'stop_limit' | 'trailing_stop', limitPrice, stopPrice, trailPercent, trailAmount, takeProfit, stopLoss, reduceOnly } (prix en USD ; takeProfit/stopLoss acceptent un prix ou { percent }).
ctx.sell(s, qty, opts?)Vente d'une quantité (ouvre une vente à découvert si le portefeuille l'autorise).
ctx.buyNotional(s, usd, opts?)Achat pour un montant en USD, frais et slippage inclus.
ctx.sellNotional(s, usd, opts?)Vente pour un montant en USD.
ctx.closePosition(s)Solde la position : vend si elle est longue, rachète si elle est courte.
ctx.cancel(id) / ctx.cancelAll(s?)Annule un ordre en attente / tous (d'un symbole).
ctx.log(…) / ctx.warn(…)Journal visible dans l'interface (50 lignes max). console.log fonctionne aussi.

Indicateurs (ta)

Les fonctions renvoient une série de même longueur que l'entrée (NaN tant que la période n'est pas remplie) : ta.sma, ta.ema, ta.rsi, ta.stdev, ta.bollinger (middle, upper, lower), ta.macd (macd, signal, histogram), ta.highest, ta.lowest. Aides : ta.crossover(a, b), ta.crossunder(a, b) (a et b : séries ou nombres) et ta.last(série, recul?).

Limites et sécurité

  • Exécution dans QuickJS (WebAssembly) : pas de réseau, pas de fichiers, pas de timers, onTick doit être synchrone.
  • 300 ms de calcul et 32 Mo de mémoire par exécution, 10 ordres maximum.
  • Après 5 erreurs consécutives, le bot est mis en pause automatiquement.
  • Le bouton « Tester » exécute votre code (même non enregistré) sur les données réelles et simule les ordres sans rien exécuter.

Backtest et rejeu historique

Les deux s'appuient sur le même cœur de simulation que le moteur réel : mêmes frais, même slippage, mêmes règles de marge. Une décision prise à la clôture d'une bougie ne s'exécute qu'à l'ouverture de la suivante — impossible, donc, de profiter d'un cours qu'on ne pouvait pas connaître au moment de décider.

Backtest d'un bot

  • Depuis la page d'un bot, « Backtest » rejoue votre code sur l'historique réel des symboles déclarés, avec les réglages de son portefeuille. Aucun ordre n'est passé et rien n'est enregistré.
  • La courbe obtenue est confrontée à un achat-conservation sur la même période — battre le marché est plus difficile qu'il n'y paraît —, avec Sharpe, perte maximale et statistiques d'allers-retours.
  • Limites assumées : le taux de change est figé sur toute la période (un backtest sur une valeur hors dollar ignore l'effet devise) et les brackets OCO ne sont pas simulés.

Rejeu historique

  • Une période de marché réelle, anonymisée : ni symbole, ni date, et les prix sont ramenés en base 100 (la première bougie vaut 100). Les titres s'appellent « Titre A », « Titre B »…
  • La partie se joue dans votre navigateur, bougie par bougie : vous ne voyez jamais la suite, et le temps n'avance que lorsque vous le décidez.
  • Vos décisions sont journalisées en temps de scénario (rang de bougie, jamais l'heure de votre machine). À la clôture, le serveur recalcule la partie à partir de ce journal : c'est ce résultat, et lui seul, qui est classé. Manipuler l'horloge de son poste ou recharger la page n'y change rien.
  • Le classement reste indicatif : quelqu'un qui reconnaît l'épisode rejoué garde un avantage que l'anonymisation ne supprime pas. L'intérêt est dans le débriefing, pas dans le rang — n'en faites pas une note d'examen.
  • Les administrateurs d'un club créent les scénarios depuis l'onglet Rejeu de leur espace : un à trois titres, une profondeur d'historique, une fenêtre d'ouverture facultative. L'historique est scellé à la création, si bien qu'un scénario reste identique même si le fournisseur révise ses données ensuite.
  • Hors crypto, l'historique intraday ancien n'existe pas chez le fournisseur : les scénarios sur actions se jouent en bougies journalières.

Apprendre

Quatre surfaces — leçons, missions, badges, bilan — reposent sur un socle unique : un objectif décrit une condition vérifiable sur votre activité réelle. Aucun objectif ne récompense le fait d'avoir gagné de l'argent : le résultat dépend surtout du marché, alors que poser un stop ou justifier un ordre sont des gestes qui s'apprennent.

  • Leçons et quiz : quatre leçons de cinq à sept minutes (types d'ordres, risque et taille de position, coûts, biais), chacune suivie d'un quiz. Il faut 80 sur 100 pour valider l'objectif correspondant, et le meilleur score est conservé — rater une seconde tentative n'efface jamais une réussite. Les corrigés n'arrivent qu'après la tentative.
  • Missions : un parcours progressif, chaque étape s'ouvrant quand la précédente est terminée. Les fondations d'abord — comprendre un ordre, décider sa sortie à froid, mesurer le risque, compter les coûts — puis se relire.
  • Badges : des reconnaissances ponctuelles (premier stop posé, trois classes d'actifs négociées, appel de marge traversé sans liquidation, carnet tenu). Un acquis est définitif : il ne se reperd pas si une position est soldée ensuite.
  • Bilan de biais : sur vos allers-retours clôturés, la plateforme cherche cinq schémas — garder ses perdants plus longtemps que ses gagnants, pertes moyennes supérieures aux gains moyens, sur-trading, concentration, augmentation de la mise après une perte. Chaque constat est chiffré pour que vous puissiez le vérifier.
  • En dessous de huit allers-retours clôturés, le bilan ne dit rien — et l'affiche franchement. Annoncer un biais sur trois opérations reviendrait à présenter du hasard comme un enseignement.
  • Les administrateurs d'un club suivent la progression de leur classe depuis l'onglet Apprentissage : leçons lues, quiz réussis, objectifs et badges. Ce tableau ne montre aucune performance de portefeuille.

Outils de marché

  • Indicateurs sur le graphique d'un instrument : moyennes mobiles simples (20 et 50), exponentielles (12 et 26) et bandes de Bollinger. Ce sont exactement les calculs offerts à vos bots via ta — un test de parité exécute le bac à sable et compare les séries, pour que les deux ne puissent pas diverger. Ils sont recalculés à chaque nouvelle bougie, pas à chaque battement du flux temps réel : une moyenne mobile ne se lit que sur des bougies closes.
  • Liste de suivi et alertes de prix depuis la page Suivi. Une alerte se déclenche quand le cours franchit votre seuil, dans le sens choisi, et vous notifie une seule fois : elle est ensuite archivée avec le cours constaté, jamais rejouée. Les alertes sont évaluées à chaque tick planifié, donc pas en continu.
  • Screener : filtre l'univers sur la classe d'actif, la variation, le PER, le rendement et le secteur. Les filtres vivent dans l'adresse de la page, si bien qu'une sélection se partage par un simple lien — pratique pour envoyer une liste à sa classe.
  • Fondamentaux sur la fiche d'un instrument : capitalisation, PER, rendement, marge, croissance, bêta, secteur. Un tiret signale une donnée que le fournisseur ne publie pas ; elle n'est jamais remplacée par zéro.
  • Limites assumées : ces écrans n'interrogent pas les fournisseurs en direct — ils lisent ce que le dernier tick a enregistré, et affichent la date du relevé. Les indices et les cryptomonnaies n'ont pas de fondamentaux. Le RSI et le MACD, qui demandent un panneau séparé sous le graphique, ne sont pas encore disponibles.

API REST (bots externes)

Créez une clé dans Clés API (droits lecture et/ou trading, restriction optionnelle à un portefeuille), puis envoyez-la dans l'en-tête Authorization.

URL de base
https://bursium.rplane.fr/api/v1
Terminal (sous Windows, utilisez curl.exe)
curl -H "Authorization: Bearer pt_live_VOTRE_CLE" "https://bursium.rplane.fr/api/v1/portfolios"

curl -X POST "https://bursium.rplane.fr/api/v1/portfolios/PORTFOLIO_ID/orders" \
  -H "Authorization: Bearer pt_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{"symbol":"AAPL","side":"buy","type":"market","notional":"1000","clientOrderId":"mon-ordre-001"}'

Points d'accès

RequêteDroitDescription
GET /melectureUtilisateur et informations sur la clé.
GET /instruments?search=&assetClass=lectureRecherche d'instruments (crypto, stock, etf, index).
GET /quotes?symbols=BTCUSDT,AAPL&fresh=1lectureCotes (50 symboles max) : price, bid, ask, priceUsd, fxRate… fresh=1 ignore le cache.
GET /candles?symbol=&interval=1h&limit=200lectureBougies : 1m, 5m, 15m, 1h ou 1d (500 max).
GET /portfolioslectureVos portefeuilles valorisés (scope personal ou competition, avec les règles).
GET /competitionslectureVos inscriptions : compétition, club, phase, règles, portefeuille associé.
GET /competitions/{id}/leaderboardlectureClassement (membres du club, selon la visibilité choisie).
POST /portfoliostradingCrée un portefeuille « API » : { name, initialCash? }.
GET /portfolios/{id}lectureDétail avec positions, réglages et bloc margin (exposition, levier, pouvoir d'achat).
GET /portfolios/{id}/positionslecturePositions valorisées.
GET /portfolios/{id}/orders?status=openlectureOrdres (filtre par statut, pagination before=).
POST /portfolios/{id}/orderstradingPasse un ordre (voir ci-dessous).
GET /portfolios/{id}/orders/{orderId}lectureUn ordre et son exécution.
PATCH /portfolios/{id}/orders/{orderId}tradingModifie un ordre en attente : { version, quantity?, limitPrice?, stopPrice?, trailAmount?, trailPercent?, expiresAt? }. Version périmée → 409.
DELETE /portfolios/{id}/orders/{orderId}tradingAnnule un ordre en attente (et ses sorties liées).
POST /portfolios/{id}/synctradingTrading rapide : rafraîchit les cotes de vos ordres en attente (sans cache) et les évalue immédiatement, sans attendre le tick.
GET /portfolios/{id}/trades?limit=&before=lectureTransactions exécutées (pagination par date).
GET /portfolios/{id}/history?resolution=daylectureHistorique de valeur (hour ou day).
GET /portfolios/{id}/analytics?benchmark=^GSPClectureSharpe, Sortino, perte maximale, allers-retours, contribution par ligne et comparaison à un indice.

Passer un ordre

ChampDescription
symbolSymbole, ex. BTCUSDT, AAPL, MC.PA.
sidebuy ou sell.
typemarket (défaut), limit, stop, stop_limit ou trailing_stop.
quantity | notionalSoit une quantité, soit un montant en USD (exclusifs).
limitPrice / stopPriceRequis pour limit / stop (les deux pour stop_limit), dans la devise de cotation.
trailAmount | trailPercentStop suiveur : écart en devise de cotation, ou en % (0,01 à 50).
takeProfit / stopLossSorties liées (OCO) : { price } dans la devise de cotation, ou { percent } depuis le prix d'entrée.
reduceOnlytrue : l'ordre ne peut que réduire la position.
noteOptionnel (1 000 car.) : justification du trade, visible dans le journal.
expiresAtOptionnel, date ISO d'expiration d'un ordre en attente.
clientOrderIdOptionnel (64 car.) : rend la requête idempotente. Un renvoi renvoie l'ordre existant (200).

Réponses : 201 ordre créé, 200 doublon (clientOrderId), 422 ORDER_REJECTED ordre refusé (liquidités, position…) avec son motif. Les décimaux sont renvoyés sous forme de chaînes.

Erreurs et limites

Format : { "error": { "code": "…", "message": "…" } }. Codes : 400 requête invalide, 401 clé absente ou invalide, 403 droit ou portefeuille non autorisé, ou ordre contraire aux règles de la compétition, 404 introuvable, 409 conflit, 422 rejet, 429 limite atteinte (en-tête Retry-After). Limites : 120 requêtes et 30 ordres par minute et par clé.

Exemple complet en Python

bot.py — pip install requests
import time, uuid, requests

API = "https://bursium.rplane.fr/api/v1"
HEADERS = {"Authorization": "Bearer pt_live_VOTRE_CLE"}
SYMBOL = "ETHUSDT"

def get(path, **params):
    r = requests.get(f"{API}{path}", headers=HEADERS, params=params, timeout=10)
    r.raise_for_status()
    return r.json()["data"]

def order(portfolio_id, **body):
    body.setdefault("clientOrderId", str(uuid.uuid4()))
    r = requests.post(f"{API}/portfolios/{portfolio_id}/orders", headers=HEADERS, json=body, timeout=10)
    data = r.json()
    if r.status_code == 422:
        print("Ordre rejeté :", data["error"]["message"])
    r.raise_for_status() if r.status_code not in (200, 201, 422) else None
    return data.get("data")

portfolio = get("/portfolios")[0]
pid = portfolio["id"]

while True:
    closes = [c["c"] for c in get("/candles", symbol=SYMBOL, interval="1h", limit=60)["candles"]]
    fast, slow = sum(closes[-10:]) / 10, sum(closes[-30:]) / 30
    held = next((p for p in get(f"/portfolios/{pid}/positions") if p["symbol"] == SYMBOL), None)

    if fast > slow and not held:
        print(order(pid, symbol=SYMBOL, side="buy", type="market", notional="1000"))
    elif fast < slow and held:
        print(order(pid, symbol=SYMBOL, side="sell", type="market", quantity=held["quantity"]))

    time.sleep(15 * 60)