Skip to main content
Glama
PNX89
by PNX89

QUOTEZ

Marktdaten für Agenten. Nur lesend durch Konstruktion, nicht durch Konfiguration.

CI Python License: MIT

Ein MCP-Server, der MetaTrader 5-Marktdaten als typisierte, schreibgeschützte Tools bereitstellt, die ein LLM-Agent aufrufen kann. Python 3.11 oder neuer, eine Laufzeitabhängigkeit, stdio-Transport. Das Badge endet bei 3.13, weil dort die Klassifikatoren enden; CI führt auch einen 3.14-Durchlauf durch, der als hinweisend markiert ist, und 3.14 kommt zum Badge hinzu, sobald es lange genug grün war, um ein Versprechen und nicht nur eine Hoffnung zu sein.

Ein Agent ist nur so gut wie die Tools, die man ihm gibt, und bei Marktdaten richtet ein schlampiges Tool echten Schaden an. Das Modell gibt alles, was ein Tool zurückgibt, als Tatsache wieder, sodass eine Nutzlast ohne Einheiten, Zeitzone und Herkunft zu einem selbstbewussten Satz über einen Preis wird, auf den jemand reagieren könnte. QUOTEZ antwortet mit generierten Ausgabeschemata anstelle von Textblobs, UTC überall, einem synthetic-Flag auf jeder Nutzlast und keinem Schreibpfad im Code.

Dies ist ein Tool, das so gebaut ist, dass es keinen Schaden anrichten kann – eine kleinere und besser überprüfbare Frage, als ob einem Agenten als Ganzes mit Tools vertraut werden kann. Diese größere Frage ist Sache von QUELLZ.

Umfang und Grenzen

  • Nur lesend: kein order_send, kein order_check, kein symbol_select, keinerlei Schreibvorgänge.

  • Live-MetaTrader-Daten benötigen Windows und ein laufendes Terminal; die Räder sind nur win_amd64.

  • Die Standardquelle spielt generierte Daten ab und kennzeichnet jede Nutzlast mit synthetic: true.

  • Zeiten sind UTC und jeder Balken wird durch seinen Eröffnungskurs, den linken Rand seines Intervalls, gekennzeichnet.

Beispiel-Agenten-Sitzung

Echte Ausgabe, kein Einfügen. Neu generieren mit uv run python examples/agent_session.py; tests/test_readme.py prüft diesen Block Byte für Byte gegen die Standardausgabe dieses Befehls. Die Wiedergabepreise werden generiert, nicht von einem Markt aufgezeichnet.

QUOTEZ over an in-memory MCP client, source=replay.
Every price below is generated. This repository bundles no real market data.

>>> list_symbols(group="*FX*")
{
  "source": "replay",
  "synthetic": true,
  "count": 2,
  "symbols": [
    {"name": "SYNTH_FX_ALPHA", "description": "Synthetic FX pair Alpha", "digits": 5, "point": 1e-05},
    {"name": "SYNTH_FX_BETA", "description": "Synthetic FX pair Beta", "digits": 3, "point": 0.001}
  ]
}

>>> get_quote(symbol="SYNTH_FX_ALPHA")
{
  "symbol": "SYNTH_FX_ALPHA",
  "time": "2026-06-12T13:59:00Z",
  "bid": 1.08044,
  "ask": 1.08056,
  "spread_points": 12,
  "source": "replay",
  "synthetic": true
}

>>> get_bars(symbol="SYNTH_FX_ALPHA", timeframe="H1", count=5)
{
  "symbol": "SYNTH_FX_ALPHA",
  "timeframe": "H1",
  "source": "replay",
  "synthetic": true,
  "count": 5,
  "bars": [
    {"time": "2026-06-12T08:00:00Z", "open": 1.07985, "high": 1.08231, "low": 1.07978, "close": 1.08125, "tick_volume": 4257, "spread": null},
    {"time": "2026-06-12T09:00:00Z", "open": 1.08125, "high": 1.0844, "low": 1.08113, "close": 1.08302, "tick_volume": 2501, "spread": null},
    {"time": "2026-06-12T10:00:00Z", "open": 1.08302, "high": 1.08439, "low": 1.08298, "close": 1.08368, "tick_volume": 1643, "spread": null},
    {"time": "2026-06-12T11:00:00Z", "open": 1.08368, "high": 1.08395, "low": 1.08036, "close": 1.08097, "tick_volume": 1570, "spread": null},
    {"time": "2026-06-12T12:00:00Z", "open": 1.08097, "high": 1.08284, "low": 1.08084, "close": 1.08159, "tick_volume": 2589, "spread": null}
  ]
}

>>> symbol_info(symbol="SYNTH_FX_ALPHA")
{
  "name": "SYNTH_FX_ALPHA",
  "description": "Synthetic FX pair Alpha",
  "digits": 5,
  "point": 1e-05,
  "spread": 12,
  "spread_float": true,
  "trade_stops_level": 10,
  "trade_freeze_level": 0,
  "trade_tick_value": 1.0,
  "trade_tick_size": 1e-05,
  "trade_contract_size": 100000.0,
  "volume_min": 0.01,
  "volume_max": 100.0,
  "volume_step": 0.01,
  "currency_base": "SYA",
  "currency_profit": "SYN",
  "currency_margin": "SYA",
  "source": "replay",
  "synthetic": true
}

A symbol that does not exist, to show what the model actually sees:

>>> get_quote(symbol="NOT_A_SYMBOL")
is_error: true
Error executing tool get_quote: Symbol 'NOT_A_SYMBOL' is not available on this server.

Schnellstart

Ein Befehl, nichts zu konfigurieren, keine MetaTrader-Installation irgendwo:

uvx --from git+https://github.com/PNX89/QUOTEZ quotez --source replay

QUOTEZ ist nicht auf PyPI veröffentlicht, daher ist die Git-Form die Installation; fügen Sie @main, einen Tag oder einen Commit hinzu, um einen Referenzpunkt festzulegen, gemäß uvs Abhängigkeitsdokumentation. Es scheint dann zu hängen, weil stdout der JSON-RPC-Draht ist und ein Host ihn steuert. Um es ohne Host in Aktion zu sehen, klonen Sie das Repository und führen Sie die Beispielsitzung aus, die denselben Server von einem prozessinternen Client aus steuert:

git clone https://github.com/PNX89/QUOTEZ && cd QUOTEZ
uv run python examples/agent_session.py

Unter Windows, gegen ein bereits laufendes und angemeldetes Terminal:

uvx --from "quotez[mt5] @ git+https://github.com/PNX89/QUOTEZ" quotez --source mt5

Das Konsolenskript ist der einzige Einstiegspunkt, der Flags akzeptiert, und Flags gewinnen gegenüber der Umgebung. mcp run src/quotez/server.py bedient diesen Server auch über das Modul-Level mcp-Global, leitet aber nichts weiter, daher liest dieser Pfad stattdessen die Variablen.

Flag

Umgebungsvariable

Standard

Bedeutung

--source

QUOTEZ_SOURCE

replay

replay liest die gebündelten generierten Dateien, mt5 liest ein Live-Terminal

--symbols

QUOTEZ_SYMBOLS

leer

Kommagetrennte Whitelist, Groß-/Kleinschreibung nicht beachtend. Leer gibt alles frei, was die Quelle hat

--max-bars

QUOTEZ_MAX_BARS

1000

Maximal Balken, die ein Aufruf zurückgeben darf, 1 bis 5000

--log-level

QUOTEZ_LOG_LEVEL

INFO

Protokollierungsschwelle. Aufzeichnungen gehen immer nach stderr, weil stdout der Draht ist

Verbinden mit einem Host

Die Hosts sind sich über den Konfigurationsschlüssel nicht einig, und mcpServers versus servers falsch zu setzen, ist der übliche Grund, warum ein Server nie erscheint.

Host

Datei

Schlüssel

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json auf macOS, %APPDATA%\Claude\claude_desktop_config.json auf Windows

mcpServers

Cursor

.cursor/mcp.json

mcpServers

VS Code

.vscode/mcp.json

servers, und fügen Sie "type": "stdio" neben command hinzu

Claude Code

keine Datei, CLI verwenden

claude mcp add quotez -- uv tool run --from git+https://github.com/PNX89/QUOTEZ quotez --source replay

{
  "mcpServers": {
    "quotez": {
      "command": "/absolute/path/to/uv",
      "args": ["tool", "run", "--from", "git+https://github.com/PNX89/QUOTEZ",
               "quotez", "--source", "replay"]
    }
  }
}

command muss der absolute Pfad von which uv sein. Ein Host startet den Server mit einem nahezu leeren PATH, daher ist ein bloßes uv der mit Abstand häufigste Grund, warum ein Server stillschweigend keine Verbindung herstellt.

Tools

Acht Tools, in dieser Reihenfolge registriert, was der Reihenfolge von tools/list entspricht; Clients cachen diese Liste, daher ist die Reihenfolge absichtlich festgelegt. Eine Ressource, symbols://list, liefert dasselbe Instrumentenuniversum als application/json.

Tool

Argumente

Rückgabe

Zugriff

Wiedergabequelle

MetaTrader-Quelle

list_symbols

group optional, MetaTrader-Gruppensyntax

SymbolList

lesen

4 generierte Instrumente

symbols_get(group=...)

get_quote

symbol

Quote

lesen

abgeleitet vom letzten gespeicherten Balken

symbol_info_tick

get_bars

symbol, timeframe, count (1 bis 5000, begrenzt durch --max-bars)

BarSeries

lesen

M1 lokal hochaggregiert

copy_rates_from_pos

get_bars_range

symbol, timeframe, start, end

BarSeries

lesen

M1 lokal hochaggregiert

copy_rates_range

symbol_info

symbol

SymbolSpec

lesen

aus symbols.json

symbol_info

get_account

keine

Account

lesen

Platzhalterzahlen, synthetic: true

account_info, Login maskiert

list_positions

keine

PositionList

lesen

immer leer

positions_get

list_orders

keine

OrderList

lesen

immer leer

orders_get

list_symbols verwendet MetaTraders eigene Gruppensyntax anstatt eine eigene zu erfinden: *-Platzhalter am Anfang und Ende eines Musters, kommagetrennte Bedingungen und ! zur Negation einer Bedingung. Einschlüsse müssen vor Ausschlüssen kommen, also ist "*, !*USD*" alles außer den USD-Instrumenten, während "!*USD*, *" alles abdeckt. Mt5Source übergibt die Zeichenkette an symbols_get; die Wiedergabequelle führt dieselbe Syntax durch quotez.groups aus, sodass beide einen Filter identisch beantworten.

Jedes Tool gibt ein Pydantic-Modell zurück, sodass das SDK ein outputSchema aus der Rückgabeannotation ableitet, structuredContent füllt und die Nutzlast validiert, bevor sie den Server verlässt. Ein BaseModel wird unverpackt verwendet, weshalb get_bars ein Objekt mit einem bars-Schlüssel zurückgibt, anstatt {"result": ...}.

Wie es funktioniert

flowchart LR
    host["MCP host<br/>Claude Desktop, Cursor, VS Code"]
    server["quotez.server<br/>8 tools, 1 resource"]
    proto["MarketDataSource<br/>Protocol"]
    replay["ReplaySource<br/>bundled CSVs, any OS"]
    mt5["Mt5Source<br/>Windows only, lazy import"]
    term["MetaTrader 5 terminal"]
    host -- "JSON-RPC over stdio" --> server
    server --> proto
    proto --> replay
    proto --> mt5
    mt5 -- "read calls only" --> term

MarketDataSource ist die Nahtstelle, gegen die der gesamte Server geschrieben ist. Nichts darüber importiert MetaTrader5, und Mt5Source löst die Erweiterung innerhalb eines privaten Helfers beim ersten Gebrauch auf, nicht beim Modulimport, sodass import quotez funktioniert, wo kein Rad existiert. Das macht ReplaySource zu einer erstklassigen Implementierung anstelle eines Mocks: Die Toolschicht kann die beiden nicht unterscheiden, sodass die gesamte Suite den echten Codepfad ohne installiertes Terminal durchläuft.

Die gebündelten Daten sind vier generierte Instrumente (SYNTH_FX_ALPHA, SYNTH_FX_BETA, SYNTH_IDX_GAMMA, SYNTH_MTL_DELTA), jeweils 3600 M1-Balken, 08:00 bis 14:00 UTC an Wochentagen vom 01.06.2026 bis 12.06.2026, mit neun Sitzungsunterbrechungen, acht über Nacht und eine über ein Wochenende, denn eine lückenlose Serie ist die Serie, die einen Aggregationsfehler verbirgt. scripts/generate_replay_data.py hat die Dateien einmalig aus einem gesetzten random.Random erstellt und die Ausgabe ist eingecheckt. CSVs werden über importlib.resources gelesen, nie über Path(__file__).parent, was in einem Checkout funktioniert und unter der gezippten Installation, die uvx durchführt, bricht.

Tools und Ressourcen sind nicht dasselbe

Ein Tool ist das, was das MODELL aufzurufen entscheidet; eine Ressource ist das, was die ANWENDUNG zu laden entscheidet. get_bars ist modellgesteuert: Es wählt mitten in der Argumentation ein Symbol, einen Zeitrahmen und eine Anzahl aus. symbols://list ist anwendungsgesteuert: Ein Host heftet das Universum einmalig in den Kontext, bevor das Modell irgendetwas entschieden hat. Deshalb ist es keine beiläufige Duplizierung von list_symbols, einer gefilterten Suche, die das Modell absichtlich ausführt.

Die offensichtliche nächste Ressource, bars://{symbol}/{timeframe}, wurde bewusst nicht gebaut: Sie dupliziert get_bars für dieselben Daten, und ein URI mit Platzhaltern ist eine Ressourcenvorlage, die resources/list für resources/templates/list verlässt und von vielen Hosts schlecht oder gar nicht dargestellt wird. Ein Test stellt sicher, dass keine Ressourcenvorlagen registriert sind.

Zeitrahmenaggregation

Die Wiedergabequelle speichert einen Basiszeitrahmen, M1, und quotez.aggregate aggregiert M5, M15, M30, H1, H4 und D1 daraus hoch. Eine gespeicherte Kopie, eine Hochaggregation, eigenständig testbar, was wichtig ist, weil seine Fehlerart still ist: Eine falsche Aggregation liefert auf unbestimmte Zeit plausible Zahlen und löst nie eine Ausnahme aus.

Die MetaTrader-Quelle aggregiert nichts hoch. Ein Terminal enthält bereits jeden Zeitraum, daher wird es direkt nach dem Zeitrahmen gefragt; eine erneute Ableitung aus M1 wäre langsamer und würde von den Diagrammen abweichen, die der Bediener geöffnet hat. Die beiden Quellen beantworten daher denselben Aufruf bei D1, H4 und spread geringfügig unterschiedlich, was in den Einschränkungen steht und nicht Ihnen überlassen wird, herauszufinden.

Die Invarianten der Hochaggregation, von denen jede ein Testname ist:

  1. M1 ist der einzige Basis-Zeitrahmen. Alles Gröbere wird daraus abgeleitet.

  2. Buckets sind Wanduhrzeit, berechnet durch Ganzzahldivision der Epochensekunde, niemals durch gruppenweise Anordnung von N Zeilen.

  3. Ziele sind ganzzahlige Vielfache von 60 Sekunden. Alles andere löst InvalidRequest aus.

  4. OHLC ist erster Eröffnungskurs, maximaler Höchstkurs, minimaler Tiefstkurs, letzter Schlusskurs.

  5. tick_volume wird summiert. spread nicht: Es ist eine zeitpunktbezogene Eigenschaft eines Quotes, daher meldet ein aggregierter Balken null.

  6. Balken werden nach ihrem linken Rand in UTC bezeichnet.

  7. Ein unvollständiger nachlaufender Bucket wird verworfen, anstatt als Teilbalken ausgegeben zu werden. Ein Bucket wird nur dann ausgegeben, wenn die Eingabe einen Balken zu oder nach dem Ende dieses Buckets enthält.

  8. Leere Eingabe gibt eine leere Liste zurück.

Invariante 2 verdient ihre Tests. Gruppierung nach Position stimmt mit der Wanduhr-Bucketbildung bei einer lückenlosen Serie überein und weicht ab, sobald eine Lücke auftritt: Die Gruppierung von 360 Balken-Sitzungen in Vierergruppen setzt den Schlusskurs von Freitag und den Eröffnungskurs von Montag in einen Balken und nennt ihn eine Vier-Stunden-Kerze. Invariante 7 ist ihr Gegenstück, denn das Ende einer Sitzung ist nicht dasselbe Ereignis wie das Ausgehen der Daten.

Sicherheitsdesign

Die Behauptung ist strukturell, nicht konfigurierbar. Diese Codebasis enthält keinen Schreibpfad. Es gibt kein order_send, kein order_check, kein symbol_select, keine MarketWatch-Mutation und keinen Dateischreibvorgang irgendwo in src/quotez/. Keine Konfiguration kann einen Schreibvorgang aktivieren, weil es nichts zu aktivieren gibt.

Zwei Tests stellen dies sicher, und der zweite ist der, der etwas bedeutet. Der erste durchsucht das Paket nach diesen drei MetaTrader-Aufrufen: günstig, deckt jede Datei ab und wird durch einen zur Laufzeit zusammengesetzten Namen erfüllt. Der zweite durchläuft den AST von mt5source.py und behauptet stattdessen die positive Eigenschaft, dass die Menge der Attribute, die dieses Paket vom Terminalmodul liest, genau die Leseaufrufe sind, die sein eigenes Docstring nennt, plus die sieben Zeitrahmenkonstanten, ohne dass etwas über getattr erreicht und ohne dass etwas an eine zweite Variable gebunden wird. _mt5() gibt das gesamte MetaTrader5-Modul zurück, daher beweist das Fehlen von drei Namen unter mehreren hundert Attributen für sich genommen sehr wenig. Fünf absichtlich defekte Code-Schnipsel werden gegen diesen Durchlauf geprüft, sodass bekannt ist, dass der Durchlauf selbst fehlschlägt, wenn er sollte.

Jedes Tool wird mit ToolAnnotations(read_only_hint=True, open_world_hint=False) deklariert. Diese Deklaration ist eine Höflichkeit gegenüber Clients und nichts weiter: Die MCP-Spezifikation teilt Clients mit, Tool-Annotationen als nicht vertrauenswürdig zu behandeln, es sei denn, sie stammen von einem vertrauenswürdigen Server. read_only_hint=True beschreibt das Tool, es schränkt den Client nicht ein, und die Eigenschaft, die ein Prüfer überprüfen kann, ist das Fehlen der Aufrufe und nicht das Vorhandensein des Flags. Übertragen auf die eigenen Sicherheitsüberlegungen für Tools der Spezifikation, einschließlich der Anforderung, die dieser Server nicht erfüllt:

Spezifikationsanforderung

QUOTEZ

Wo

Alle Tool-Eingaben validieren

Ja

JSON Schema abgeleitet von den Typannotationen, Literal-Zeitrahmen, Field(ge=1, le=5000) auf count, plus Laufzeitprüfungen in den Handlern

Angemessene Zugriffskontrollen implementieren

Ja

Die Symbol-Whitelist wird auf jedes Tool und auf die Ressource angewendet, nicht nur auf die Getter

Tool-Aufrufe ratenbegrenzen

Nein

nicht implementiert und in den Einschränkungen aufgeführt. Ein Stdio-Server ist ein Kindprozess genau eines Hosts, daher obliegt die Ratenbegrenzung dem Host

Tool-Ausgaben bereinigen

Ja

Der Kontologin wird auf die letzten vier Ziffern maskiert, die Namen von Broker, Server und Kontoinhaber werden nie zurückgegeben, und synthetic ist ein Pflichtfeld in jeder Nutzlast

Ein blockiertes Symbol wird als SymbolNotFound mit der Meldung gemeldet, die ein Tippfehler erhalten würde: „Symbol 'X' ist auf diesem Server nicht verfügbar.“ Ein eindeutiges „nicht erlaubt“ würde die Whitelist zu einem Entdeckungsorakel für Instrumente machen, die ein Betreiber nicht offenlegen wollte.

Fehler nehmen einen von zwei Kanälen, ausgewählt danach, ob ein intelligenteres Modell den Fehler hätte vermeiden können. Ein falsch geschriebenes Symbol könnte das, daher sind SymbolNotFound und InvalidRequest gewöhnliche Ausnahmen, die zu Tool-Fehlern werden, die das Modell lesen und erneut versuchen kann. Ein Terminal, das nicht läuft, könnte das nicht, daher wird SourceUnavailable als MCPError ausgelöst, ein Protokollfehler ohne jegliches Ergebnis. Nichts hier gibt einen Fehlerstring zurück: Ein zurückgegebener String trägt is_error=False und wird als erfolgreiche Antwort gelesen. Ein Test ruft jedes Tool mit schlechter Eingabe auf und behauptet das Flag.

Designentscheidungen

mcp>=2.0.0,<3 und MCPServer, nicht der v1-Pin und FastMCP. Das SDK bietet immer noch mcp>=1.28,<2 für Leute, die noch nicht migriert haben, aber ein Server aus der v1-Ära verrät sich in drei Sekunden: from mcp.server.fastmcp import FastMCP. Die Migrationsanleitung enthält die Umbenennungen. Der Low-Level-Server war die Alternative und umschließt Rückgabewerte nicht mehr automatisch, was bedeutet hätte, JSON Schema für acht Tools von Hand zu schreiben.

Typisierte Pydantic-Rückgaben, keine Textblobs. Die meisten öffentlichen MCP-Server geben Prosa zurück und überlassen dem Modell das Parsen. Hier ist die Rückgabeannotation das Ausgabeschema, daher kostet Typisierung nichts und bringt Validierung, bevor die Nutzlast den Server verlässt.

Zwei Balken-Tools, nicht eines mit optionalen Argumenten. JSON Schema kann keine gegenseitige Ausschließlichkeit ausdrücken, daher würde ein einzelnes get_bars(count or start..end) „entweder oder, aber nicht beides“ als Prosa auf das Modell abwälzen. Zwei Tools haben zwei vollständig gültige Schemata, und die Fehlerklasse „beide angegeben, keins angegeben“ existiert nicht mehr.

Generierte Daten, kein echter Feed. Eine Lizenzentscheidung, keine Präferenz. MetaTrader-Exporte sind der lizenzierte Feed des Brokers, und bei Index- und Aktien-CFDs ist der Basiswert börsenlizenziert. Yahoos Hilfeseiten geben die Einschränkung in klaren Worten an: Sie dürfen Informationen, die auf Yahoo Finance angezeigt oder von Yahoo Finance bereitgestellt werden, nicht weiterverbreiten, und seine Entwickler-API-Bedingungen schränken den Verkauf oder die Unterlizenzierung des Zugriffs separat ein. HistDatas FAQ gewährt überhaupt keine Weiterverbreitungsrechte; es heißt nur, dass die Daten ohne Gewährleistung kommen, und Schweigen ist keine Lizenz. Das Einchecken davon in ein MIT-Repository würde Daten neu lizenzieren, die ich nicht neu lizenzieren darf.

Die Standardbibliothek, nicht pandas oder numpy. Bei gebündeltem CSV-Maßstab reichen csv plus datetime plus Datenklassen aus, und der Baum bleibt prüfbar. Dieser Baum verdient es jedoch, ehrlich benannt zu werden, alles davon: mcp 2.x ist eine direkte Abhängigkeit, die anyio, httpx2, jsonschema, mcp-types, opentelemetry-api, pydantic, pyjwt mit seinem Crypto-Extra, python-multipart, sse-starlette, starlette, typing-extensions, typing-inspection und uvicorn mitbringt, plus pywin32 unter Windows. Das Crypto-Extra bringt cryptography, cffi und pycparser dahinter mit. Das ist ein größerer Fußabdruck als v1, und ein Test liest das eingecheckte uv.lock und schlägt fehl, wenn diese Liste nicht mehr damit übereinstimmt, denn ein Absatz, der existiert, um den Baum zu benennen, ist nichts wert, wenn er den größten Teil des Baums nicht benennt.

Kein run_backtest-Tool. Ein Backtest ist rechenzeitlich unbegrenzt, benötigt weit mehr als eine MarketDataSource und würde QUACKZ duplizieren, daher würde das Paar wie zwei halbe Projekte statt zwei fokussierte wirken. Aus demselben Grund sind die Leitplanken hier domänenlokal: Eingabevalidierung, begrenzte Abfragen, ein festes Instrumentenuniversum, keine Nebenwirkungen. Allgemeine Agenten-Leitplanken gehören in QUELLZ, nicht fünfmal neu erfunden.

Einschränkungen

  • Kein Continuous-Integration-Runner übt den Live-MetaTrader-Pfad aus, nirgendwo. Es gibt kein Nicht-Windows-Rad und kein Runner hat ein Terminal oder ein Brokerkonto. Der Windows-Job beweist, dass die Erweiterung importiert wird und dass Mt5Source ein fehlendes Terminal sauber meldet, und das ist alles. Die Feldzuordnung von Mt5Source ist der am wenigsten getestete Code hier, abgedeckt durch Fake-Modul-Tests.

  • MetaTrader5 ist nur für Windows und veröffentlicht keine Quellverteilung, daher ist pip install quotez[mt5] auf macOS und Linux absichtlich ein No-Op. Ein Test stellt sicher, dass der Umgebungsmarker dies so beibehält.

  • initialize() startet das Terminal, falls es nicht bereits läuft, und die gesamte Operation wird durch sein timeout-Argument begrenzt, das standardmäßig auf 60000 Millisekunden dokumentiert ist. Die Seite gibt keine Zahl für den Start selbst an, daher behandeln Sie 60 Sekunden als Obergrenze für den Aufruf und nicht als gemessene Startzeit. QUOTEZ öffnet die Verbindung einmal in der Serverlebensdauer und nicht pro Aufruf, daher landet das, was es kostet, beim Start, anstatt den ersten Tool-Aufruf wie aufgehängt aussehen zu lassen.

  • Die beiden Quellen stimmen nicht darin überein, wo ein D1- oder H4-Bucket beginnt. Der Replay-Rollup rundet auf die Epochensekunde ab, daher öffnet D1 um 00:00 UTC und H4 um 00, 04, 08, 12, 16 und 20 UTC. Ein MetaTrader-Terminal richtet D1 und H4 am Broker-Server-Tag aus, der üblicherweise UTC+2 oder UTC+3 ist, daher gibt derselbe get_bars(symbol, "D1") eine Kerze mit einer anderen Eröffnungszeit und anderen OHLC zurück, je nachdem, welche Quelle konfiguriert ist. Nichts hier resampelt das M1 des Terminals, um dies zu verbergen, denn ein Balken, der vom eigenen Chart des Betreibers abweicht, ist schlimmer als ein dokumentierter Offset.

  • Aus demselben Grund ist spread bei jedem Replay-Balken über M1 null und bei jedem MetaTrader-Balken gesetzt. Der Rollup löscht es absichtlich; das Terminal meldet seinen eigenen Wert für jeden Zeitrahmen und QUOTEZ gibt ihn weiter, anstatt Daten zu verwerfen, die die Quelle geliefert hat.

  • copy_rates_from_pos und copy_rates_range werden stillschweigend durch die Einstellung „Max. Balken im Chart“ des Terminals begrenzt, daher kann eine Anfrage innerhalb der eigenen Obergrenze des Servers dennoch zu kurz kommen, und nichts in der MetaTrader-API sagt dies.

  • get_bars überspringt den Balken, den das Terminal noch aufbaut, daher ist sein neuester Balken immer geschlossen. get_bars_range tut dies nicht, weil die Grenzen die des Aufrufers sind: Ein end innerhalb des aktuellen Intervalls gibt den Teilbalken dieses Intervalls zurück.

  • symbol_info() gibt None zurück für ein unbekanntes Symbol, anstatt eine Ausnahme auszulösen, ebenso wie symbols_get() bei einem Fehler. Jede Aufrufstelle hier prüft dies, aber das ist die Form der umschlossenen API.

  • MetaTrader speichert Balken- und Tick-Zeiten in UTC ohne Verschiebung, während ein naives Python-datetime gegen die lokale Zone auflöst; die Dokumentation zu copy_rates_range sagt dies. Jeder ausgehende Zeitstempel wird mit tz=UTC erstellt und naive Eingaben werden abgelehnt, aber dies ist die Falle, die eine ganze Serie stillschweigend um eine Stunde verschiebt.

  • Keine Ratenbegrenzung. Ein Stdio-Server ist ein Kindprozess eines Hosts, und der Host besitzt diese.

  • Die Replay-Daten sind im Beispielmaßstab und generiert: 4 Instrumente, jeweils 3600 M1-Balken, zehn Handelstage. Sie demonstrieren die Tools und üben die Aggregation, und sie sind weder ein Forschungsdatensatz noch ein Markt.

  • Version 0.1.0 ist schreibgeschützt und nur Stdio, ohne Prompts-Fähigkeit, ohne SSE oder streamable HTTP-Transport und ohne OAuth.

Warum ich dies gebaut habe

Ich führe Walk-Forward-Forschung mit Indexdaten durch und habe MetaTrader-Terminals für die FX- und Metallseite parat, also waren beide Hälften bereits auf meinem Schreibtisch. Was mich dazu brachte, dies zu schreiben, war die Beobachtung, wie ein Agent eine Zahl aus einem schlecht getippten Tool als Tatsache wiederholte, ohne Einheit, ohne Zeitzone und ohne Angabe, woher sie stammte. Bei Marktdaten ist das nicht kosmetisch: Ein Balken, der nach seinem Schlusskurs statt nach seinem Eröffnungskurs benannt ist, oder ein Zeitstempel, der stillschweigend in die Ortszeit verschoben wurde, liefert eine Antwort, die richtig aussieht, aber um eine Stunde daneben liegt. Dabei handelt es sich also hauptsächlich um Entscheidungen über Herkunft und darüber, was ein Tool behaupten darf, eingewickelt in einen kleinen Aggregationscode.

Entwicklung

uv sync --dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy

251 Tests, kein Netzwerk, ein paar Sekunden, und identisch auf macOS, Linux und Windows. Diese Anzahl wird gegen einen echten Collection-Lauf geprüft, denn eine Zahl in einer README ist eine Zahl, die niemand aktualisiert.

Lizenz

MIT. Siehe LICENSE.

Teil des Q...Z-Toolsets, fünf Tools für das Versagen, das sich nicht ankündigt:

  • QUACKZ, das einen Backtest entlarvt, der nur gut aussieht, weil er aus zweihundert ausgewählt wurde.

  • QUOTEZ, dieses hier: Marktdaten, die ein Agent lesen, aber nicht darauf handeln kann.

  • QUELLZ, misst, was Prompt-Injection-Eindämmung sowohl an Nutzen als auch an Angriffsrate kostet.

  • QUIDZ, verweigert die ausgehende Zahlung, die zweimal rausgegangen wäre.

  • QUESTZ, stoppt einen Scraper, bevor er ein CSV von einer Seite schreibt, die ihre Form geändert hat.

-
license - not tested
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • MCP server exposing the Backtest360 engine API as tools for AI agents.

  • Connect any MCP client to MetaTrader 4/5 to read prices, manage positions, and place trades.

  • Agent-native crypto market-data over MCP+REST: order flow, whales, liquidations, calibrated scores

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PNX89/QUOTEZ'

If you have feedback or need assistance with the MCP directory API, please join our Discord server