Skip to content

Conversation

@shazbert
Copy link
Collaborator

@shazbert shazbert commented Sep 9, 2025

This is only opened for spot right now for design issues than can be expanded across all assets.

resolves #2028

Type of change

Please delete options that are not relevant and add an x in [] as item is complete.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How has this been tested

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration and
also consider improving test coverage whilst working on a certain feature or package.

  • go test ./... -race
  • golangci-lint run
  • Test X

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation and regenerated documentation via the documentation tool
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally and on Github Actions with my changes
  • Any dependent changes have been merged and published in downstream modules

@shazbert shazbert self-assigned this Sep 9, 2025
@shazbert shazbert added the review me This pull request is ready for review label Sep 9, 2025

This comment was marked as outdated.

@shazbert shazbert requested a review from Copilot September 9, 2025 23:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a websocket subscription manager and orderbook update functionality with snapshot support for the GateIO exchange. The implementation focuses on spot trading with the ability to handle both incremental updates and full snapshots for orderbook data.

Key changes:

  • Adds a new websocket subscription manager to handle resubscriptions and track subscription states
  • Implements orderbook update with snapshot functionality for handling both full snapshots and incremental updates
  • Updates currency package to expose ErrCannotCreatePair as a public variable for better error handling

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
exchanges/gateio/ws_ob_sub_manager.go New subscription manager implementation with resubscription tracking
exchanges/gateio/ws_ob_sub_manager_test.go Comprehensive test suite for the subscription manager
exchanges/gateio/gateio_websocket.go Enhanced websocket handling with new orderbook update with snapshot channel
exchanges/gateio/gateio_websocket_test.go Test cases for the new orderbook update functionality
exchanges/gateio/gateio_types.go New type definition for orderbook update with snapshot data
exchanges/gateio/gateio_wrapper.go Integration of the subscription manager in exchange setup
exchanges/gateio/gateio.go Addition of subscription manager field to Exchange struct
currency/pair.go Export of ErrCannotCreatePair error variable
currency/pair_methods.go Updated to use the exported error variable
currency/pair_test.go Test updates to use the exported error variable
currency/pairs_test.go Test updates to use the exported error variable

}); err != nil {
return err
}
e.wsOBSubMgr.CompletedResubscribe(pair, asset.Spot) // asset.Spot used so that all pathways don't compete
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The hardcoded use of asset.Spot in comments suggests this is a temporary solution. Consider using a more explicit approach or a constant to make this design decision clearer and more maintainable.

Copilot uses AI. Check for mistakes.
Comment on lines 483 to 485
if e.wsOBSubMgr.IsResubscribing(pair, asset.Spot) { // asset.Spot used so that all pathways don't compete
continue // Drop incremental updates; waiting for a fresh snapshot
}
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The hardcoded use of asset.Spot in comments suggests this is a temporary solution. Consider using a more explicit approach or a constant to make this design decision clearer and more maintainable.

Copilot uses AI. Check for mistakes.
}

if lastUpdateID, _ := e.Websocket.Orderbook.LastUpdateID(pair, a); lastUpdateID+1 != data.FirstUpdateID {
errs = common.AppendError(errs, e.wsOBSubMgr.Resubscribe(e, conn, data.Channel, pair, asset.Spot)) // asset.Spot used so that all pathways don't compete
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The hardcoded use of asset.Spot in comments suggests this is a temporary solution. Consider using a more explicit approach or a constant to make this design decision clearer and more maintainable.

Copilot uses AI. Check for mistakes.
@thrasher-
Copy link
Collaborator

@codex please review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Review: Here are some suggestions.

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

Comment on lines 31 to 49
// Resubscribe marks a subscription as resubscribing and starts the unsubscribe/resubscribe process
func (m *wsSubscriptionManager) Resubscribe(e *Exchange, conn websocket.Connection, qualifiedChannel string, pair currency.Pair, a asset.Item) error {
if err := e.Websocket.Orderbook.InvalidateOrderbook(pair, a); err != nil {
return err
}

m.m.Lock()
defer m.m.Unlock()

sub := e.Websocket.GetSubscription(newQualifiedChannelKey(qualifiedChannel))
if sub == nil {
return fmt.Errorf("%w: %q", subscription.ErrNotFound, qualifiedChannel)
}

m.lookup[key.PairAsset{Base: pair.Base.Item, Quote: pair.Quote.Item, Asset: a}] = true

go func() { // Has to be called in routine to not impede websocket throughput
if err := e.Websocket.ResubscribeToChannel(conn, sub); err != nil {
log.Errorf(log.ExchangeSys, "Failed to resubscribe to channel %q: %v", qualifiedChannel, err)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Clear resubscribe flag when ResubscribeToChannel fails

The subscription manager marks a pair as resubscribing (lookup[...] = true) before starting the goroutine that calls e.Websocket.ResubscribeToChannel. If that call returns an error, the code only logs it and never clears the flag. Because processOrderbookUpdateWithSnapshot skips every incremental update while IsResubscribing is true, a single failed resubscribe attempt leaves the order book stuck forever, dropping all future updates until the process is restarted. Consider clearing the flag or retrying when ResubscribeToChannel fails so that pairs can recover after a transient websocket error.

Useful? React with 👍 / 👎.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

Comment on lines 456 to 458
pair, err := currency.NewPairFromString(strings.Split(data.Channel, ".")[1])
if err != nil {
return err

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Validate channel format before indexing

In processOrderbookUpdateWithSnapshot the pair is parsed with strings.Split(data.Channel, ".")[1] without checking the slice length. If Gate.io ever sends an unexpected payload (e.g. a malformed s field or an empty string), this will panic the websocket handler and tear down the connection. Guarding against a split shorter than two elements avoids the crash and allows the handler to return a parse error instead.

Useful? React with 👍 / 👎.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I have a strategy actively trading this and they cook their values I am okay for this to panic. But if other people want to me to jank around let me know.

Copy link
Collaborator

@gloriousCode gloriousCode Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not without precedent to assume splits have a len (Kucoin ws topics). It's a bit of a lame spot to panic on though (versus say a Must... func). Not having order book data for a dumb pair sounds better than this dying when you could be doing many other things elsewhere on other exchanges. Plus PR turnaround time to fix it
I'd prefer the check

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@samuael samuael left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick review. The PR looks clean. Thanks.
Just left a minor suggestion on unit tests


m := newWSSubscriptionManager()
require.NotNil(t, m)
require.NotNil(t, m.lookup)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to use require on these last assertify checks? This is below.

require.NoError(t, err)
err = m.Resubscribe(e, conn, "ob.BTC_USDT.50", currency.NewBTCUSDT(), asset.Spot)
require.NoError(t, err)
require.True(t, m.IsResubscribing(currency.NewBTCUSDT(), asset.Spot))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too. and below

"github.com/thrasher-corp/gocryptotrader/log"
)

type wsSubscriptionManager struct {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can somebody suggest a better name for this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wsOrderbookResubscriptionHandler
wsCanSomeoneSuggestABetterNameForThis
wsObResubManager
wsOrderbooSilverIsMooningOMG

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wsOrderbookSilverIsMooningOMG is my pick thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shazbert shazbert changed the title gateio: add websocket subscription manager and orderbook update with snapshot functionality [spot] gateio: add websocket resub manager and orderbook update with snapshot functionality [spot] Sep 11, 2025
Copy link
Collaborator

@samuael samuael left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't find much but left two suggestion on minor issues

return err
}

m.m.Lock()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock can be moved to line 45; below GetSubscription() call

delete(m.lookup, key.PairAsset{Base: pair.Base.Item, Quote: pair.Quote.Item, Asset: a})
}

func newQualifiedChannelKey(qualifiedChannel string) qualifiedChannelKey {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Optional suggestion) I see no need to declare this as it only used once and the qualifiedChannelKey{} instance creation can also be don inside the Resubscribe function

@shazbert shazbert requested a review from samuael September 16, 2025 22:33
@codecov
Copy link

codecov bot commented Sep 16, 2025

Codecov Report

❌ Patch coverage is 78.57143% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.99%. Comparing base (9441f33) to head (dfa7802).

Files with missing lines Patch % Lines
exchanges/deribit/deribit_websocket.go 0.00% 18 Missing ⚠️
exchanges/gateio/gateio_websocket.go 95.00% 3 Missing ⚠️
exchanges/gateio/gateio_websocket_futures.go 0.00% 1 Missing ⚠️
exchanges/kucoin/kucoin_convert.go 0.00% 1 Missing ⚠️
exchanges/kucoin/kucoin_websocket.go 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2045      +/-   ##
==========================================
+ Coverage   41.93%   41.99%   +0.05%     
==========================================
  Files         443      444       +1     
  Lines      143138   143220      +82     
==========================================
+ Hits        60021    60139     +118     
+ Misses      75977    75934      -43     
- Partials     7140     7147       +7     
Files with missing lines Coverage Δ
common/common.go 94.61% <ø> (ø)
currency/pair.go 87.50% <100.00%> (ø)
currency/pair_methods.go 98.14% <100.00%> (ø)
exchanges/deribit/deribit_types.go 89.47% <ø> (ø)
exchanges/gateio/gateio.go 17.35% <ø> (ø)
exchanges/gateio/gateio_types.go 91.30% <ø> (ø)
exchanges/gateio/gateio_wrapper.go 48.52% <100.00%> (-0.44%) ⬇️
exchanges/gateio/ws_ob_resub_manager.go 100.00% <100.00%> (ø)
exchanges/kucoin/kucoin_types.go 66.66% <ø> (ø)
exchanges/gateio/gateio_websocket_futures.go 61.41% <0.00%> (ø)
... and 4 more

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@shazbert shazbert added reconstructing Based on PR feedback, this is currently being reworked and is not to be merged and removed review me This pull request is ready for review labels Sep 17, 2025
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
@shazbert shazbert added blocked and removed review me This pull request is ready for review labels Sep 29, 2025
@shazbert
Copy link
Collaborator Author

shazbert commented Sep 29, 2025

books get cooked on long running instance, investigating. This is all good.

@shazbert shazbert added review me This pull request is ready for review and removed blocked labels Sep 30, 2025
shazbert pushed a commit to shazbert/gocryptotrader that referenced this pull request Nov 10, 2025
shazbert pushed a commit to shazbert/gocryptotrader that referenced this pull request Nov 16, 2025
shazbert pushed a commit to shazbert/gocryptotrader that referenced this pull request Dec 11, 2025
@github-actions
Copy link

This PR is stale because it has been open 21 days with no activity. Please provide an update on the progress of this PR.

@github-actions github-actions bot added the stale label Dec 19, 2025
@shazbert
Copy link
Collaborator Author

Go away stale tag! Other things are a priority.

@shazbert shazbert removed the stale label Jan 15, 2026
Copy link
Collaborator

@gloriousCode gloriousCode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

INFO]  | SYNC | 20/01/2026 17:50:30 | GateIO websocket BTC-USDT SPOT ORDERBOOK: Bids len: 50 Amount: 3.457137 BTC. Total value: 314203.20859570004 Asks len: 50 Amount: 2.550006 BTC. Total value: 231906.23394580002
[DEBUG] | WEBSOCKET | 20/01/2026 17:50:30 | GateIO wss://api.gateio.ws/ws/v4/: Message received: {"channel":"spot.obu","result":{"t":1768891830817,"s":"ob.BTC_USDT.50","u":31518294256,"U":31518294200,"b":[["90914.9","0.22685"],["90908.5","0.011006"],["90903","0"]],"a":[["90915","0.116082"],["90916.8","0.005499"],["90924.9","0.011006"],["90920.2","0"],["90967.9","0"]]},"time_ms":1768891830817,"event":"update"}
[DEBUG] | WEBSOCKET | 20/01/2026 17:50:30 | GateIO wss://api.gateio.ws/ws/v4/: Sending message: {"time":1768891830,"id":62,"channel":"spot.obu","event":"unsubscribe","payload":["ob.BTC_USDT.50"]}
[DEBUG] | WEBSOCKET | 20/01/2026 17:50:30 | GateIO wss://api.gateio.ws/ws/v4/: Message received: {"time":1768891830,"time_ms":1768891830927,"id":62,"conn_id":"ba2a016b99263e15","trace_id":"e99fd40bb8d7b4e7044a1e36e2b15d3c","channel":"spot.obu","event":"unsubscribe","payload":["ob.BTC_USDT.50"],"result":{"status":"success"},"requestId":"e99fd40bb8d7b4e7044a1e36e2b15d3c"}
[DEBUG] | WEBSOCKET | 20/01/2026 17:50:30 | GateIO wss://api.gateio.ws/ws/v4/: Sending message: {"time":1768891830,"id":63,"channel":"spot.obu","event":"subscribe","payload":["ob.BTC_USDT.50"]}
[DEBUG] | WEBSOCKET | 20/01/2026 17:50:31 | GateIO wss://api.gateio.ws/ws/v4/: Message received: {"time":1768891831,"time_ms":1768891831036,"id":63,"conn_id":"ba2a016b99263e15","trace_id":"e99fd40bb8d7b4e7044a1e36e2b15d3c","channel":"spot.obu","event":"subscribe","payload":["ob.BTC_USDT.50"],"result":{"status":"success"},

Comment on lines +433 to +436
channelParts := strings.Split(data.Channel, ".")
if len(channelParts) < 3 {
return fmt.Errorf("%w: %q", common.ErrMalformedData, data.Channel)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this before you convert the book plz

return nil
}

// CompletedResubscribe marks a subscription as completed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no marking. Please update

@shazbert shazbert requested a review from gloriousCode January 20, 2026 23:09
Copy link
Collaborator

@gloriousCode gloriousCode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK! TY4MakinChanges

@gloriousCode gloriousCode added the gcrc GloriousCode Review Complete label Jan 20, 2026
Copy link
Collaborator

@thrasher- thrasher- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK!
image

@thrasher- thrasher- merged commit a03d065 into thrasher-corp:master Jan 21, 2026
5 of 12 checks passed
@shazbert shazbert deleted the gateio_ob_v2 branch January 22, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gcrc GloriousCode Review Complete review me This pull request is ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GateIO: Add support for new orderbook API 2

5 participants