Skip to content

Commit f96d2b8

Browse files
committed
Implement portfolio management and conversion features in Coinbase SDK
- Added methods for listing, creating, editing, and deleting portfolios in CoinbaseRestClient. - Implemented portfolio breakdown retrieval and fund movement between portfolios. - Introduced conversion quote creation and trade commitment functionalities. - Added support for futures operations including balance summary, positions, and sweeps. - Enhanced awaitable client with corresponding asynchronous methods for portfolio and futures operations. - Expanded test coverage for new portfolio and futures functionalities, ensuring robustness and correctness.
1 parent 1166f07 commit f96d2b8

17 files changed

Lines changed: 1706 additions & 24 deletions

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [unreleased]
8+
## [0.3.0] - 2026-06-08
99

1010
### Added
1111
- taker_fee_rate and maker_fee_rate in REST api.
12+
- Portfolios endpoints: `list_portfolios`, `create_portfolio`, `get_portfolio_breakdown`, `move_portfolio_funds`, `edit_portfolio`, `delete_portfolio`
13+
- Convert endpoints: `create_convert_quote`, `get_convert_trade`, `commit_convert_trade`
14+
- Payment Methods endpoints: `list_payment_methods`, `get_payment_method`
15+
- Data API endpoint: `get_api_key_permissions`
16+
- Futures (CFM) endpoints: `get_futures_balance_summary`, `list_futures_positions`, `get_futures_position`, `schedule_futures_sweep`, `list_futures_sweeps`, `cancel_pending_futures_sweep`, `get_intraday_margin_setting`, `get_current_margin_window`, `set_intraday_margin_setting`
17+
- Perpetuals (INTX) endpoints: `allocate_portfolio`, `get_perps_portfolio_summary`, `list_perps_positions`, `get_perps_position`, `get_perps_portfolio_balances`, `opt_in_or_out_multi_asset_collateral`
18+
- Async mirrors of all the above in `CoinbaseAwaitableRestClient`, plus new data model headers (`portfolio.hpp`, `convert.hpp`, `payment_method.hpp`, `key_permissions.hpp`, `futures.hpp`, `perpetuals.hpp`) and `Amount` type in `common.hpp`
1219

1320
### Changed
1421
- Change log file opening mode to append for data logging
@@ -23,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2330

2431
### Fixed
2532
- `unsubscribe()` now holds a reference to the `unique_ptr` instead of copying it
33+
- `double_from_json` now handles fields the API returns as raw JSON numbers (not just stringified numbers), fixing parsing of `PortfolioPosition.allocation`/`available_to_trade_fiat`
2634

2735
## [0.2.2] - 2026-03-05
2836

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.20)
33
set(CMAKE_CXX_STANDARD 20)
44

55
project(coinbase-advanced-cpp
6-
VERSION 0.2.2
6+
VERSION 0.3.0
77
LANGUAGES CXX)
88

99
if (CMAKE_BUILD_TYPE MATCHES Debug)

README.md

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,29 @@ The SDK is organized into several key components:
2727

2828
```
2929
include/coinbase/
30-
├── account.h # Account and balance management
31-
├── auth.h # Authentication utilities
32-
├── candle.h # Candlestick data
33-
├── common.h # Common types and enums
34-
├── fill.h # Fill data
35-
├── market_data.h # Market data structures
36-
├── order.h # Order management
37-
├── position.h # Position management
38-
├── price_book.h # Price book data
39-
├── product.h # Product information
40-
├── rest.h # REST client implementation
41-
├── rest_awaitable.h # Async REST operations
42-
├── side.h # Order side definitions
43-
├── trades.h # Trade data
44-
├── utils.h # Utility functions
45-
└── websocket.h # WebSocket client implementation
30+
├── account.hpp # Account and balance management
31+
├── auth.hpp # Authentication utilities
32+
├── candle.hpp # Candlestick data
33+
├── common.hpp # Common types and enums
34+
├── convert.hpp # Currency conversion (Convert) data models
35+
├── fill.hpp # Fill data
36+
├── futures.hpp # Futures (CFM) data models
37+
├── key_permissions.hpp # API key permissions (Data API) data models
38+
├── logging.hpp # Logging utilities
39+
├── market_data.hpp # Market data structures
40+
├── order.hpp # Order management
41+
├── payment_method.hpp # Payment methods data models
42+
├── perpetuals.hpp # Perpetuals (INTX) data models
43+
├── portfolio.hpp # Portfolios data models
44+
├── position.hpp # Position management
45+
├── price_book.hpp # Price book data
46+
├── product.hpp # Product information
47+
├── rest.hpp # REST client implementation
48+
├── rest_awaitable.hpp # Async REST operations
49+
├── side.hpp # Order side definitions
50+
├── trades.hpp # Trade data
51+
├── utils.hpp # Utility functions
52+
└── websocket.hpp # WebSocket client implementation
4653
```
4754

4855
## Installation
@@ -84,7 +91,7 @@ cmake --build build
8491
#### REST Client
8592

8693
```cpp
87-
#include <coinbase/rest.h>
94+
#include <coinbase/rest.hpp>
8895

8996
// Create client
9097
coinbase::CoinbaseRestClient client;
@@ -112,6 +119,34 @@ auto response = client.create_order(
112119

113120
// Cancel order
114121
auto cancel_response = client.cancel_orders({"order_id_123"});
122+
123+
// Portfolios
124+
auto portfolios = client.list_portfolios();
125+
auto breakdown = client.get_portfolio_breakdown(portfolios.front().uuid);
126+
127+
// Convert (quote only - commit_convert_trade executes a real conversion)
128+
auto quote = client.create_convert_quote(from_account_uuid, to_account_uuid, 10.0);
129+
130+
// Futures (CFM)
131+
auto balance_summary = client.get_futures_balance_summary();
132+
auto positions = client.list_futures_positions();
133+
```
134+
135+
#### Async REST Client
136+
137+
`CoinbaseAwaitableRestClient` mirrors every `CoinbaseRestClient` method as a C++20 coroutine returning `asio::awaitable<T>`, so the same endpoints (including all of the ones listed in [API Endpoints](#api-endpoints)) can be awaited from coroutine-based code:
138+
139+
```cpp
140+
#include <coinbase/rest_awaitable.hpp>
141+
142+
coinbase::CoinbaseAwaitableRestClient client;
143+
144+
asio::awaitable<void> run() {
145+
auto accounts = co_await client.list_accounts();
146+
auto portfolios = co_await client.list_portfolios();
147+
auto permissions = co_await client.get_api_key_permissions();
148+
// ...
149+
}
115150
```
116151

117152
#### WebSocket Client
@@ -125,7 +160,7 @@ The SDK provides two callback mechanisms for handling WebSocket data:
125160
##### Using WebsocketCallbacks (WebSocket Thread)
126161

127162
```cpp
128-
#include <coinbase/websocket.h>
163+
#include <coinbase/websocket.hpp>
129164

130165
class MyCallbacks : public coinbase::WebsocketCallbacks {
131166
public:
@@ -193,7 +228,7 @@ client.stop();
193228
##### Using UserThreadWebsocketCallbacks (User Thread)
194229
195230
```cpp
196-
#include <coinbase/websocket.h>
231+
#include <coinbase/websocket.hpp>
197232
198233
class MyCallbacks : public coinbase::UserThreadWebsocketCallbacks {
199234
public:
@@ -244,8 +279,17 @@ client.stop();
244279
- **Products**: List products, get product details
245280
- **Orders**: Create, list, get, modify, and cancel orders
246281
- **Fills**: List fills
282+
- **Fees**: Get taker and maker fee rates
247283
- **Market Data**: Get best bid/ask, price book, market trades, candles
248284
- **Time**: Get server time
285+
- **Portfolios**: List portfolios, create/edit/delete a portfolio, get portfolio breakdown, move funds between portfolios
286+
- **Convert**: Create a convert quote, get a convert trade, commit a convert trade
287+
- **Payment Methods**: List payment methods, get payment method details
288+
- **Data API**: Get API key permissions
289+
- **Futures (CFM)**: Get balance summary, list/get positions, schedule/list/cancel sweeps, get/set intraday margin settings, get current margin window
290+
- **Perpetuals (INTX)**: Allocate portfolio, get portfolio summary, list/get positions, get portfolio balances, opt in/out of multi-asset collateral
291+
292+
All REST endpoints are available on both the synchronous `CoinbaseRestClient` and the coroutine-based `CoinbaseAwaitableRestClient` (see [Async REST Client](#async-rest-client)).
249293

250294
### WebSocket API
251295

include/coinbase/common.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
#include <cstdint>
88
#include <string_view>
99
#include <string>
10+
#include <nlohmann/json.hpp>
11+
#include <coinbase/utils.hpp>
12+
13+
using json = nlohmann::json;
1014

1115
namespace coinbase {
1216

@@ -40,6 +44,16 @@ inline std::string to_string(ContractExpiryType cet) {
4044
return "UNKNOWN_CONTRACT_EXPIRY_TYPE";
4145
}
4246

47+
struct Amount {
48+
double value;
49+
std::string currency;
50+
};
51+
52+
inline void from_json(const json &j, Amount &a) {
53+
DOUBLE_FROM_JSON(j, a, value);
54+
VARIABLE_FROM_JSON(j, a, currency);
55+
}
56+
4357
struct Commission {
4458
double total_commission;
4559
double gst_commission;

include/coinbase/convert.hpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2025-2026 Slick Quant
3+
// https://github.com/SlickQuant/slick-socket
4+
5+
#pragma once
6+
7+
#include <string>
8+
#include <vector>
9+
#include <nlohmann/json.hpp>
10+
#include <coinbase/common.hpp>
11+
#include <coinbase/utils.hpp>
12+
13+
using json = nlohmann::json;
14+
15+
namespace coinbase {
16+
17+
struct ConvertTradePaymentMethod {
18+
std::string type;
19+
std::string network;
20+
};
21+
22+
inline void from_json(const json &j, ConvertTradePaymentMethod &m) {
23+
VARIABLE_FROM_JSON(j, m, type);
24+
VARIABLE_FROM_JSON(j, m, network);
25+
}
26+
27+
struct Fee {
28+
std::string title;
29+
std::string description;
30+
std::string label;
31+
Amount amount;
32+
};
33+
34+
inline void from_json(const json &j, Fee &f) {
35+
VARIABLE_FROM_JSON(j, f, title);
36+
VARIABLE_FROM_JSON(j, f, description);
37+
VARIABLE_FROM_JSON(j, f, label);
38+
STRUCT_FROM_JSON(j, f, amount);
39+
}
40+
41+
struct ConvertTrade {
42+
std::string id;
43+
std::string status;
44+
std::string user_reference;
45+
std::string source_currency;
46+
std::string source_id;
47+
std::string target_id;
48+
Amount user_entered_amount;
49+
Amount amount;
50+
Amount subtotal;
51+
Amount total;
52+
Amount total_fee;
53+
Amount exchange_rate;
54+
Amount fiat_denoted_total;
55+
std::vector<Fee> fees;
56+
ConvertTradePaymentMethod source;
57+
ConvertTradePaymentMethod target;
58+
};
59+
60+
inline void from_json(const json &j, ConvertTrade &t) {
61+
VARIABLE_FROM_JSON(j, t, id);
62+
VARIABLE_FROM_JSON(j, t, status);
63+
VARIABLE_FROM_JSON(j, t, user_reference);
64+
VARIABLE_FROM_JSON(j, t, source_currency);
65+
VARIABLE_FROM_JSON(j, t, source_id);
66+
VARIABLE_FROM_JSON(j, t, target_id);
67+
STRUCT_FROM_JSON(j, t, user_entered_amount);
68+
STRUCT_FROM_JSON(j, t, amount);
69+
STRUCT_FROM_JSON(j, t, subtotal);
70+
STRUCT_FROM_JSON(j, t, total);
71+
STRUCT_FROM_JSON(j, t, total_fee);
72+
STRUCT_FROM_JSON(j, t, exchange_rate);
73+
STRUCT_FROM_JSON(j, t, fiat_denoted_total);
74+
STRUCT_FROM_JSON(j, t, fees);
75+
STRUCT_FROM_JSON(j, t, source);
76+
STRUCT_FROM_JSON(j, t, target);
77+
}
78+
79+
} // end namespace coinbase

include/coinbase/futures.hpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2025-2026 Slick Quant
3+
// https://github.com/SlickQuant/slick-socket
4+
5+
#pragma once
6+
7+
#include <string>
8+
#include <vector>
9+
#include <nlohmann/json.hpp>
10+
#include <coinbase/common.hpp>
11+
#include <coinbase/side.hpp>
12+
#include <coinbase/utils.hpp>
13+
14+
using json = nlohmann::json;
15+
16+
namespace coinbase {
17+
18+
struct FCMBalanceSummary {
19+
Amount futures_buying_power;
20+
Amount total_usd_balance;
21+
Amount cbi_usd_balance;
22+
Amount cfm_usd_balance;
23+
Amount total_open_orders_hold_amount;
24+
Amount unrealized_pnl;
25+
Amount daily_realized_pnl;
26+
Amount initial_margin;
27+
Amount available_margin;
28+
Amount liquidation_threshold;
29+
Amount liquidation_buffer_amount;
30+
std::string liquidation_buffer_percentage;
31+
};
32+
33+
inline void from_json(const json &j, FCMBalanceSummary &b) {
34+
STRUCT_FROM_JSON(j, b, futures_buying_power);
35+
STRUCT_FROM_JSON(j, b, total_usd_balance);
36+
STRUCT_FROM_JSON(j, b, cbi_usd_balance);
37+
STRUCT_FROM_JSON(j, b, cfm_usd_balance);
38+
STRUCT_FROM_JSON(j, b, total_open_orders_hold_amount);
39+
STRUCT_FROM_JSON(j, b, unrealized_pnl);
40+
STRUCT_FROM_JSON(j, b, daily_realized_pnl);
41+
STRUCT_FROM_JSON(j, b, initial_margin);
42+
STRUCT_FROM_JSON(j, b, available_margin);
43+
STRUCT_FROM_JSON(j, b, liquidation_threshold);
44+
STRUCT_FROM_JSON(j, b, liquidation_buffer_amount);
45+
VARIABLE_FROM_JSON(j, b, liquidation_buffer_percentage);
46+
}
47+
48+
struct FCMPosition {
49+
std::string product_id;
50+
Side side;
51+
double number_of_contracts;
52+
double current_price;
53+
double avg_entry_price;
54+
double unrealized_pnl;
55+
double daily_realized_pnl;
56+
};
57+
58+
inline void from_json(const json &j, FCMPosition &p) {
59+
VARIABLE_FROM_JSON(j, p, product_id);
60+
ENUM_FROM_JSON(j, p, side);
61+
DOUBLE_FROM_JSON(j, p, number_of_contracts);
62+
DOUBLE_FROM_JSON(j, p, current_price);
63+
DOUBLE_FROM_JSON(j, p, avg_entry_price);
64+
DOUBLE_FROM_JSON(j, p, unrealized_pnl);
65+
DOUBLE_FROM_JSON(j, p, daily_realized_pnl);
66+
}
67+
68+
struct MarginWindow {
69+
std::string margin_window_type;
70+
std::string end_time;
71+
};
72+
73+
inline void from_json(const json &j, MarginWindow &w) {
74+
VARIABLE_FROM_JSON(j, w, margin_window_type);
75+
VARIABLE_FROM_JSON(j, w, end_time);
76+
}
77+
78+
struct FCMSweep {
79+
std::string id;
80+
std::string status;
81+
Amount requested_amount;
82+
bool should_sweep_all;
83+
};
84+
85+
inline void from_json(const json &j, FCMSweep &s) {
86+
VARIABLE_FROM_JSON(j, s, id);
87+
VARIABLE_FROM_JSON(j, s, status);
88+
STRUCT_FROM_JSON(j, s, requested_amount);
89+
VARIABLE_FROM_JSON(j, s, should_sweep_all);
90+
}
91+
92+
struct CurrentMarginWindow {
93+
MarginWindow margin_window;
94+
bool is_intraday_margin_killswitch_enabled;
95+
bool is_intraday_margin_enrollment_killswitch_enabled;
96+
};
97+
98+
inline void from_json(const json &j, CurrentMarginWindow &w) {
99+
STRUCT_FROM_JSON(j, w, margin_window);
100+
VARIABLE_FROM_JSON(j, w, is_intraday_margin_killswitch_enabled);
101+
VARIABLE_FROM_JSON(j, w, is_intraday_margin_enrollment_killswitch_enabled);
102+
}
103+
104+
} // end namespace coinbase

0 commit comments

Comments
 (0)