Crypto Never Sleeps — Neither Should Your Risk Management
Cryptocurrency markets never sleep — and neither do the risks that come with them. Price swings of 10% or more within a single hour are not unusual, and for traders managing multi-asset portfolios across spot and futures accounts, staying on top of exposure in real time is not just good practice — it's essential.
Traditional risk management approaches that rely on end-of-day batch calculations simply don't cut it in this environment. What's needed is a system that continuously monitors account balances, positions, and market prices, computes key risk metrics on the fly, and alerts you the moment thresholds are breached.
This post walks through how to build exactly that using DolphinDB. By integrating directly with Binance and OKX exchange APIs, the solution provides real-time computation of portfolio net value, unrealized P&L, leverage ratios, and more — all persisted to a time-series database and wired to instant notifications.
Solution Overview
The system is built around three core capabilities:
-
Exchange data retrieval : Periodically polls wallet balances, futures positions, and market quotes from exchanges via REST APIs, and writes them into DolphinDB stream tables for downstream computation.
-
Real-time risk computation : Computes risk metrics for spot and futures accounts separately, then combines them into unified portfolio-level indicators — updated every minute.
-
Real-time data persistence and alerting : Persists all account information and risk metrics to the DolphinDB database in real time, and triggers notifications when defined risk thresholds are exceeded.
Risk Metric Computation
The solution computes risk metrics at three levels: spot account, futures account, and overall portfolio. Variable names and formulas follow Binance's conventions.
Spot account risk metrics
-
Free value (freeValue) : The sum of each asset’s free quantity multiplied by its corresponding market price (midPrice).
-
Locked value (lockedValue) : The sum of each asset’s locked quantity multiplied by its corresponding market price (midPrice).
-
Spot total value (spotTotalValue) : The sum of free value and locked value.
Futures account risk metrics
-
Live unrealized P &L (liveUnrealizedProfit): The sum of potential profits across all futures positions based on the difference between the current market price and the break-even price.
-
Futures net value (futuresNetValue) : The sum of the total futures account value and unrealized profit.
-
Total futures position value (principleAmt) : The sum of each futures position multiplied by its corresponding market price (midPrice).
-
Futures leverage ratio (futuresLeverageRatio) : The total futures position value divided by the total futures account balance.
In the leverage computation, the discountRatio parameter is introduced as a conservative adjustment factor to prevent excessive leverage. In this solution, the discount ratio is set to 0.95.
Overall risk metrics
-
Total value (totalValue) : The sum of spot total value and futures net value.
-
Total leverage ratio (totalLeverageRatio) : The ratio of (spot free value plus the total futures position value) to the total account value.
Building the Risk Model
The following steps walk through building the real-time risk model using Binance as the example exchange.
Create data tables
In DolphinDB, create in-memory tables to store exchange data. Use latestKeyedTable to create key-value in-memory tables for account information (asset and position data) and price data, and share them across all sessions on the current node using share. Key-value in-memory tables make it easy to store the latest data and can be directly used for risk metric computation. The following example shows how to create a spot position table:
// Spot position
colNames = `asset`free`locked`updateTime
colTypes = [SYMBOL, DOUBLE, DOUBLE, TIMESTAMP]
share latestKeyedTable(`asset, `updateTime, 1000:0, colNames, colTypes) as spotBalanceKT
go
For risk metrics, use streamTable to create stream tables, and enable enableTableShareAndPersistence to allow both sharing and persistence.
// Risk metrics
colNames = `freeValue`lockedValue`spotTotalValue`baseCurrency`free`locked`balance`crossWalletBalance`crossUnPnl`availableBalance`maxWithdrawAmount`updateTime`principleAmt`liveUnrealizedProfit`unrealizedProfit`futuresNetValue`futuresLeverageRatio`totalValue`totalLeverageRatio
colTypes = [DOUBLE,DOUBLE,DOUBLE,STRING,DOUBLE,DOUBLE,
DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,TIMESTAMP,DOUBLE,
DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE]
enableTableShareAndPersistence(
table=streamTable(10000:0, colNames, colTypes),
tableName="portfolioRiskIndicatorST",
cacheSize=100000,
preCache=10000
)
go
Retrieve exchange data
Binance provides RESTful APIs for account information (assets and positions) and trading data. Using DolphinDB’s httpClient plugin, these data can be retrieved and written synchronously into DolphinDB tables.
- Account information: Retrieved via background jobs that poll every minute and compute metrics. An example of fetching spot account information is shown below:
def getBinanceSpotAccount(keyInfo,proxy=NULL){
apiKey = keyInfo["apiKey"]
secretKey = keyInfo["secretKey"]
baseUrl = 'https://testnet.binance.vision/api/v3/account'
config = dict(STRING, ANY)
// ...
headers = dict(STRING,STRING)
headers["X-MBX-APIKEY"] = apiKey
headers["Content-type"] = "application/x-www-form-urlencoded"
param = dict(STRING,ANY)
param["recvWindow"] = 5000
target = objByName("spotBalanceKT")
do{
try{
timestamp = getBinanceServerTime("spot")
param["timestamp"] = timestamp
bodyString = signatureByHMAC(param,secretKey)
url = baseUrl + "?" + bodyString
response = httpClient::httpGet(url,,10000,headers,config)
if(response.responseCode!=200){
print("response error: " + response.text)
}
res = parseExpr(response.text).eval()
updateTime = res.updateTime
tb = each(def(mutable d){
d["free"] = double(d["free"]);
d["locked"] = double(d["locked"]);
return d
}, res.balances)
target.tableInsert(
tb.join!(take(updateTime.timestamp()+8*2600*1000,size(tb)) as updateTime)
)
success = true
}catch(ex){
failCnt += 1
print("failCnt " + failCnt + ": " + ex)
if(failCnt == 3){
print("Failed to retrieve spot balance data")
}
}
}while((not success) and failCnt < 3)
return
}
You must configure account credentials via keyInfo. Account data is fetched using httpClient::httpGet, processed, and written into the spotBalanceKT table. All requests use a retry-on-failure mechanism.
- Price data: Real-time cryptocurrency prices are obtained from market data stream tables. After computing midPrice, the data is written into the corresponding in-memory tables.
spotPrice = select symbol, (high+low)/2 as midPrice, eventTime as updateTime
from Cryptocurrency_minKLineST
where symbolSource = "Binance-Spot"
context by symbol, symbolSource
order by eventTime
limit -1
objByName("spotPriceKT").tableInsert(spotPrice)
Compute risk metrics
After retrieving the required data, risk metrics are computed and written into the corresponding stream tables. A partial code example is shown below:
def computePortfolioRiskIndicator(){
// Query spot market data and compute free and locked values
spotmd = select asset, free, locked,
sum(free*midPrice) as freeValue,
sum(locked*midPrice) as lockedValue
from spotBalanceKT
left join spotPriceKT
on spotBalanceKT.asset = left(spotPriceKT.symbol,
strlen(spotPriceKT.symbol)-4)
and spotPriceKT.symbol like '%USDT'
// ...
// Compute portfolio risk metrics
portfolioRiskIndicator = select *,
spotTotalValue + futuresNetValue as totalValue,
(freeValue + principleAmt) / (spotTotalValue + futuresNetValue)
as totalLeverageRatio
from spotRiskIndicator
cross join futuresRiskIndicator
// Write results to the stream table
objByName("portfolioRiskIndicatorST").append!(portfolioRiskIndicator)
}
Schedule Background Jobs
Finally, background jobs are submitted using submitJob, with the interval parameter set to 60000. Once submitted, the function polls account data and computes risk metrics every minute. In the code, spotKeyInfo and futuresKeyInfo correspond to your spot and futures account credentials.
def run_all(spotKeyInfo,futuresKeyInfo,interval=60000){
do{
getBinanceSpotAccount(spotKeyInfo)
getBinanceFutureBalance(futuresKeyInfo)
getBinanceFuturePosition(futuresKeyInfo)
getBinancePrice()
computePortfolioRiskIndicator()
sleep(interval)
}while(true)
}
// ...
submitJob(
"realTimeRiskTest",
"test for cryptocurrency real time risk",
run_all,
spotKeyInfo,
futuresKeyInfo
)
The data in the risk metric stream table (portfolioRiskIndicatorST) is shown in the figure below.
Risk Alert Notifications
Knowing your risk metrics is only half the battle — you also need to be notified the moment something goes wrong. The solution integrates with WeCom using the httpClient plugin to push instant alerts. The implementation is similar to the real-time monitoring and alerting described in Engineering Always-on Market Data Infrastucture.
The main alert function is shown below:
def sendWeChatMsg(msg, webhook){
// JSON data
sendcontent = dict(STRING, ANY)
sendcontent["msgtype"] = "text"
text = dict(STRING, STRING)
text[`content] = msg
sendcontent["text"] = text
header = dict(STRING,STRING)
header["Content-Type"] = "application/json"
sendNum = 0 // for early warning, send once
for(i in 1..5){
if(sendNum != 1){
try {
jsontext = toStdJson(sendcontent)
response = httpClient::httpPost(webhook, jsontext, 10000, header)
sendNum = 1
}catch (ex) {
info = "send message " + webhook + " failed " + ex[1];
writeLogLevel(WARNING, info);
}
}
}
}
-
Select the chat group to receive alerts, create a notification bot, and obtain the webhook URL.
-
Check the risk metric stream table. For example, if freeValue exceeds 12,500, send an alert.
def checkRiskValueFunc(){
freeV = select * from objByName("portfolioRiskIndicatorST")
order by updateTime desc limit 1
freeVal = freeV[`freeValue][0]
if(freeVal > 12500){
msg = "Risk indicator freeValue is: " + freeVal +
". Please take action to control account risk."
sendWeChatMsg(msg)
}
}
- Schedule a task to check risk metrics every 10 minutes:
scheduleJob("checkRiskValue","check risk info",checkRiskValueFunc,09:00m+(0..18)*30,2025.09.12,2035.12.31,"D")
Note: Beyond WeCom, DolphinDB's httpClient plugin supports sending alerts via email or other HTTP-based channels. Refer to the official documentation for integration details.
Conclusion
Real-time risk management in crypto markets demands more than dashboards — it requires a system that acts before losses spiral. By combining DolphinDB's high-performance stream processing with direct exchange API integration, this solution delivers minute-level portfolio risk visibility and automated alerting with minimal infrastructure overhead. From position-level leverage to portfolio-wide net value, every key metric is continuously computed, persisted, and monitored — so you can trade with confidence, even in the most volatile conditions.