1 // This file is part of Qt Bitcoin Trader
2 // https://github.com/JulyIGHOR/QtBitcoinTrader
3 // Copyright (C) 2013-2021 July Ighor <julyighor@gmail.com>
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // In addition, as a special exception, the copyright holders give
11 // permission to link the code of portions of this program with the
12 // OpenSSL library under certain conditions as described in each
13 // individual source file, and distribute linked combinations including
14 // the two.
15 //
16 // You must obey the GNU General Public License in all respects for all
17 // of the code used other than OpenSSL. If you modify file(s) with this
18 // exception, you may extend this exception to your version of the
19 // file(s), but you are not obligated to do so. If you do not wish to do
20 // so, delete this exception statement from your version. If you delete
21 // this exception statement from all source files in the program, then
22 // also delete it here.
23 //
24 // This program is distributed in the hope that it will be useful,
25 // but WITHOUT ANY WARRANTY; without even the implied warranty of
26 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 // GNU General Public License for more details.
28 //
29 // You should have received a copy of the GNU General Public License
30 // along with this program. If not, see <http://www.gnu.org/licenses/>.
31
32 #include "exchange.h"
33 #include "main.h"
34 #include "depthitem.h"
35 #include <QFile>
36 #include "currencypairitem.h"
37 #include "iniengine.h"
38
Exchange()39 Exchange::Exchange()
40 : QObject()
41 {
42 multiCurrencyTradeSupport = false;
43 exchangeDisplayOnlyCurrentPairOpenOrders = false;
44 orderBookItemIsDedicatedOrder = false;
45 supportsExchangeFee = true;
46 supportsExchangeVolume = true;
47 clearOpenOrdersOnCurrencyChanged = false;
48 clearHistoryOnCurrencyChanged = false;
49 exchangeTickerSupportsHiLowPrices = true;
50 depthEnabledFlag = true;
51 balanceDisplayAvailableAmount = true;
52 minimumRequestIntervalAllowed = 100;
53 minimumRequestTimeoutAllowed = 2000;
54 decAmountFromOpenOrder = 0.0;
55 buySellAmountExcludedFee = false;
56 calculatingFeeMode = 0;
57 supportsLoginIndicator = true;
58 supportsAccountVolume = true;
59 exchangeSupportsAvailableAmount = false;
60 checkDuplicatedOID = false;
61 isLastTradesTypeSupported = true;
62 forceDepthLoad = false;
63 port = 0;
64 useSsl = true;
65 m_pairChangeCount = 1;
66
67 clearVariables();
68 }
69
~Exchange()70 Exchange::~Exchange()
71 {
72 if (debugLevel)
73 logThread->writeLogB(baseValues.exchangeName + " API Thread Deleted", 2);
74 }
75
isDepthEnabled() const76 bool Exchange::isDepthEnabled() const
77 {
78 return depthEnabledFlag || baseValues.scriptsThatUseOrderBookCount;
79 }
80
getMidData(const QString & a,const QString & b,const QByteArray * data)81 QByteArray Exchange::getMidData(const QString& a, const QString& b, const QByteArray* data)
82 {
83 QByteArray rez;
84
85 int startPos = data->indexOf(a, 0);
86
87 if (startPos > -1)
88 {
89 int endPos = data->indexOf(b.isEmpty() ? "\"," : b, startPos + a.length());
90
91 if (endPos > -1)
92 rez = data->mid(startPos + a.length(), endPos - startPos - a.length());
93 }
94
95 return rez;
96 }
97
getMidVal(const QString & a,const QString & b,const QByteArray * data)98 QByteArray Exchange::getMidVal(const QString& a, const QString& b, const QByteArray* data)
99 {
100 QByteArray rez;
101
102 int startPos = data->indexOf(a, 0);
103
104 if (startPos > -1)
105 {
106 startPos += a.length();
107
108 if (startPos < data->size() && data->at(startPos) == '\"')
109 ++startPos;
110
111 int endPos = data->indexOf(b.isEmpty() ? "," : b, startPos);
112
113 if (endPos > -1)
114 {
115 if (data->at(endPos - 1) == '\"')
116 --endPos;
117
118 rez = data->mid(startPos, endPos - startPos);
119 }
120 }
121
122 return rez;
123 }
124
translateUnicodeStr(QString * str)125 void Exchange::translateUnicodeStr(QString* str)
126 {
127 const QRegExp rx("(\\\\u[0-9a-fA-F]{4})");
128 int pos = 0;
129
130 while ((pos = rx.indexIn(*str, pos)) != -1)
131 str->replace(pos++, 6, QChar(rx.cap(1).right(4).toUShort(nullptr, 16)));
132 }
133
translateUnicodeOne(QByteArray * str)134 void Exchange::translateUnicodeOne(QByteArray* str)
135 {
136 if (!str->contains("\\u"))
137 return;
138
139 QStringList bytesList = QString(*str).split("\\u");
140
141 if (!bytesList.empty())
142 bytesList.removeFirst();
143 else
144 return;
145
146 QString strToReturn;
147
148 for (int n = 0; n < bytesList.size(); n++)
149 if (bytesList.at(n).length() > 3)
150 strToReturn += "\\u" + bytesList.at(n).left(4);
151
152 translateUnicodeStr(&strToReturn);
153 *str = strToReturn.toLatin1();
154 }
155
run()156 void Exchange::run()
157 {
158 if (debugLevel)
159 logThread->writeLogB(baseValues.exchangeName + " API Thread Started", 2);
160
161 clearVariables();
162
163 QSettings iniSettings(baseValues.iniFileName, QSettings::IniFormat);
164 domain = iniSettings.value("Domain").toString();
165 port = static_cast<quint16>(iniSettings.value("Port", 0).toUInt());
166 useSsl = iniSettings.value("SSL", true).toBool();
167
168 if (domain.startsWith("http://"))
169 domain.remove(0, 7);
170 else if (domain.startsWith("https://"))
171 domain.remove(0, 8);
172
173 secondTimer.reset(new QTimer);
174 secondTimer->setSingleShot(true);
175 connect(secondTimer.data(), &QTimer::timeout, this, &Exchange::secondSlot);
176
177 connect(QThread::currentThread(), &QThread::finished, this, &Exchange::quitExchange, Qt::DirectConnection);
178 secondSlot();
179 emit started();
180 }
181
quitExchange()182 void Exchange::quitExchange()
183 {
184 secondTimer.reset();
185 emit threadFinished();
186 }
187
secondSlot()188 void Exchange::secondSlot()
189 {
190 if (secondTimer)
191 secondTimer->start(baseValues.httpRequestInterval);
192 }
193
dataReceivedAuth(const QByteArray &,int,int)194 void Exchange::dataReceivedAuth(const QByteArray& /*unused*/, int /*unused*/, int /*unused*/)
195 {
196 }
197
reloadDepth()198 void Exchange::reloadDepth()
199 {
200 forceDepthLoad = true;
201 }
202
clearVariables()203 void Exchange::clearVariables()
204 {
205 lastTickerLast = 0.0;
206 lastTickerHigh = 0.0;
207 lastTickerLow = 0.0;
208 lastTickerSell = 0.0;
209 lastTickerBuy = 0.0;
210 lastTickerVolume = 0.0;
211
212 lastBtcBalance = 0.0;
213 lastUsdBalance = 0.0;
214 lastAvUsdBalance = 0.0;
215 lastVolume = 0.0;
216 lastFee = -1.0;
217
218 ++m_pairChangeCount;
219 }
220
filterAvailableUSDAmountValue(double *)221 void Exchange::filterAvailableUSDAmountValue(double* /*unused*/)
222 {
223
224 }
225
setupApi(QtBitcoinTrader * mainClass,bool tickOnly)226 void Exchange::setupApi(QtBitcoinTrader* mainClass, bool tickOnly)//Execute only once
227 {
228 IniEngine::loadExchangeLock(currencyMapFile, defaultCurrencyParams);
229 tickerOnly = tickOnly;
230
231 if (!tickerOnly)
232 {
233 connect(mainClass, SIGNAL(apiBuy(QString, double, double)), this, SLOT(buy(QString, double, double)));
234 connect(mainClass, SIGNAL(apiSell(QString, double, double)), this, SLOT(sell(const QString&, double, double)));
235 connect(mainClass, SIGNAL(cancelOrderByOid(QString, QByteArray)), this, SLOT(cancelOrder(const QString&, const QByteArray&)));
236 connect(mainClass, SIGNAL(getHistory(bool)), this, SLOT(getHistory(bool)));
237
238 connect(this, SIGNAL(orderBookChanged(QString, QList<OrderItem>*)), mainClass, SLOT(orderBookChanged(QString,
239 QList<OrderItem>*)));
240 connect(this, SIGNAL(historyChanged(QList<HistoryItem>*)), mainClass, SLOT(historyChanged(QList<HistoryItem>*)));
241 connect(this, SIGNAL(orderCanceled(QString, QByteArray)), mainClass, SLOT(orderCanceled(QString, QByteArray)));
242 connect(this, SIGNAL(ordersIsEmpty()), mainClass, SLOT(ordersIsEmpty()));
243 }
244
245 connect(this, SIGNAL(depthRequested()), mainClass, SLOT(depthRequested()));
246 connect(this, SIGNAL(depthRequestReceived()), mainClass, SLOT(depthRequestReceived()));
247 connect(this, SIGNAL(depthSubmitOrders(QString, QList<DepthItem>*, QList<DepthItem>*)), mainClass,
248 SLOT(depthSubmitOrders(QString, QList<DepthItem>*, QList<DepthItem>*)));
249 connect(this, SIGNAL(depthFirstOrder(QString, double, double, bool)), mainClass, SLOT(depthFirstOrder(QString, double,
250 double, bool)));
251 connect(this, SIGNAL(showErrorMessage(QString)), mainClass, SLOT(showErrorMessage(QString)));
252
253 connect(this, SIGNAL(availableAmountChanged(QString, double)), mainClass, SLOT(availableAmountChanged(QString,
254 double)));
255 connect(mainClass, SIGNAL(clearValues()), this, SLOT(clearValues()));
256 connect(mainClass, SIGNAL(reloadDepth()), this, SLOT(reloadDepth()));
257
258 connect(this, SIGNAL(accVolumeChanged(double)), mainClass->ui.accountVolume, SLOT(setValue(double)));
259 connect(this, SIGNAL(accFeeChanged(QString, double)), mainClass, SLOT(accFeeChanged(QString, double)));
260 connect(this, SIGNAL(accBtcBalanceChanged(QString, double)), mainClass, SLOT(accBtcBalanceChanged(QString, double)));
261 connect(this, SIGNAL(accUsdBalanceChanged(QString, double)), mainClass, SLOT(accUsdBalanceChanged(QString, double)));
262
263 connect(this, SIGNAL(loginChanged(QString)), mainClass, SLOT(loginChanged(QString)));
264
265 connect(this, SIGNAL(addLastTrades(QString, QList<TradesItem>*)), mainClass, SLOT(addLastTrades(QString,
266 QList<TradesItem>*)));
267 }
268
setApiKeySecret(QByteArray key,QByteArray secret)269 void Exchange::setApiKeySecret(QByteArray key, QByteArray secret)
270 {
271 if (!apiKeyChars.isEmpty())
272 return;
273
274 privateKey = key;
275
276 for (int n = secret.size() - 1; n >= 0; n--)
277 apiSignChars << new char(secret[n]);
278 }
279
getApiKey()280 QByteArray& Exchange::getApiKey()
281 {
282 return privateKey;
283 }
284
getApiSign()285 QByteArray Exchange::getApiSign()
286 {
287 QByteArray result;
288
289 for (int n = apiSignChars.size() - 1; n >= 0; n--)
290 result += *(apiSignChars[n]);
291
292 return result;
293 }
294
clearValues()295 void Exchange::clearValues()
296 {
297
298 }
299
getHistory(bool)300 void Exchange::getHistory(bool /*unused*/)
301 {
302 }
303
buy(const QString &,double,double)304 void Exchange::buy(const QString& /*unused*/, double /*unused*/, double /*unused*/)
305 {
306 }
307
sell(const QString &,double,double)308 void Exchange::sell(const QString& /*unused*/, double /*unused*/, double /*unused*/)
309 {
310 }
311
cancelOrder(const QString &,const QByteArray &)312 void Exchange::cancelOrder(const QString& /*unused*/, const QByteArray& /*unused*/)
313 {
314 }
315
sslErrors(const QList<QSslError> & errors)316 void Exchange::sslErrors(const QList<QSslError>& errors)
317 {
318 QStringList errorList;
319
320 for (int n = 0; n < errors.size(); n++)
321 errorList << errors.at(n).errorString();
322
323 if (debugLevel)
324 logThread->writeLog(errorList.join(" ").toLatin1(), 2);
325
326 emit showErrorMessage("SSL Error: " + errorList.join(" "));
327 }
328
checkValue(QByteArray & valueStr,double & lastValue)329 bool Exchange::checkValue(QByteArray& valueStr, double& lastValue)
330 {
331 double value = valueStr.toDouble();
332
333 if (!qFuzzyIsNull(value) && value < 0.0)
334 return false;
335
336 if (qFuzzyCompare(value + 1.0, lastValue + 1.0))
337 return false;
338
339 lastValue = value;
340 return true;
341 }
342