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