Class QTSurfer

java.lang.Object
com.qtsurfer.api.sdk.QTSurfer

public final class QTSurfer extends Object
High-level SDK for the QTSurfer platform.

Quick start


 QTSurfer qts = QTSurfer.builder()
     .baseUrl("https://api.qtsurfer.com/v1")
     .token(System.getenv("JWT_API_TOKEN"))
     .build();

 // One-shot shortcut:
 ResultMap result = qts.backtest(request, options).join();

 // Or decomposed for streaming / reuse:
 Strategy strategy = qts.compile(source).join();
 Backtest job = strategy.backtest(request, options).join();
 job.progress().subscribe( ... );
 ResultMap result = job.await().join();

 // Or sweep a parameter grid instead of running one configuration:
 Sweep sweep = qts.sweep(sweepRequest).join();
 ExecuteSweepResult leaderboard = sweep.await().join();
 
  • Method Details

    • options

      public QTSurferOptions options()
      Configuration this client was built with.
    • compile

      public CompletableFuture<Strategy> compile(String source)
      Compile a strategy source. Resolves with a Strategy handle you can reuse.
    • compile

      public CompletableFuture<Strategy> compile(String source, BacktestOptions options)
      Compile strategy source into a reusable Strategy handle.

      Issues a single synchronous HTTP request; the compile endpoint returns the strategyId directly, so a failing compile surfaces immediately as QTSStrategyCompileError rather than on a later poll. A 429 means the platform was holding too many compilations and the source was never judged, so it is safe to retry; any other error status reflects a judgment on the submitted code and will not succeed by retrying alone.

      Parameters:
      options - tuning knobs; only onProgress is used here (a single COMPILING event) — polling and timeout settings do not apply to this stage
    • compile

      public CompletableFuture<Strategy> compile(BacktestRequest request)
      Convenience: compile the strategy embedded in the given request.
    • compile

      public CompletableFuture<Strategy> compile(BacktestRequest request, BacktestOptions options)
      Convenience overload that compiles BacktestRequest.strategy() with the given options, ignoring every other field of the request. See compile(String, BacktestOptions).
    • validateStrategy

      public ValidationOutcome validateStrategy(String strategyId)
      Ask the platform to check that a registered strategy can actually run. The compiled class is instantiated and driven through a bounded synthetic series, so a wiring fault surfaces here instead of at the first backtest. Synchronous — blocks the calling thread for the HTTP round trip.

      Did this call start work, and is there a verdict? Two questions, two answers. The call is idempotent: it either queues a check, or queues nothing because the current compilation is already accounted for. The returned ValidationOutcome says which — ValidationOutcome.Queued for the first, ValidationOutcome.NotQueued for the second.

      NotQueued does not mean a verdict exists. The StrategyState it carries can itself be pending — a check queued by an earlier call, possibly from another process, that has not answered yet. So a caller that wants a verdict has to read one either way:

      Polling needs its own deadline. A queued check can go unreported for far longer than one takes — the platform reports that as StrategyState.getValidationStalled() — so pending is not guaranteed to resolve, and a caller that waits without a timeout can wait indefinitely. A stall disproves nothing about the strategy; the check simply has not run. This SDK ships no polling helper.

      passed does not mean the strategy is correct. It means the class loaded and survived the first event of a short synthetic run — a floor, not a guarantee, and not a statement that the strategy is safe to run. When StrategyState.getDryRunIncomplete() is true the check did not even finish its budget, so the floor is lower still and an empty StrategyState.getNotices() list is not a clean bill of health.

      Parameters:
      strategyId - id of a registered strategy, as returned by compile(String)
      Returns:
      whether this call queued a check, and — when it did not — the state the platform holds
      Throws:
      QTSError - on HTTP 4xx/5xx (including 404 when no such strategy is registered for this caller) or transport failure
    • getStrategyState

      public com.qtsurfer.api.client.model.StrategyState getStrategyState(String strategyId)
      Fetch what the platform knows about a registered strategy: that it compiled, what market data the compiled class needs, and what validating it found. Synchronous — blocks the calling thread for the HTTP round trip.

      Resolves to the api-client's StrategyState record — the platform's view of the strategy — not to the SDK's Strategy handle that compile(String) produces.

      This is the endpoint to poll while a check is outstanding, whether validateStrategy(String) queued it or reported that one was already accounted for. See that method for what a verdict does and does not mean, and for why a polling loop needs its own deadline.

      A verdict describes the bytecode that produced it, and recompiling supersedes it: when StrategyState.getCompiledAt() is later than StrategyState.getValidatedAt(), the recorded verdict was reached against a compilation that is no longer what would run, and validateStrategy(String) can be called again to refresh it.

      A 404 means exactly one thing — no such registered strategy for this caller. It is never a stale or expired answer; registration and verdict are stored durably, not cached.

      Parameters:
      strategyId - id of a registered strategy, as returned by compile(String)
      Returns:
      the platform's record of the strategy
      Throws:
      QTSError - on HTTP 4xx/5xx or transport failure
    • strategyState

      @Deprecated(forRemoval=false) public com.qtsurfer.api.client.model.StrategyState strategyState(String strategyId)
      Deprecated.
    • getStrategies

      public List<com.qtsurfer.api.client.model.StrategySummary> getStrategies()
      List every strategy registered under this account and not since deleted, most recently compiled first. Synchronous — blocks the calling thread for the HTTP round trip.

      Deliberately cheaper than reading each strategy individually: each entry carries the same compiledAt / requiredSources provenance strategyState(String) does, but not validation state, so listing stays cheap no matter how many strategies exist. Check a specific strategy's validation with strategyState(String).

      Never fails with a 404 — an empty list means the caller has no registered strategies, not that the resource is missing.

      Returns:
      the caller's registered strategies
      Throws:
      QTSError - on HTTP 4xx/5xx or transport failure
    • listStrategies

      @Deprecated(forRemoval=false) public List<com.qtsurfer.api.client.model.StrategySummary> listStrategies()
      Deprecated.
    • deleteStrategy

      public void deleteStrategy(String strategyId)
      Release a registered strategy: removes it from both strategyState(String) and listStrategies(). Synchronous — blocks the calling thread for the HTTP round trip.

      Not undone by recompiling the same source. Submitting identical source to compile(String) afterward registers a brand-new strategy with a brand-new id — it does not "undelete" this one.

      History is untouched. Backtests already run against this strategy are completely unaffected by deleting it. Deletion only stops the strategy counting against the account and stops future validation or re-run under this id.

      Scoped to the caller's own registration. If this id was copied from someone else's strategy (a shared/marketplace listing), deleting it here never affects their copy, or anyone else's copy of the same source.

      Parameters:
      strategyId - id of a registered strategy, as returned by compile(String)
      Throws:
      QTSError - on HTTP 4xx/5xx (including 404 when no such strategy is registered for this caller) or transport failure
    • getStrategyCode

      public String getStrategyCode(String strategyId)
      Fetch the exact source last submitted for a registered strategy id — the same text compile(String) derived strategyId from, whitespace and comments included. Synchronous — blocks the calling thread for the HTTP round trip.

      A 404 here covers two different situations, and deliberately does not distinguish them: the id was never registered by this caller, or it resolves only through a shared/marketplace reference that carries no source of its own. Both mean the same thing from this call's point of view — nothing to return — so both raise the same way.

      Parameters:
      strategyId - id of a registered strategy, as returned by compile(String)
      Returns:
      the raw strategy source last registered for this id
      Throws:
      QTSError - on HTTP 4xx/5xx (including the 404 above) or transport failure
    • executeBacktest

      public CompletableFuture<com.qtsurfer.api.client.model.ResultMap> executeBacktest(BacktestRequest request)
      Run the full compile → prepare → execute → await pipeline as a single future. Equivalent to compile(request).thenCompose(s -> s.backtest(request, options)).thenCompose(Backtest::await).
    • executeBacktest

      public CompletableFuture<com.qtsurfer.api.client.model.ResultMap> executeBacktest(BacktestRequest request, BacktestOptions options)
      Run the full compile → prepare → execute pipeline and resolve once the run reaches a terminal state (completed, failed, or canceled). The returned future completes exceptionally with QTSStrategyCompileError if compilation fails, QTSPreparationError if data preparation fails, QTSExecutionError if execution fails, or QTSTimeoutError if a stage exceeds its configured timeout.
      Parameters:
      options - tuning knobs (poll interval, timeout, progress callback) applied to every stage of the pipeline
    • backtest

      @Deprecated(forRemoval=false) public CompletableFuture<com.qtsurfer.api.client.model.ResultMap> backtest(BacktestRequest request)
    • backtest

      @Deprecated(forRemoval=false) public CompletableFuture<com.qtsurfer.api.client.model.ResultMap> backtest(BacktestRequest request, BacktestOptions options)
    • getBacktestResult

      public BacktestOutcome getBacktestResult(String exchangeId, String jobId)
      Read what the platform holds for a backtest run, addressed by the exchange it ran on and the id of its execute job. Synchronous — blocks the calling thread for the HTTP round trip.

      The run does not have to be one this process started. Every other route to a run's numbers in this SDK goes through the handle backtest(BacktestRequest) or Strategy.backtest(BacktestRequest) hands back, and that handle only exists in the process that submitted the run. A job id that arrived from anywhere else — another client, another session, this one before a restart — has no handle behind it, and this is how to ask the platform about it directly. It compiles nothing, prepares nothing, submits nothing, and starts no second run.

      A run that ended badly is an answer, not a failure of this call. The BacktestOutcome handed back says which of four things the platform is reporting — the run finished, it failed, it was cancelled, or it is still going and has nothing final to say yet. Only a job the platform does not recognise for this caller raises, as a 404. That is a deliberate departure from Backtest.await(), which completes exceptionally on a failed or aborted run: a caller waiting for a result it asked for is not getting one, whereas a caller asking what happened to a job is.

      Waiting for a run to finish remains Backtest.await()'s job, on the process that started it. This does not poll — it is a snapshot, and BacktestOutcome.InProgress means ask again later.

      exchangeId is required and cannot be guessed. A run's result is addressed under the exchange it was submitted against, so a job id on its own does not identify the resource and there is nothing sensible to default the exchange to. An id carried to the wrong exchange does not name the same run.

      Parameters:
      exchangeId - exchange the run was submitted against (e.g. "binance")
      jobId - id of the execute job, as carried by Backtest.id() on the process that submitted it
      Throws:
      QTSError - on HTTP 4xx/5xx (including the 404 above) or transport failure
    • backtestResult

      @Deprecated(forRemoval=false) public BacktestOutcome backtestResult(String exchangeId, String jobId)
    • sweep

      public CompletableFuture<Sweep> sweep(SweepRequest request)
      Parameters:
      request - the grid, the instrument, and the window
      Returns:
      the handle, once the platform has accepted the sweep
    • sweep

      public CompletableFuture<Sweep> sweep(SweepRequest request, SweepOptions options)
      Run the full compile → prepare → executeSweep pipeline and resolve once the platform has accepted the sweep, handing back a Sweep that keeps polling the leaderboard in the background.

      The whole sweep is one call because the execute-sweep endpoint is addressed by the id of an already-prepared dataset: exposing the stages separately would hand dataset lifecycle to the caller and buy nothing. Preparing is idempotent, so sweeping the same window twice prepares it once.

      The returned future completes exceptionally with QTSStrategyCompileError if compilation fails, QTSPreparationError if data preparation fails, QTSExecutionError if the platform rejects the sweep — an expanded grid over the server limit, or a walk-forward request whose fold count multiplies past the sweep budget, both answer 400 — or QTSTimeoutError if a stage exceeds its configured timeout.

      What the sweep found arrives through Sweep.await(), which is also where the semantics of the leaderboard are documented. Acceptance already answers three things worth reading before any result exists — the effective seed, whether this submission enqueued anything, and whether this is a walk-forward sweep — see Sweep.accepted().

      Parameters:
      request - the grid, the instrument, and the window
      options - tuning knobs (poll interval, timeout, progress callback, leaderboard ordering) applied to every stage of the pipeline
      Returns:
      the handle, once the platform has accepted the sweep
    • getSweepRunEquityCurve

      public com.qtsurfer.api.client.model.EquityCurveResult getSweepRunEquityCurve(String exchangeId, String requestId, String sweepId, int runIx, com.qtsurfer.api.client.model.EquityCurveOutMode outMode, Integer resample, Boolean differential)
      Read a retained sweep trial's equity curve without starting or polling a sweep.

      Pass null for a transform argument to inherit that sweep's submission default. The returned meta says whether points use ARRAY or SHORT output; do not infer that from the requested mode.

      Parameters:
      exchangeId - exchange that owns the sweep
      requestId - prepared-dataset identifier from Sweep.requestId()
      sweepId - sweep identifier from Sweep.id()
      runIx - trial index to read
      outMode - requested point representation, or null for the sweep default
      resample - maximum point count, or null for the sweep default
      differential - whether to delta-encode points, or null for the sweep default
      Returns:
      the retained curve with authoritative response metadata
      Throws:
      QTSError - if the sweep/trial is unknown, its curve was not retained, or the request fails
    • getBoundedSweepRunEquityCurve

      public BoundedEquityCurve getBoundedSweepRunEquityCurve(String exchangeId, String requestId, String sweepId, int runIx, Integer maxResample)
      Read a retained sweep curve as normalized absolute points with a bounded server request.
    • createDataset

      public com.qtsurfer.api.client.model.DatasetCreated createDataset(com.qtsurfer.api.client.model.CreateDatasetRequest request)
      Create a dataset and its first presigned upload session.
    • importDataset

      public com.qtsurfer.api.client.model.DatasetImportCreated importDataset(com.qtsurfer.api.client.model.DatasetImportRequest request)
      Start an external-history import into a new dataset.
    • getDatasetImport

      public com.qtsurfer.api.client.model.DatasetImportState getDatasetImport(String datasetId, String importId)
      Read the fetch and ingest state for one external-history import.
    • getDatasets

      public List<com.qtsurfer.api.client.model.Dataset> getDatasets()
      List the caller's non-deleted datasets, newest first.
    • listDatasets

      @Deprecated(forRemoval=false) public List<com.qtsurfer.api.client.model.Dataset> listDatasets()
      Deprecated.
    • getDataset

      public com.qtsurfer.api.client.model.DatasetWithLinks getDataset(String datasetId)
      Read one dataset and its self link.
    • dataset

      @Deprecated(forRemoval=false) public com.qtsurfer.api.client.model.DatasetWithLinks dataset(String datasetId)
      Deprecated.
    • deleteDataset

      public void deleteDataset(String datasetId)
      Soft-delete a dataset. Existing runs against it are unaffected.
    • finalizeDatasetUpload

      public com.qtsurfer.api.client.model.FinalizeDatasetUpload202Response finalizeDatasetUpload(String datasetId, String uploadId)
      Mark a completed presigned upload ready for ingestion and return its ingest job id.
    • getDatasetUpload

      public com.qtsurfer.api.client.model.DatasetUploadState getDatasetUpload(String datasetId, String uploadId)
      Read an upload's ingest state after it has been finalized.
    • datasetUpload

      @Deprecated(forRemoval=false) public com.qtsurfer.api.client.model.DatasetUploadState datasetUpload(String datasetId, String uploadId)
    • openDatasetUpload

      public com.qtsurfer.api.client.model.DatasetUploadSession openDatasetUpload(String datasetId)
      Open or recover the pending upload session for an existing dataset.
      Parameters:
      datasetId - dataset that will receive the next version
      Returns:
      presigned upload session to pass to uploadDatasetFile(DatasetUploadSession, Path)
      Throws:
      QTSError - on HTTP 4xx/5xx or transport failure
    • uploadDatasetFile

      public void uploadDatasetFile(com.qtsurfer.api.client.model.DatasetCreated created, Path file)
      Stream a local file to the initial presigned target without attaching API credentials.
      Parameters:
      created - result returned by createDataset(CreateDatasetRequest)
      file - readable regular file to upload
      Throws:
      QTSUploadError - when the transfer fails
    • uploadDatasetFile

      public void uploadDatasetFile(com.qtsurfer.api.client.model.DatasetUploadSession session, Path file)
      Stream a local file to a reopened presigned target without attaching API credentials.
      Parameters:
      session - session returned by openDatasetUpload(String)
      file - readable regular file to upload
      Throws:
      QTSUploadError - when the transfer fails
    • getExchanges

      public List<com.qtsurfer.api.client.model.Exchange> getExchanges()
      List available exchanges on the platform.
      Throws:
      QTSError - on HTTP 4xx/5xx or transport failure
    • exchanges

      @Deprecated(forRemoval=false) public List<com.qtsurfer.api.client.model.Exchange> exchanges()
      Deprecated.
    • getInstruments

      public List<com.qtsurfer.api.client.model.InstrumentDetail> getInstruments(String exchangeId)
      List instruments available on the given exchange, including per-data-type coverage (see InstrumentDetail.getCoverage()) and market info.

      Unwraps the InstrumentListResponse HAL envelope returned by the underlying API client and returns just the instrument list.

      Parameters:
      exchangeId - exchange identifier (e.g. "binance")
      Throws:
      QTSError - on HTTP 4xx/5xx or transport failure
    • instruments

      @Deprecated(forRemoval=false) public List<com.qtsurfer.api.client.model.InstrumentDetail> instruments(String exchangeId)
      Deprecated.
    • getInstruments

      public List<com.qtsurfer.api.client.model.InstrumentDetail> getInstruments(String exchangeId, String segment)
      List the instruments of one market segment of the given exchange, including per-data-type coverage (see InstrumentDetail.getCoverage()) and market info.

      Unwraps the same InstrumentListResponse HAL envelope as instruments(String) and returns just the instrument list. The single-argument overload is the default-segment shortcut and lists the spot segment.

      Parameters:
      exchangeId - exchange identifier (e.g. "binance")
      segment - market segment to list: "spot" or "futures"
      Returns:
      the instruments of that segment
      Throws:
      QTSError - on HTTP 4xx/5xx or transport failure
    • instruments

      @Deprecated(forRemoval=false) public List<com.qtsurfer.api.client.model.InstrumentDetail> instruments(String exchangeId, String segment)
    • downloadTickers

      public InputStream downloadTickers(String exchangeId, String base, String quote, String hour)
      Download one hour of raw tickers for an instrument as a streaming InputStream. Defaults to DownloadFormat.LASTRA; pass DownloadFormat.PARQUET for on-the-fly Parquet conversion.

      The caller is responsible for closing the stream — typically via try-with-resources, piping to Files.copy(...), or feeding it into a Lastra/Parquet reader.

      Throws:
      QTSDownloadError - on HTTP 4xx/5xx or transport failure
    • downloadTickers

      public InputStream downloadTickers(String exchangeId, String base, String quote, String hour, DownloadFormat format)
      Download one hour of raw tickers for an instrument as a streaming InputStream, requesting the given DownloadFormat. The 4-argument overload delegates here with DownloadFormat.LASTRA.
      Throws:
      QTSDownloadError - on HTTP 4xx/5xx or transport failure
    • tickers

      @Deprecated(forRemoval=false) public InputStream tickers(String exchangeId, String base, String quote, String hour)
    • tickers

      @Deprecated(forRemoval=false) public InputStream tickers(String exchangeId, String base, String quote, String hour, DownloadFormat format)
    • downloadKlines

      public InputStream downloadKlines(String exchangeId, String base, String quote, String hour)
      Download one hour of klines for an instrument as a streaming InputStream. See tickers(java.lang.String, java.lang.String, java.lang.String, java.lang.String) for semantics.
      Throws:
      QTSDownloadError - on HTTP 4xx/5xx or transport failure
    • downloadKlines

      public InputStream downloadKlines(String exchangeId, String base, String quote, String hour, DownloadFormat format)
      Download one hour of klines for an instrument as a streaming InputStream, requesting the given DownloadFormat. See tickers(String, String, String, String, DownloadFormat) for stream-closing semantics.
      Throws:
      QTSDownloadError - on HTTP 4xx/5xx or transport failure
    • klines

      @Deprecated(forRemoval=false) public InputStream klines(String exchangeId, String base, String quote, String hour)
    • klines

      @Deprecated(forRemoval=false) public InputStream klines(String exchangeId, String base, String quote, String hour, DownloadFormat format)
    • authenticate

      public static AuthenticatedClient authenticate(String apikey)
      One-call setup: exchange an API key for a short-lived JWT and return an AuthenticatedClient that mirrors this SDK's surface (compile / validateStrategy / strategyState / listStrategies / deleteStrategy / getStrategyCode / backtest / backtestResult / sweep / dataset management / exchanges / instruments / tickers / klines) with automatic refresh-on-401.

      If apikey is null or blank, the value is read from the QTSURFER_APIKEY environment variable.

      Throws:
      QTSAuthError - when no API key is available or the initial JWT exchange fails.
    • authenticate

      public static AuthenticatedClient authenticate(String apikey, AuthOptions options)
      Overload accepting an AuthOptions (base URL, token store, executor).
    • authenticate

      public static AuthenticatedClient authenticate()
      Overload that reads the API key from QTSURFER_APIKEY.
    • builder

      public static QTSurfer.Builder builder()
      Start building a QTSurfer client via the fluent QTSurfer.Builder.
    • getAccount

      public com.qtsurfer.api.client.model.Account getAccount()
      Read the authenticated account's tier and limits.
    • getAccountUsage

      public com.qtsurfer.api.client.model.AccountUsage getAccountUsage()
      Read current account storage consumption, including retained signals.
    • startLive

      public com.qtsurfer.api.client.model.LiveRun startLive(String strategyId, com.qtsurfer.api.client.model.StartLiveRequest request)
      Start the compiled strategy's live run.
    • getLive

      public com.qtsurfer.api.client.model.LiveRun getLive(String strategyId)
      Read a strategy's live run.
    • stopLive

      public com.qtsurfer.api.client.model.LiveRun stopLive(String strategyId)
      Stop a strategy's active live run.
    • listPublicLive

      public com.qtsurfer.api.client.model.PublicLiveListResponse listPublicLive(String cursor, Integer limit)
      List publicly visible live runs.
    • listLive

      public com.qtsurfer.api.client.model.LiveListResponse listLive(String cursor, Integer limit)
      List the authenticated account's live runs, newest first.
    • updateLive

      public com.qtsurfer.api.client.model.LiveRunCompact updateLive(String runId, com.qtsurfer.api.client.model.UpdateLiveRequest request)
      Update mutable metadata for a live run.
    • updateLiveParams

      public com.qtsurfer.api.client.model.LiveParamsUpdateResult updateLiveParams(String runId, com.qtsurfer.api.client.model.UpdateLiveParamsRequest request)
      Update live parameters without opening a WebSocket connection.
    • updateLiveParams

      public com.qtsurfer.api.client.model.LiveParamsUpdateResult updateLiveParams(String runId, UpdateLiveParamsRequestBuilder request)
      Update live strategy parameters through the SDK request builder.
    • getLiveSignals

      public com.qtsurfer.api.client.model.LiveSignalPage getLiveSignals(String runId, Long sinceMs, String instrument, String cursor, Integer limit)
      Read one oldest-first page of retained live signals.
    • getNextLiveSignals

      public Optional<com.qtsurfer.api.client.model.LiveSignalPage> getNextLiveSignals(String runId, com.qtsurfer.api.client.model.LiveSignalPage page)
      Continue a retained-signal page without requiring callers to parse its HAL link.