Skip to content

Commit 7b5da7d

Browse files
authored
Merge pull request #5156 from Mavym246/dase-exchange
[Dase] add new exchange implementation
2 parents e651189 + 3cdbb5c commit 7b5da7d

File tree

79 files changed

+4015
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+4015
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<module>xchange-coinone</module>
7676
<module>xchange-coinsph</module>
7777
<module>xchange-core</module>
78+
<module>xchange-dase</module>
7879
<module>xchange-deribit</module>
7980
<module>xchange-dvchain</module>
8081
<module>xchange-dydx</module>

xchange-dase/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>org.knowm.xchange</groupId>
8+
<artifactId>xchange-parent</artifactId>
9+
<version>5.2.3-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>xchange-dase</artifactId>
13+
14+
<name>XChange Dase</name>
15+
<description>XChange implementation for the Dase Exchange</description>
16+
17+
<dependencies>
18+
19+
<dependency>
20+
<groupId>org.junit.jupiter</groupId>
21+
<artifactId>junit-jupiter-engine</artifactId>
22+
<scope>test</scope>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.knowm.xchange</groupId>
26+
<artifactId>xchange-core</artifactId>
27+
<version>${project.version}</version>
28+
</dependency>
29+
</dependencies>
30+
31+
</project>
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package org.knowm.xchange.dase;
2+
3+
import java.math.BigDecimal;
4+
import java.util.ArrayList;
5+
import java.util.Date;
6+
import java.util.List;
7+
import org.knowm.xchange.currency.Currency;
8+
import org.knowm.xchange.currency.CurrencyPair;
9+
import org.knowm.xchange.dase.dto.account.ApiAccountTxn;
10+
import org.knowm.xchange.dase.dto.account.DaseBalanceItem;
11+
import org.knowm.xchange.dase.dto.account.DaseBalancesResponse;
12+
import org.knowm.xchange.dase.dto.marketdata.DaseOrderBookSnapshot;
13+
import org.knowm.xchange.dase.dto.marketdata.DaseTicker;
14+
import org.knowm.xchange.dase.dto.marketdata.DaseTrade;
15+
import org.knowm.xchange.dase.dto.trade.DaseOrder;
16+
import org.knowm.xchange.dto.Order;
17+
import org.knowm.xchange.dto.Order.OrderType;
18+
import org.knowm.xchange.dto.account.AccountInfo;
19+
import org.knowm.xchange.dto.account.Balance;
20+
import org.knowm.xchange.dto.account.FundingRecord;
21+
import org.knowm.xchange.dto.account.Wallet;
22+
import org.knowm.xchange.dto.marketdata.OrderBook;
23+
import org.knowm.xchange.dto.marketdata.Ticker;
24+
import org.knowm.xchange.dto.marketdata.Trades;
25+
import org.knowm.xchange.dto.trade.LimitOrder;
26+
import org.knowm.xchange.exceptions.ExchangeException;
27+
28+
public final class DaseAdapters {
29+
30+
private DaseAdapters() {}
31+
32+
public static String toMarketString(CurrencyPair pair) {
33+
return pair.getBase().getCurrencyCode() + "-" + pair.getCounter().getCurrencyCode();
34+
}
35+
36+
public static AccountInfo adaptAccountInfo(
37+
String portfolioId, DaseBalancesResponse balancesResponse) {
38+
List<Balance> balances = new ArrayList<>();
39+
if (balancesResponse != null && balancesResponse.getBalances() != null) {
40+
for (DaseBalanceItem b : balancesResponse.getBalances()) {
41+
Currency currency = Currency.getInstance(b.getCurrency());
42+
Balance xchgBalance = new Balance(currency, b.getTotal(), b.getAvailable(), b.getBlocked());
43+
balances.add(xchgBalance);
44+
}
45+
}
46+
Wallet wallet = Wallet.Builder.from(balances).build();
47+
return new AccountInfo(portfolioId, null, List.of(wallet));
48+
}
49+
50+
public static CurrencyPair toCurrencyPair(String market) {
51+
if (market == null) return null;
52+
String[] parts = market.trim().split("-");
53+
if (parts.length != 2) return null;
54+
return new CurrencyPair(parts[0].toUpperCase(), parts[1].toUpperCase());
55+
}
56+
57+
public static Ticker adaptTicker(DaseTicker t, CurrencyPair pair) {
58+
return new Ticker.Builder()
59+
.instrument(pair)
60+
.timestamp(new Date(t.getTime()))
61+
.ask(t.getAsk())
62+
.bid(t.getBid())
63+
.last(t.getPrice())
64+
.volume(t.getVolume())
65+
.build();
66+
}
67+
68+
public static OrderBook adaptOrderBook(DaseOrderBookSnapshot s, CurrencyPair pair) {
69+
List<LimitOrder> bids = createOrders(pair, Order.OrderType.BID, s.getBids());
70+
List<LimitOrder> asks = createOrders(pair, Order.OrderType.ASK, s.getAsks());
71+
return new OrderBook(new Date(s.getTimestamp()), asks, bids);
72+
}
73+
74+
public static Trades adaptTrades(List<DaseTrade> trades, CurrencyPair pair) {
75+
List<org.knowm.xchange.dto.marketdata.Trade> out =
76+
new ArrayList<>(trades == null ? 0 : trades.size());
77+
if (trades != null) {
78+
for (DaseTrade tr : trades) {
79+
// maker_side indicates the maker's side; taker side is the opposite and maps to Trade type
80+
String makerSide = tr.getMakerSide();
81+
Order.OrderType takerType;
82+
if ("buy".equalsIgnoreCase(makerSide)) {
83+
takerType = Order.OrderType.ASK; // maker buy -> taker sell (ASK)
84+
} else if ("sell".equalsIgnoreCase(makerSide)) {
85+
takerType = Order.OrderType.BID; // maker sell -> taker buy (BID)
86+
} else {
87+
takerType = null; // unknown; let builder handle null if allowed
88+
}
89+
90+
out.add(
91+
org.knowm.xchange.dto.marketdata.Trade.builder()
92+
.type(takerType)
93+
.originalAmount(tr.getSize())
94+
.price(tr.getPrice())
95+
.instrument(pair)
96+
.timestamp(new Date(tr.getTime()))
97+
.id(tr.getId())
98+
.build());
99+
}
100+
}
101+
return new Trades(out, Trades.TradeSortType.SortByTimestamp);
102+
}
103+
104+
public static Order adaptOrder(DaseOrder o) {
105+
CurrencyPair pair = toCurrencyPair(o.getMarket());
106+
if (pair == null) {
107+
throw new ExchangeException("Invalid market in order: " + o.getMarket());
108+
}
109+
OrderType side =
110+
"buy".equalsIgnoreCase(o.getSide()) ? Order.OrderType.BID : Order.OrderType.ASK;
111+
BigDecimal originalAmount = parseDecimal(o.getSize());
112+
BigDecimal averagePrice = parseDecimal(o.getFilledPrice());
113+
BigDecimal cumulativeAmount = parseDecimal(o.getFilled());
114+
boolean hasPartial =
115+
cumulativeAmount != null
116+
&& originalAmount != null
117+
&& cumulativeAmount.compareTo(BigDecimal.ZERO) > 0
118+
&& cumulativeAmount.compareTo(originalAmount) < 0;
119+
120+
Order.OrderStatus status;
121+
if ("open".equalsIgnoreCase(o.getStatus())) {
122+
status = hasPartial ? Order.OrderStatus.PARTIALLY_FILLED : Order.OrderStatus.NEW;
123+
} else if ("canceled".equalsIgnoreCase(o.getStatus())) {
124+
status = Order.OrderStatus.CANCELED;
125+
} else if ("closed".equalsIgnoreCase(o.getStatus())) {
126+
boolean isFullyFilled =
127+
originalAmount != null
128+
&& cumulativeAmount != null
129+
&& cumulativeAmount.compareTo(originalAmount) >= 0;
130+
status = isFullyFilled ? Order.OrderStatus.FILLED : Order.OrderStatus.CANCELED;
131+
} else {
132+
status = Order.OrderStatus.UNKNOWN;
133+
}
134+
Date ts = o.getCreatedAt() == null ? null : new Date(o.getCreatedAt());
135+
136+
if ("limit".equalsIgnoreCase(o.getType())) {
137+
BigDecimal price = parseDecimal(o.getPrice());
138+
return new LimitOrder(
139+
side,
140+
originalAmount,
141+
pair,
142+
o.getId(),
143+
ts,
144+
price,
145+
averagePrice,
146+
cumulativeAmount,
147+
null,
148+
status,
149+
o.getClientId());
150+
}
151+
152+
// market: use core MarketOrder DTO
153+
return new org.knowm.xchange.dto.trade.MarketOrder(
154+
side,
155+
// Prefer original amount; if unknown and the order is FILLED, fall back to
156+
// cumulative.
157+
// Otherwise leave null to avoid misrepresenting size.
158+
originalAmount != null
159+
? originalAmount
160+
: (status == Order.OrderStatus.FILLED && cumulativeAmount != null
161+
? cumulativeAmount
162+
: null),
163+
pair,
164+
o.getId(),
165+
ts,
166+
averagePrice,
167+
cumulativeAmount,
168+
null,
169+
status,
170+
o.getClientId());
171+
}
172+
173+
private static BigDecimal parseDecimal(String s) {
174+
if (s == null) return null;
175+
try {
176+
return new BigDecimal(s);
177+
} catch (Exception e) {
178+
return null;
179+
}
180+
}
181+
182+
private static List<LimitOrder> createOrders(
183+
CurrencyPair pair, Order.OrderType side, List<List<BigDecimal>> levels) {
184+
List<LimitOrder> out = new ArrayList<>(levels == null ? 0 : levels.size());
185+
if (levels == null) {
186+
return out;
187+
}
188+
for (List<BigDecimal> l : levels) {
189+
if (l == null || l.size() != 2) {
190+
continue;
191+
}
192+
BigDecimal price = l.get(0);
193+
BigDecimal amount = l.get(1);
194+
if (price == null || amount == null) {
195+
continue;
196+
}
197+
out.add(new LimitOrder(side, amount, pair, null, null, price));
198+
}
199+
return out;
200+
}
201+
202+
public static List<FundingRecord> adaptFundingRecords(List<ApiAccountTxn> txns) {
203+
List<FundingRecord> out = new ArrayList<>(txns == null ? 0 : txns.size());
204+
if (txns == null) {
205+
return out;
206+
}
207+
for (ApiAccountTxn t : txns) {
208+
if (t == null) {
209+
continue;
210+
}
211+
Currency currency = t.getCurrency() == null ? null : Currency.getInstance(t.getCurrency());
212+
Date date = new Date(t.getCreatedAt());
213+
214+
FundingRecord.Type type = mapTxnTypeToFundingType(t.getTxnType());
215+
FundingRecord.Status status = FundingRecord.Status.COMPLETE;
216+
String description = t.getTxnType();
217+
218+
FundingRecord fr =
219+
FundingRecord.builder()
220+
.date(date)
221+
.currency(currency)
222+
.amount(t.getAmount())
223+
.internalId(t.getId())
224+
.type(type)
225+
.status(status)
226+
.description(description)
227+
.build();
228+
out.add(fr);
229+
}
230+
return out;
231+
}
232+
233+
private static FundingRecord.Type mapTxnTypeToFundingType(String txnType) {
234+
if (txnType == null) {
235+
throw new IllegalArgumentException("txnType is null");
236+
}
237+
switch (txnType) {
238+
case "deposit":
239+
return FundingRecord.Type.DEPOSIT;
240+
case "withdrawal_commit":
241+
return FundingRecord.Type.WITHDRAWAL;
242+
case "withdrawal_block":
243+
return FundingRecord.Type.OTHER_OUTFLOW;
244+
case "withdrawal_unblock":
245+
return FundingRecord.Type.OTHER_INFLOW;
246+
case "trade_fill_fee_base":
247+
case "trade_fill_fee_quote":
248+
return FundingRecord.Type.OTHER_OUTFLOW;
249+
case "trade_fill_credit_base":
250+
case "trade_fill_credit_quote":
251+
return FundingRecord.Type.OTHER_INFLOW;
252+
case "trade_fill_debit_base":
253+
case "trade_fill_debit_quote":
254+
return FundingRecord.Type.OTHER_OUTFLOW;
255+
case "portfolio_transfer_credit":
256+
return FundingRecord.Type.INTERNAL_DEPOSIT;
257+
case "portfolio_transfer_debit":
258+
return FundingRecord.Type.INTERNAL_WITHDRAWAL;
259+
default:
260+
throw new IllegalArgumentException("Unknown txnType: " + txnType);
261+
}
262+
}
263+
}

0 commit comments

Comments
 (0)