Lightstreamer et temps réel Dernière Modification le : 2000-01-01 Le ZIP des Ressources ... Mise en évidence de Ligthstreamer sur Société Générale Le site d'un emetteur de turbo (Société Générale) publie le cours du CAC40, en temps réel. Il y a bien transit de cette information à fréquence élévée, car je la vois dans mon navigateur. C'est magique! Dans l'inspecteur de réseau, sous firefox, je n'arrive pas a identifier le flux. Je passe à Edge. On voit dans l'inspecteur de Edge, filtré par WS (WebSockets), le ligthstreamer, public, diffusé par Société Générale (01-01-2025) Avec Edge, naviguez vers la page contenant le flux du CAC40 Le CAC40 →: h t t p s://sgbourse.fr/underlying-detail?underlyingId=579. Accéder à l'inspecteur (F-12). Les outils de développement s’ouvrent: allez dans l’onglet Réseau. Rechargez la page pour capturer toutes les requêtes. Vous verrez une liste de toutes les requêtes effectuées par la page. Appliquer le filtre WS pour isoler lightstreamer. Cliquer sur la ligne pour la griser et voir l'échange de messages. Echange de messages avec le ligthstreamer de Société Générale: session, souscription, premiers messages wss://warrantspushserver.societegenerale.com/lightstreamer est appelé par https://sgbourse.fr/scripts-XIVD7GUK.js https://sgbourse.fr/main-WMNF32XU.js https://labs.ig.com/streaming-api-guide.html La documentation est ici : https://sdk.lightstreamer.com/ls-python-client/2.2.0/api/index.html Lightstreamer a publié une nouvelle bibliothèque sdk (https://github.com/Lightstreamer/Lightstreamer-lib-client-haxe) pour se connecter aux serveurs Lightstreamer version 7.4.0 et plus. https://www.lightstreamer.com/sdks/ls-generic-client/2.2.0/TLCP%20Specifications.pdf Premiers pas: Lightstreamer, versions et documentations En 2025, commencer avec Lightstreamer peut être très différent de ce que cela a pu être par le passé. Historiquement, LS est conçu pour le Web, donc Java, Javascript. Une bibliothèque officielle Python est apparue récemment, ce qui rendrait obsolète les bibliothèques non-officielles, du moins pour les serveurs LS postérieurs à la version . La version la plus récente, au 01/01/2025, est : 2.2.0 Les exemples (quand on en trouve...) , eux, lui sont antérieurs et ne fonctionnent donc pas. Un exemple en Python est donné sur GitHub Lire →. La librairie la plus récente semble être: lightstreamer.client.ls_python_client_haxe. J'ai eu du mal à l'installer, sans doute à cause de changement de noms au travers des versions. Le Client SDK Lightstreamer sont écrits en Haxe, un langage de programmation libre qui permet la compilation d'une base de code unique vers des cibles multiples. Le projet sur GitHub Lire →. Pour Python Lire → ; La documentation Lire →. J'ai aussi trouvé utile d'avoir la spécification générique sous PDF LightStreamer_TLCP Specifications_060125 (dans le ZIP de ressources) Ma première connection Python - Lightstreamer Un équivalent à 'Hello World" serait de réaliser sa première connexion au serveur de démo, fourni par Ligthstreamer soi-même. C'est mon script: Lightstreamer\stock_list_demo.py from lightstreamer.client.ls_python_client_haxe import * from subscription_listener import * import time def wait_for_input(): input("{0:-^80}\n".format("HIT CR (= RETURN) TO UNSUBSCRIBE AND DISCONNECT FROM LIGHTSTREAMER")) loggerProvider = LSConsoleLoggerProvider(LSConsoleLogLevel.WARN) LSLightstreamerClient.setLoggerProvider(loggerProvider) lightstreamer_client = LSLightstreamerClient("http://push.lightstreamer.com", "DEMO") lightstreamer_client.connect() time.sleep(1) print(f"Status : {lightstreamer_client.getStatus()}") # Making a new Subscription in MERGE mode subscription = LSSubscription(mode="MERGE",items=["item1", "item2", "item3", "item4","item5", "item6", "item7", "item8","item9", "item10", "item11", "item12"], fields=["stock_name", "last_price", "time", "bid", "ask"]) subscription.setDataAdapter("QUOTE_ADAPTER") # Adding the subscription listener to get notifications about new updates subscription.addListener(SubListener()) # Registering the Subscription lightstreamer_client.subscribe(subscription) wait_for_input() # Unsubscribing from Lightstreamer by using the subscription as key # lightstreamer_client.unsubscribe(subscription) # Disconnecting # lightstreamer_client.disconnect() Ce tableau montre ce qui est utile et nécessaire au client Python, version 2.2.0 (Janv. 2025). Pour les versions antérieures sera un peu différent! ParamètreDescriptionLS_modeMode de souscription (MERGE, DISTINCT)LS_itemsItems souscritsLS_fieldsChamps de donnéesLS_adapterNom de l'adaptateur de données Lighstreamer demo avec Python, affichage sur la console CMD Lighstreamer démo, IG, SocGen: tous différents IG fournit une doc complète pour le client Python qui convient à SA Version du Streamer. SG, c'est encore autre chose! Donc pour SG, on revient à la base: WebSocket et cela marche très bien! Serveur Demo SG BNP IG Serveur très récent ancien très ancien récent Protocole LS LS/WebSocket WebSocket LS/WebSocket Session Auto. LS_session ? ? Souscription LS_mode, LS_items,LS_fields, LS_adapter LS_mode, LS_schema,LS_id, LS_data_adapter ? LightStreamer, dans sa version moderne a une étape create_session avec create_session.txt. On ne la voit pas, mais on s'en doute, car l'échange entre Lighstreamer et le navigateur commence par un envoi contenant LS_session. Il vient bien de quelque part! et il est antérieur à l'échange... Quand on inspecte la page d'acceuil on voit un create_session.js. On examine ce que POSTe ce script. le LS_cid est un identifiant nécessaire. "pcYgxn8m8 feOojyA1T681f3g2.pz479mDv" est généralement utilisé LS_user et LS_password: inutiles ici car ressource publique. LS_polling est TRUE : Demande une connexion par groupée (pooled). Sans trop chercher à comprendre (voir TLCP Specification de 27/5/2020) je reprends à l'identique : LS_op2: create, LS_phase: 201, LS_cause: new.api, LS_polling: true, LS_polling_millis: 0, LS_idle_millis: 0, LS_cid: pcYgxn8m8 feOojyA1T681f3g2.pz479mDv, LS_adapter_set: ProxyIcomAdapter, LS_container: lsc Le LS_CID a probablement été généré lors de sa programmation Jouer avec → initiale et est utilisé, inchangé, par SG. On le considère 'stable' car il est déjà cité dans un post datant de 2022 Lire → et aussi ici, dans un travail de Marc-Alexander Richts Lire →. C'est un peu piégeux car on pourrait croire qu'il faille récupérer ou générer ce CID: il n'en est rien... Les difficultés sont consignées dans un mémoire universitaire PDF (dont je mets une traduction en français dans le ZIP de ressources). Je n'ai pas rencontré les mêmes points durs: le registre des 'valeurs' est constitué à partir d'une base fournie par SG au format Excel (trivial avec QSV vs celle fournie par L&S en PDF!). M-A. Richts a éessayé de comprendre les Javascripts de création/souscription. Je me suis contenté de les imiter. Des restrictions sur les bits d'entête n'ont pas été rencontrées chez SG (heureusement, sinon, j'aurais eu du mal) Au total, M-A. Richts montre que c'est faisable mais fastidieux. C'est exact, mais une fois qu'on l'a on a accès au temps réel! ParamètreDescriptionLS_op2Type d'opération : createLS_phasePhase de la connexion : 201LS_causeCause de la création de session : new.apiLS_pollingMode de connexion : trueLS_polling_millisIntervalle de polling : 0LS_idle_millisTemps d'inactivité : 0LS_cidClient ID spécifique (doit être envoyé)LS_adapter_setNom de l'adaptateur utilisé : ProxyIcomAdapterLS_containerConteneur côté client : lsc Pour la souscription, des serveurs différents utiliseront des noms différents pour l'ensemble d'adaptateurs, les éléments, les champs et l'adaptateur de données, qui peuvent être choisis librement. Le Connecteur WebSocket On ouvre la session, ensuite, le connecteur WebSocket est simple à manipuler. Je commence par une requête générale pour récupérer les headers et cookies, et ne pas être embété. import websocket # Connexion WebSocket avec les bons en-têtes ws = websocket.WebSocketApp(url,header=custom_headers(headers),on_open=on_open,on_message=on_message) ws.run_forever() Dans "on_open" on définit les paramètres de la connection, sa validation, puis de la souscription. Dans on_message, on valide le premier message, on souscrit, on filtre un peu les messges recus: en effets, le serveur envoie des messages qui ne nous concernent pas. Il envoie aussi parfois des cotations erronées, faciles à filtrer. Cela peut être du à une gestion de 'phase' en Javascript: point que j'ai négligé (je ne fais pas de gestion de phase) et surmonté avec le filtre. Ouvrir la Session On envoie à "create_session.js" la même charge que celle récupérée sur l'inspecteur de Edge. Je ne cherche que à recupérer le "session_id". Le reste ne me préoccupe pas, pas même la phase, n'ayant pas vu de difference entre la gérer et ne pas la gérer. # Étape 1 : Créer une session via POST url_http = "https://xxx/lightstreamer/create_session.js" headers_http = { "Content-Type": "application/x-www-form-urlencoded", "Origin": "https://xxx.fr", "Referer": "https://xxx.fr/", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0", "Accept": "*/*", "Cache-Control": "no-cache", "Pragma": "no-cache" } data = { "LS_op2": "create", "LS_phase": "201", "LS_cause": "new.api", "LS_polling": "true", "LS_polling_millis": "0", "LS_idle_millis": "0", "LS_cid": "pcYgxn8m8 feOojyA1T681f3g2.pz479mDv", "LS_adapter_set": "ProxyIcomAdapter", "LS_container": "lsc" } response = requests.post(url_http, headers=headers_http, data=data) # Extraire le SessionId de la réponse if "start('" in response.text: session_id = response.text.split("start('")[1].split("'")[0] else: print("Erreur : la réponse ne contient pas 'start('.") exit () print(f"SessionId : {session_id}") De façon étonnante, et contrairement à la documention Lightstreamer, une fois la session crée, le serveur de la banque ne renvoie pas un message tel que CONOK (connexion Okay), mais plus simplement un javascript (que l'on va simplement ignorer) puis un message "bw(0.0)" (pour Notification de bande passante ?). La séquence javascript puis "bw(0.0)" tient lieu de CONOK. Pour bien faire, il faudrait gérer un booléen étagé, pour être le plus orthodoxe possible. Pour un premier jet, on s'en passe. Faire la souscription A la première (et seulement la première) réception du bw(), on envoie le message de souscription qui est juste un copie/collé, simplifié de ce que l'on voit dans l'inspecteur subscription_msg = ( "control\r\n" "LS_mode=MERGE&" "LS_id=FRF_CAC.CBUL&" "LS_schema=BIDTIME%20BID&" "LS_data_adapter=DEFAULT&" "LS_snapshot=true&" "LS_table=1&" "LS_req_phase=2&" "LS_win_phase=1&" "LS_op=add&\r\n" ) On note que les parametres sont ici LS_mode, LS_id, LS_schema et non pas LS_mode, LS_items et LS_fields, comme dans la documentation de la version 'moderne' du LigthStreamer. LS_data_adapter est à l'identique et important. https://www.produitsdebourse.bnpparibas.fr/underlying-detail/?underlying=CAC40&u=255 https://www.produitsdebourse.bnpparibas.fr/js/main.min.js?v=10.0.1-3 wss://websockets.produitsdebourse.bnpparibas.fr/QuotesForUnderlyings Previous Home Next