Overview
The MarketDataFeed is a singleton class that manages websocket connections to the Drift Data API. It intelligently shares websocket connections between multiple subscribers when possible, reducing resource usage and improving performance.
This is an internal class primarily used by CandleClient. Most applications should use CandleClient instead of using MarketDataFeed directly.
Key Features
Singleton Pattern : Single instance manages all subscriptions
Subscription Sharing : Multiple subscribers can share the same websocket connection
Automatic Transfer : Trade subscribers automatically transfer to more efficient subscriptions when available
Type-Safe : Separate subscription types for candles and trades with proper type inference
Memory Efficient : Automatic cleanup when subscriptions are no longer needed
Architecture
Core Concepts
Subscription vs Subscriber:
A subscription is an actual websocket connection to the Drift Data API
A subscriber is an external party requesting data (candle or trade)
One subscription can serve multiple subscribers
Subscription Compatibility:
Trade subscriptions : Can share any subscription for the same market (regardless of resolution)
Candle subscriptions : Must share the same market AND resolution
Subscription Sharing Example
// Timeline of subscriptions:
// 1. SOL-PERP Resolution-1 candle subscriber
// → Creates Subscription A (SOL-PERP, Resolution-1)
// 2. SOL-PERP Resolution-5 candle subscriber
// → Creates Subscription B (SOL-PERP, Resolution-5)
// (different resolution, can't share)
// 3. SOL-PERP trade subscriber
// → Uses existing Subscription A
// (trades work with any resolution)
// 4. Another SOL-PERP Resolution-1 candle subscriber
// → Uses existing Subscription A
// (same market and resolution)
Automatic Transfer Optimization
When subscriptions change, the system automatically optimizes:
// Initial state:
// - Subscription A: SOL-PERP Resolution-1 (trade subscriber only)
// New candle subscriber comes in:
// - Subscription B: SOL-PERP Resolution-1 (candle subscriber)
// System automatically:
// 1. Transfers trade subscriber from A to B
// 2. Closes Subscription A (no longer needed)
// Result: Single subscription serving both subscribers
Static Methods
subscribe
Subscribe to candle or trade data. Returns a subscription object with an observable stream.
static subscribe (
config : CandleSubscriptionConfig
): CandleSubscriberSubscription ;
static subscribe (
config : TradeSubscriptionConfig
): TradeSubscriberSubscription ;
Parameters
config
SubscriptionConfig
required
Subscription configuration Show CandleSubscriptionConfig
Environment configuration
Market symbol (e.g., ‘SOL-PERP’)
Show TradeSubscriptionConfig
Environment configuration
Market symbol (e.g., ‘SOL-PERP’)
Returns
Subscription object with:
id: Unique subscriber ID
observable: RxJS Observable stream of data
Example: Candle Subscription
import { MarketDataFeed } from '@drift-labs/common' ;
const candleSubscription = MarketDataFeed . subscribe ({
type: 'candles' ,
env: { sdkEnv: 'mainnet-beta' , isDevnet: false , isStaging: false , key: 'mainnet' },
marketSymbol: 'SOL-PERP' ,
resolution: '15'
});
candleSubscription . observable . subscribe (( candle ) => {
console . log ( 'Candle update:' , candle );
console . log ( `Price: ${ candle . close } , Volume: ${ candle . volume } ` );
});
// Later: clean up
MarketDataFeed . unsubscribe ( candleSubscription . id );
Example: Trade Subscription
const tradeSubscription = MarketDataFeed . subscribe ({
type: 'trades' ,
env: { sdkEnv: 'mainnet-beta' , isDevnet: false , isStaging: false , key: 'mainnet' },
marketSymbol: 'BTC-PERP'
});
tradeSubscription . observable . subscribe (( trades ) => {
console . log ( `Received ${ trades . length } trades` );
trades . forEach ( trade => {
console . log ( `Price: ${ trade . price } , Size: ${ trade . size } ` );
});
});
unsubscribe
Unsubscribe from a subscription using its ID.
static unsubscribe ( subscriberId : SubscriberId ): void
Parameters
Subscriber ID returned from subscribe()
Example
const subscription = MarketDataFeed . subscribe ( config );
// Later...
MarketDataFeed . unsubscribe ( subscription . id );
Internal Classes
ApiSubscription
Manages a single websocket connection and distributes data to attached subscribers.
Key responsibilities:
Establishes websocket connection via DataApiWsClient
Manages lists of candle and trade subscribers
Distributes incoming data to relevant subscribers
Triggers cleanup when no subscribers remain
CandleSubscriber / TradeSubscriber
Internal subscriber management classes that:
Generate unique subscriber IDs
Create RxJS Subject for data distribution
Track the ApiSubscription they’re attached to
Provide the observable stream to external code
SubscriptionLookup
Lookup table management for finding compatible existing subscriptions:
CandleSubscriptionLookup:
Key: {marketSymbol}:{resolution}:{env}
One ApiSubscription per unique key
TradeSubscriptionLookup:
Key: {marketSymbol}:{env}
Multiple ApiSubscriptions possible (different resolutions)
Returns first available compatible subscription
Types
CandleSubscriberSubscription
Subscription object for candle data:
class CandleSubscriberSubscription {
readonly id : SubscriberId ;
readonly observable : Observable < JsonCandle >;
}
TradeSubscriberSubscription
Subscription object for trade data:
class TradeSubscriberSubscription {
readonly id : SubscriberId ;
readonly observable : Observable < JsonTrade []>;
}
JsonTrade
interface JsonTrade {
price : number ;
size : number ;
side : 'buy' | 'sell' ;
ts : number ; // Timestamp in seconds
// ... additional fields
}
Advanced Usage
Multiple Subscriptions with Shared Connections
import { MarketDataFeed } from '@drift-labs/common' ;
// These will share the same websocket connection
const candle1 = MarketDataFeed . subscribe ({
type: 'candles' ,
env: mainnetEnv ,
marketSymbol: 'SOL-PERP' ,
resolution: '15'
});
const candle2 = MarketDataFeed . subscribe ({
type: 'candles' ,
env: mainnetEnv ,
marketSymbol: 'SOL-PERP' ,
resolution: '15'
});
const trades = MarketDataFeed . subscribe ({
type: 'trades' ,
env: mainnetEnv ,
marketSymbol: 'SOL-PERP'
});
// All three subscribers share a single websocket connection!
Handling Connection Errors
import { catchError } from 'rxjs/operators' ;
import { EMPTY } from 'rxjs' ;
const subscription = MarketDataFeed . subscribe ( config );
subscription . observable . pipe (
catchError ( error => {
console . error ( 'Subscription error:' , error );
// Return EMPTY to complete the stream gracefully
return EMPTY ;
})
). subscribe ({
next : ( candle ) => handleCandle ( candle ),
error : ( err ) => console . error ( 'Stream error:' , err ),
complete : () => console . log ( 'Stream completed' )
});
Combining Multiple Market Feeds
import { merge } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
const markets = [ 'SOL-PERP' , 'BTC-PERP' , 'ETH-PERP' ];
const subscriptions = markets . map ( symbol =>
MarketDataFeed . subscribe ({
type: 'candles' ,
env: mainnetEnv ,
marketSymbol: symbol ,
resolution: '15'
})
);
// Combine all streams
const combinedStream = merge (
... subscriptions . map (( sub , idx ) =>
sub . observable . pipe (
map ( candle => ({ market: markets [ idx ], candle }))
)
)
);
combinedStream . subscribe (({ market , candle }) => {
console . log ( ` ${ market } : ${ candle . close } ` );
});
// Cleanup
subscriptions . forEach ( sub => MarketDataFeed . unsubscribe ( sub . id ));
Memory Efficiency
Subscription Sharing : Multiple subscribers = single websocket
Automatic Cleanup : Connections close when last subscriber leaves
Transfer Optimization : Redundant connections eliminated automatically
Network Efficiency
Reduced Connections : Fewer websockets = less bandwidth
CDN-Friendly : Compatible subscriptions share cache-friendly connections
Heartbeat Monitoring : Built into underlying DataApiWsClient
Internal Architecture Details
Subscription Lifecycle
Transfer Optimization Logic
When a new candle subscription is created, the system:
Searches for existing trade-only subscriptions for the same market
If found, transfers all trade subscribers to the new candle subscription
Closes the old trade-only subscription
Result: Fewer websocket connections, same functionality
Debugging
The MarketDataFeed logs important events to console:
// Console output examples:
"marketDataFeed::creating_new_api_subscription:SOL-PERP:15:mainnet(0)"
"marketDataFeed::attaching_new_candle_subscriber_to_existing_subscription"
"marketDataFeed::transferring_trade_subscribers_on_unsubscribe"
"marketDataFeed::unsubscribing_api_subscription:SOL-PERP:15:mainnet(0)"
CandleClient Higher-level candle data client
Data API Websocket Underlying websocket API documentation